diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java new file mode 100644 index 00000000..9b3bfbe8 --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java @@ -0,0 +1,203 @@ +package org.raddatz.familienarchiv.controller; + +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.config.SecurityConfig; +import org.raddatz.familienarchiv.model.DocumentComment; +import org.raddatz.familienarchiv.security.PermissionAspect; +import org.raddatz.familienarchiv.service.CommentService; +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.http.MediaType; +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.List; +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.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(CommentController.class) +@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class}) +class CommentControllerTest { + + @Autowired MockMvc mockMvc; + + @MockitoBean CommentService commentService; + @MockitoBean UserService userService; + @MockitoBean CustomUserDetailsService customUserDetailsService; + + private static final String COMMENT_JSON = "{\"content\":\"Test comment\"}"; + private static final UUID DOC_ID = UUID.randomUUID(); + private static final UUID ANN_ID = UUID.randomUUID(); + private static final UUID COMMENT_ID = UUID.randomUUID(); + + // ─── GET /api/documents/{documentId}/comments ───────────────────────────── + + @Test + void getDocumentComments_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(get("/api/documents/" + DOC_ID + "/comments")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void getDocumentComments_returns200_whenAuthenticated() throws Exception { + when(commentService.getCommentsForDocument(any())).thenReturn(List.of()); + mockMvc.perform(get("/api/documents/" + DOC_ID + "/comments")) + .andExpect(status().isOk()); + } + + // ─── POST /api/documents/{documentId}/comments ──────────────────────────── + + @Test + void postDocumentComment_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(post("/api/documents/" + DOC_ID + "/comments") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void postDocumentComment_returns403_whenMissingPermission() throws Exception { + mockMvc.perform(post("/api/documents/" + DOC_ID + "/comments") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "ANNOTATE_ALL") + void postDocumentComment_returns201_whenHasPermission() throws Exception { + DocumentComment saved = DocumentComment.builder() + .id(COMMENT_ID).documentId(DOC_ID).authorName("Hans").content("Test comment").build(); + when(commentService.postComment(any(), any(), any(), any())).thenReturn(saved); + + mockMvc.perform(post("/api/documents/" + DOC_ID + "/comments") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.content").value("Test comment")); + } + + // ─── POST /api/documents/{documentId}/comments/{commentId}/replies ──────── + + @Test + void replyToComment_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(post("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID + "/replies") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(authorities = "ANNOTATE_ALL") + void replyToComment_returns201_whenHasPermission() throws Exception { + DocumentComment saved = DocumentComment.builder() + .id(UUID.randomUUID()).documentId(DOC_ID).parentId(COMMENT_ID) + .authorName("Anna").content("Test comment").build(); + when(commentService.replyToComment(any(), any(), any(), any())).thenReturn(saved); + + mockMvc.perform(post("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID + "/replies") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isCreated()); + } + + // ─── PATCH /api/documents/{documentId}/comments/{commentId} ────────────── + + @Test + void editComment_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(patch("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID) + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(authorities = "ANNOTATE_ALL") + void editComment_returns200_whenHasPermission() throws Exception { + DocumentComment updated = DocumentComment.builder() + .id(COMMENT_ID).documentId(DOC_ID).authorName("Hans").content("Test comment").build(); + when(commentService.editComment(any(), any(), any(), any())).thenReturn(updated); + + mockMvc.perform(patch("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID) + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isOk()); + } + + // ─── DELETE /api/documents/{documentId}/comments/{commentId} ───────────── + + @Test + void deleteComment_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(delete("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID)) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void deleteComment_returns204_whenAuthenticated() throws Exception { + mockMvc.perform(delete("/api/documents/" + DOC_ID + "/comments/" + COMMENT_ID)) + .andExpect(status().isNoContent()); + } + + // ─── GET /api/documents/{documentId}/annotations/{annId}/comments ───────── + + @Test + void getAnnotationComments_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(get("/api/documents/" + DOC_ID + "/annotations/" + ANN_ID + "/comments")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void getAnnotationComments_returns200_whenAuthenticated() throws Exception { + when(commentService.getCommentsForAnnotation(any())).thenReturn(List.of()); + mockMvc.perform(get("/api/documents/" + DOC_ID + "/annotations/" + ANN_ID + "/comments")) + .andExpect(status().isOk()); + } + + // ─── POST /api/documents/{documentId}/annotations/{annId}/comments ──────── + + @Test + @WithMockUser + void postAnnotationComment_returns403_whenMissingPermission() throws Exception { + mockMvc.perform(post("/api/documents/" + DOC_ID + "/annotations/" + ANN_ID + "/comments") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(authorities = "ANNOTATE_ALL") + void postAnnotationComment_returns201_whenHasPermission() throws Exception { + DocumentComment saved = DocumentComment.builder() + .id(UUID.randomUUID()).documentId(DOC_ID).annotationId(ANN_ID) + .authorName("Hans").content("Test comment").build(); + when(commentService.postComment(any(), any(), any(), any())).thenReturn(saved); + + mockMvc.perform(post("/api/documents/" + DOC_ID + "/annotations/" + ANN_ID + "/comments") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isCreated()); + } + + // ─── POST /api/documents/{documentId}/annotations/{annId}/comments/{commentId}/replies ─ + + @Test + @WithMockUser(authorities = "ANNOTATE_ALL") + void replyToAnnotationComment_returns201_whenHasPermission() throws Exception { + DocumentComment saved = DocumentComment.builder() + .id(UUID.randomUUID()).documentId(DOC_ID).annotationId(ANN_ID) + .parentId(COMMENT_ID).authorName("Anna").content("Test comment").build(); + when(commentService.replyToComment(any(), any(), any(), any())).thenReturn(saved); + + mockMvc.perform(post("/api/documents/" + DOC_ID + "/annotations/" + ANN_ID + "/comments/" + COMMENT_ID + "/replies") + .contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON)) + .andExpect(status().isCreated()); + } +}