feat(comments): add block-level comment endpoints with TDD

RED/GREEN for CommentService:
- getCommentsForBlock(blockId): returns root comments filtered by blockId
- postBlockComment(documentId, blockId, content, mentions, author): creates
  comment with block_id set

RED/GREEN for CommentController:
- GET /api/documents/{docId}/transcription-blocks/{blockId}/comments
- POST /api/documents/{docId}/transcription-blocks/{blockId}/comments
- POST .../comments/{commentId}/replies (reuses existing replyToComment)

4 new tests: 2 service unit tests + 2 controller integration tests
All 25 CommentServiceTest + 24 CommentControllerTest green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-05 21:01:02 +02:00
parent 3b2d905041
commit da43cadb0a
5 changed files with 117 additions and 0 deletions

View File

@@ -279,4 +279,30 @@ class CommentControllerTest {
.contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
.andExpect(status().isCreated());
}
// ─── Block comment endpoints ─────────────────────────────────────────────
@Test
@WithMockUser
void getBlockComments_returns200() throws Exception {
UUID blockId = UUID.randomUUID();
when(commentService.getCommentsForBlock(blockId)).thenReturn(List.of());
mockMvc.perform(get("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(authorities = "WRITE_ALL")
void postBlockComment_returns201() throws Exception {
UUID blockId = UUID.randomUUID();
DocumentComment saved = DocumentComment.builder()
.id(UUID.randomUUID()).documentId(DOC_ID).blockId(blockId).content("Nice").build();
when(commentService.postBlockComment(any(), any(), any(), any(), any())).thenReturn(saved);
mockMvc.perform(post("/api/documents/" + DOC_ID + "/transcription-blocks/" + blockId + "/comments")
.contentType(MediaType.APPLICATION_JSON).content(COMMENT_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.blockId").value(blockId.toString()));
}
}

View File

@@ -488,4 +488,40 @@ class CommentServiceTest {
.build()))
.build();
}
// ─── Block-level comments ────────────────────────────────────────────────
@Test
void getCommentsForBlock_returnsRootCommentsFilteredByBlockId() {
UUID blockId = UUID.randomUUID();
DocumentComment root = DocumentComment.builder()
.id(UUID.randomUUID()).blockId(blockId).content("Nice work").authorName("Felix")
.createdAt(LocalDateTime.now()).updatedAt(LocalDateTime.now()).build();
when(commentRepository.findByBlockIdAndParentIdIsNull(blockId)).thenReturn(List.of(root));
when(commentRepository.findByParentId(root.getId())).thenReturn(List.of());
List<DocumentComment> result = commentService.getCommentsForBlock(blockId);
assertThat(result).hasSize(1);
assertThat(result.getFirst().getContent()).isEqualTo("Nice work");
}
@Test
void postBlockComment_setsBlockIdOnComment() {
UUID documentId = UUID.randomUUID();
UUID blockId = UUID.randomUUID();
AppUser author = AppUser.builder().id(UUID.randomUUID()).username("felix").firstName("Felix").lastName("Brandt").build();
when(commentRepository.save(any())).thenAnswer(inv -> {
DocumentComment c = inv.getArgument(0);
c.setId(UUID.randomUUID());
return c;
});
DocumentComment result = commentService.postBlockComment(
documentId, blockId, "Looks like Breslau", List.of(), author);
assertThat(result.getBlockId()).isEqualTo(blockId);
assertThat(result.getDocumentId()).isEqualTo(documentId);
assertThat(result.getContent()).isEqualTo("Looks like Breslau");
}
}