diff --git a/backend/pom.xml b/backend/pom.xml index f2dc9a8b..da863de2 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -193,8 +193,7 @@ verify report - + check verify @@ -207,7 +206,7 @@ BRANCH COVEREDRATIO - 0.42 + 0.88 diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java index f316bb31..ac353a97 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java @@ -155,4 +155,51 @@ class AnnotationControllerTest { mockMvc.perform(delete("/api/documents/" + UUID.randomUUID() + "/annotations/" + UUID.randomUUID())) .andExpect(status().isNoContent()); } + + // ─── resolveUserId — unauthenticated / null user / exception branches ───── + + @Test + void createAnnotation_returns401_whenUnauthenticated_resolveUserIdReturnsNull() throws Exception { + // authentication == null → resolveUserId returns null + mockMvc.perform(post("/api/documents/" + UUID.randomUUID() + "/annotations") + .contentType(MediaType.APPLICATION_JSON) + .content(ANNOTATION_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(authorities = "ANNOTATE_ALL") + void createAnnotation_resolvesNullUserId_whenUserServiceThrows() throws Exception { + // findByUsername throws → catch block → resolveUserId returns null + UUID docId = UUID.randomUUID(); + when(userService.findByUsername(any())).thenThrow(new RuntimeException("DB error")); + DocumentAnnotation saved = DocumentAnnotation.builder() + .id(UUID.randomUUID()).documentId(docId).pageNumber(1) + .x(0.1).y(0.1).width(0.2).height(0.2).color("#ff0000").build(); + when(documentService.getDocumentById(any())).thenReturn(Document.builder().build()); + when(annotationService.createAnnotation(any(), any(), any(), any())).thenReturn(saved); + + mockMvc.perform(post("/api/documents/" + docId + "/annotations") + .contentType(MediaType.APPLICATION_JSON) + .content(ANNOTATION_JSON)) + .andExpect(status().isCreated()); + } + + @Test + @WithMockUser(authorities = "ANNOTATE_ALL") + void createAnnotation_resolvesNullUserId_whenUserServiceReturnsNull() throws Exception { + // findByUsername returns null → user != null = false → resolveUserId returns null + UUID docId = UUID.randomUUID(); + when(userService.findByUsername(any())).thenReturn(null); + DocumentAnnotation saved = DocumentAnnotation.builder() + .id(UUID.randomUUID()).documentId(docId).pageNumber(1) + .x(0.1).y(0.1).width(0.2).height(0.2).color("#ff0000").build(); + when(documentService.getDocumentById(any())).thenReturn(Document.builder().build()); + when(annotationService.createAnnotation(any(), any(), any(), any())).thenReturn(saved); + + mockMvc.perform(post("/api/documents/" + docId + "/annotations") + .contentType(MediaType.APPLICATION_JSON) + .content(ANNOTATION_JSON)) + .andExpect(status().isCreated()); + } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java index 40f01a39..d9c2f31d 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java @@ -263,4 +263,20 @@ class CommentControllerTest { .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) .andExpect(status().isCreated()); } + + // ─── resolveUser — exception branch ────────────────────────────────────── + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void postDocumentComment_stillSucceeds_whenUserServiceThrows() throws Exception { + // findByUsername throws → catch block in resolveUser → author null, saves anyway + when(userService.findByUsername(any())).thenThrow(new RuntimeException("DB error")); + DocumentComment saved = DocumentComment.builder() + .id(UUID.randomUUID()).documentId(DOC_ID).content("Test comment").build(); + when(commentService.postComment(any(), any(), any(), any(), any())).thenReturn(saved); + + mockMvc.perform(post("/api/documents/" + DOC_ID + "/comments") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isCreated()); + } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java index 749abd88..d200c6f9 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java @@ -213,6 +213,80 @@ class DocumentControllerTest { .andExpect(jsonPath("$.errors[0].code").value("UNSUPPORTED_FILE_TYPE")); } + // ─── GET /api/documents/{id}/file ──────────────────────────────────────── + + @Test + @WithMockUser + void getDocumentFile_returns404_whenDocHasNoFilePath() throws Exception { + UUID id = UUID.randomUUID(); + Document doc = Document.builder().id(id).title("Brief").build(); // filePath == null + when(documentService.getDocumentById(id)).thenReturn(doc); + + mockMvc.perform(get("/api/documents/" + id + "/file")) + .andExpect(status().isNotFound()); + } + + @Test + @WithMockUser + void getDocumentFile_returns200_withContentTypeFromDoc() throws Exception { + UUID id = UUID.randomUUID(); + Document doc = Document.builder().id(id).title("Brief") + .filePath("docs/brief.pdf").contentType("application/pdf") + .originalFilename("brief.pdf").build(); + when(documentService.getDocumentById(id)).thenReturn(doc); + java.io.InputStream stream = new java.io.ByteArrayInputStream(new byte[]{1, 2, 3}); + when(fileService.downloadFile("docs/brief.pdf")) + .thenReturn(new FileService.S3FileDownload( + new org.springframework.core.io.InputStreamResource(stream), "application/octet-stream")); + + mockMvc.perform(get("/api/documents/" + id + "/file")) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser + void getDocumentFile_returns200_withContentTypeFromStorage_whenDocContentTypeIsBlank() throws Exception { + UUID id = UUID.randomUUID(); + Document doc = Document.builder().id(id).title("Brief") + .filePath("docs/brief.pdf").contentType(" ") // blank → falls back to storage type + .originalFilename("brief.pdf").build(); + when(documentService.getDocumentById(id)).thenReturn(doc); + java.io.InputStream stream = new java.io.ByteArrayInputStream(new byte[]{1, 2, 3}); + when(fileService.downloadFile("docs/brief.pdf")) + .thenReturn(new FileService.S3FileDownload( + new org.springframework.core.io.InputStreamResource(stream), "application/pdf")); + + mockMvc.perform(get("/api/documents/" + id + "/file")) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser + void getDocumentFile_returns404_whenStorageFileNotFound() throws Exception { + UUID id = UUID.randomUUID(); + Document doc = Document.builder().id(id).title("Brief") + .filePath("docs/missing.pdf").contentType("application/pdf") + .originalFilename("missing.pdf").build(); + when(documentService.getDocumentById(id)).thenReturn(doc); + when(fileService.downloadFile("docs/missing.pdf")) + .thenThrow(new FileService.StorageFileNotFoundException("not found")); + + mockMvc.perform(get("/api/documents/" + id + "/file")) + .andExpect(status().isNotFound()); + } + + // ─── POST /api/documents/quick-upload — null/empty files ───────────────── + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void quickUpload_returnsEmptyResult_whenNoFilesPartProvided() throws Exception { + mockMvc.perform(multipart("/api/documents/quick-upload")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.created").isEmpty()) + .andExpect(jsonPath("$.updated").isEmpty()) + .andExpect(jsonPath("$.errors").isEmpty()); + } + // ─── GET /api/documents/incomplete-count ───────────────────────────────── @Test diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java index e04ba664..a56df834 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java @@ -291,4 +291,17 @@ class PersonControllerTest { .content("{\"targetPersonId\":\"" + targetId + "\"}")) .andExpect(status().isNoContent()); } + + // ─── PUT /api/persons/{id} — lastName blank branch ──────────────────────── + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void updatePerson_returns400_whenLastNameIsBlank() throws Exception { + // firstName valid, lastName blank → second || operand = true → 400 + UUID id = UUID.randomUUID(); + mockMvc.perform(put("/api/persons/{id}", id) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Hans\",\"lastName\":\" \"}")) + .andExpect(status().isBadRequest()); + } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/UserControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/UserControllerTest.java new file mode 100644 index 00000000..eb4cc3e7 --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/UserControllerTest.java @@ -0,0 +1,53 @@ +package org.raddatz.familienarchiv.controller; + +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.config.SecurityConfig; +import org.raddatz.familienarchiv.model.AppUser; +import org.raddatz.familienarchiv.security.PermissionAspect; +import org.raddatz.familienarchiv.service.CustomUserDetailsService; +import org.raddatz.familienarchiv.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserController.class) +@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class}) +class UserControllerTest { + + @Autowired MockMvc mockMvc; + + @MockitoBean UserService userService; + @MockitoBean CustomUserDetailsService customUserDetailsService; + + // ─── GET /api/users/me ──────────────────────────────────────────────────── + + @Test + void getCurrentUser_returns401_whenUnauthenticated() throws Exception { + // authentication == null → returns 401 (covers null/!isAuthenticated branch) + mockMvc.perform(get("/api/users/me")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(username = "anna") + void getCurrentUser_returns200_whenAuthenticated() throws Exception { + AppUser user = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); + when(userService.findByUsername("anna")).thenReturn(user); + + mockMvc.perform(get("/api/users/me")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.username").value("anna")); + } +}