feat(annotations): add PATCH endpoint for annotation resize/move

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-14 10:42:08 +02:00
parent 1558881c01
commit ff231db671
2 changed files with 97 additions and 0 deletions

View File

@@ -27,6 +27,7 @@ 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;
@@ -160,6 +161,91 @@ class AnnotationControllerTest {
.andExpect(status().isNoContent());
}
// ─── PATCH /api/documents/{documentId}/annotations/{annotationId} ─────────
private static final String PATCH_JSON = "{\"x\":0.2,\"y\":0.3}";
@Test
void patchAnnotation_returns401_whenUnauthenticated() throws Exception {
mockMvc.perform(patch("/api/documents/" + UUID.randomUUID() + "/annotations/" + UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON)
.content(PATCH_JSON))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockUser
void patchAnnotation_returns403_withoutPermission() throws Exception {
mockMvc.perform(patch("/api/documents/" + UUID.randomUUID() + "/annotations/" + UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON)
.content(PATCH_JSON))
.andExpect(status().isForbidden());
}
@Test
@WithMockUser(authorities = "WRITE_ALL")
void patchAnnotation_returns200_withWriteAllPermission() throws Exception {
UUID docId = UUID.randomUUID();
UUID annotId = UUID.randomUUID();
DocumentAnnotation updated = DocumentAnnotation.builder()
.id(annotId).documentId(docId).pageNumber(1)
.x(0.2).y(0.3).width(0.2).height(0.2).color("#ff0000").build();
when(annotationService.updateAnnotation(any(), any(), any())).thenReturn(updated);
mockMvc.perform(patch("/api/documents/" + docId + "/annotations/" + annotId)
.contentType(MediaType.APPLICATION_JSON)
.content(PATCH_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.x").value(0.2))
.andExpect(jsonPath("$.y").value(0.3));
}
@Test
@WithMockUser(authorities = "ANNOTATE_ALL")
void patchAnnotation_returns200_withAnnotateAllPermission() throws Exception {
UUID docId = UUID.randomUUID();
UUID annotId = UUID.randomUUID();
DocumentAnnotation updated = DocumentAnnotation.builder()
.id(annotId).documentId(docId).pageNumber(1)
.x(0.2).y(0.3).width(0.2).height(0.2).color("#ff0000").build();
when(annotationService.updateAnnotation(any(), any(), any())).thenReturn(updated);
mockMvc.perform(patch("/api/documents/" + docId + "/annotations/" + annotId)
.contentType(MediaType.APPLICATION_JSON)
.content(PATCH_JSON))
.andExpect(status().isOk());
}
@Test
@WithMockUser(authorities = "WRITE_ALL")
void patchAnnotation_returns404_whenAnnotationBelongsToDifferentDocument() throws Exception {
when(annotationService.updateAnnotation(any(), any(), any()))
.thenThrow(DomainException.notFound(ErrorCode.ANNOTATION_NOT_FOUND, "not found"));
mockMvc.perform(patch("/api/documents/" + UUID.randomUUID() + "/annotations/" + UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON)
.content(PATCH_JSON))
.andExpect(status().isNotFound());
}
@Test
@WithMockUser(authorities = "WRITE_ALL")
void patchAnnotation_returns400_withOutOfBoundsCoordinates() throws Exception {
mockMvc.perform(patch("/api/documents/" + UUID.randomUUID() + "/annotations/" + UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"x\":-0.1,\"y\":0.3}"))
.andExpect(status().isBadRequest());
}
@Test
@WithMockUser(authorities = "WRITE_ALL")
void patchAnnotation_returns400_withWidthBelowMinimum() throws Exception {
mockMvc.perform(patch("/api/documents/" + UUID.randomUUID() + "/annotations/" + UUID.randomUUID())
.contentType(MediaType.APPLICATION_JSON)
.content("{\"width\":0.005}"))
.andExpect(status().isBadRequest());
}
// ─── resolveUserId — unauthenticated / null user / exception branches ─────
@Test