From 8e6f148ff3ff36995e29f770d1e8689772880f6f Mon Sep 17 00:00:00 2001 From: mattknatt Date: Thu, 23 Apr 2026 10:44:09 +0200 Subject: [PATCH 1/2] Disable Spring JPA Open-In-View setting in application properties --- src/main/resources/application.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 007d45e..4736fa9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,6 +5,7 @@ spring.datasource.username=arende spring.datasource.password=arende spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.open-in-view=false spring.sql.init.mode=always spring.jpa.defer-datasource-initialization=true spring.datasource.driver-class-name=org.postgresql.Driver From 236f2e979e35f02371049bf8956598bbb7c3f6f2 Mon Sep 17 00:00:00 2001 From: mattknatt Date: Thu, 23 Apr 2026 10:54:42 +0200 Subject: [PATCH 2/2] Annotate document service methods with `@Transactional(readOnly = true)` and add integration tests for document listing and download actions. --- .../application/service/DocumentService.java | 3 ++ .../DocumentIntegrationTest.java | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/main/java/org/example/projektarendehantering/application/service/DocumentService.java b/src/main/java/org/example/projektarendehantering/application/service/DocumentService.java index c787e8b..fb51b6d 100644 --- a/src/main/java/org/example/projektarendehantering/application/service/DocumentService.java +++ b/src/main/java/org/example/projektarendehantering/application/service/DocumentService.java @@ -138,6 +138,7 @@ public void afterCompletion(int status) { } } + @Transactional(readOnly = true) public List listDocuments(Actor actor, UUID caseId) { CaseEntity caseEntity = caseRepository.findById(caseId) .orElseThrow(() -> new BadRequestException("Case not found")); @@ -153,6 +154,7 @@ public List listDocuments(Actor actor, UUID caseId) { .collect(Collectors.toList()); } + @Transactional(readOnly = true) public S3Resource downloadDocument(Actor actor, UUID documentId) { DocumentEntity entity = documentRepository.findById(documentId) .orElseThrow(() -> new BadRequestException("Document not found")); @@ -204,6 +206,7 @@ public void afterCommit() { } + @Transactional(readOnly = true) public DocumentEntity getEntity(Actor actor, UUID documentId) { DocumentEntity entity = documentRepository.findById(documentId) .orElseThrow(() -> new BadRequestException("Document not found")); diff --git a/src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java b/src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java index 91ef5ec..9d7b854 100644 --- a/src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java +++ b/src/test/java/org/example/projektarendehantering/DocumentIntegrationTest.java @@ -22,6 +22,9 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; +import io.awspring.cloud.s3.S3Resource; + +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.UUID; @@ -32,6 +35,9 @@ import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; @@ -103,6 +109,37 @@ void uploadDocument_shouldSucceed() throws Exception { verify(s3Template).upload(eq("test-documents"), any(), any(InputStream.class), any(ObjectMetadata.class)); } + @Test + @WithMockUser(username = "manager", roles = {"MANAGER"}) + void listDocuments_shouldReturnDocumentsWithoutLazyLoadingException() throws Exception { + MockMultipartFile file = new MockMultipartFile("file", "test.txt", MediaType.TEXT_PLAIN_VALUE, "hello".getBytes()); + mockMvc.perform(multipart("/ui/cases/{caseId}/documents/upload", caseId) + .file(file).with(csrf())) + .andExpect(status().is3xxRedirection()); + + mockMvc.perform(get("/ui/cases/{caseId}", caseId)) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser(username = "manager", roles = {"MANAGER"}) + void downloadDocument_shouldSucceedWithoutLazyLoadingException() throws Exception { + MockMultipartFile file = new MockMultipartFile("file", "test.txt", MediaType.TEXT_PLAIN_VALUE, "hello".getBytes()); + mockMvc.perform(multipart("/ui/cases/{caseId}/documents/upload", caseId) + .file(file).with(csrf())) + .andExpect(status().is3xxRedirection()); + + UUID docId = documentRepository.findAllByCaseEntityId(caseId).get(0).getId(); + + S3Resource mockResource = mock(S3Resource.class); + when(mockResource.getInputStream()).thenReturn(new ByteArrayInputStream("content".getBytes())); + when(mockResource.contentLength()).thenReturn(7L); + when(s3Template.download(any(), any())).thenReturn(mockResource); + + mockMvc.perform(get("/ui/cases/{caseId}/documents/{docId}/download", caseId, docId)) + .andExpect(status().isOk()); + } + @Test @WithMockUser(username = "other", roles = {"DOCTOR"}) void uploadDocument_shouldDenyUnauthorized() throws Exception {