|
|
|
|
@@ -20,7 +20,6 @@ import static org.mockito.ArgumentMatchers.any;
|
|
|
|
|
import static org.mockito.Mockito.never;
|
|
|
|
|
import static org.mockito.Mockito.verify;
|
|
|
|
|
import static org.mockito.Mockito.when;
|
|
|
|
|
import static org.springframework.http.HttpStatus.CONFLICT;
|
|
|
|
|
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
|
|
|
|
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
|
|
|
|
|
|
|
|
|
@@ -33,34 +32,13 @@ class AnnotationServiceTest {
|
|
|
|
|
// ─── createAnnotation ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_throwsConflict_whenAnnotationOverlapsExisting() {
|
|
|
|
|
void createAnnotation_savesAnnotation() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.3, 0.3, "#ff0000");
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation existing = DocumentAnnotation.builder()
|
|
|
|
|
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
|
|
|
|
.x(0.2).y(0.2).width(0.3).height(0.3).color("#00ff00").build();
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1))
|
|
|
|
|
.thenReturn(List.of(existing));
|
|
|
|
|
|
|
|
|
|
assertThatThrownBy(() -> annotationService.createAnnotation(docId, dto, userId, null))
|
|
|
|
|
.isInstanceOf(DomainException.class)
|
|
|
|
|
.satisfies(e -> assertThat(((DomainException) e).getStatus()).isEqualTo(CONFLICT));
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository, never()).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_savesAndReturns_whenNoOverlap() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.0, 0.0, 0.05, 0.05, "#ff0000");
|
|
|
|
|
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1)).thenReturn(List.of());
|
|
|
|
|
DocumentAnnotation saved = DocumentAnnotation.builder()
|
|
|
|
|
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
|
|
|
|
.x(0.0).y(0.0).width(0.05).height(0.05).color("#ff0000").createdBy(userId).build();
|
|
|
|
|
.x(0.1).y(0.1).width(0.3).height(0.3).color("#ff0000").createdBy(userId).build();
|
|
|
|
|
when(annotationRepository.save(any())).thenReturn(saved);
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createAnnotation(docId, dto, userId, null);
|
|
|
|
|
@@ -69,6 +47,77 @@ class AnnotationServiceTest {
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_allowsOverlappingAnnotations() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.3, 0.3, "#ff0000");
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
// Should not throw even when overlapping annotations exist on the same page
|
|
|
|
|
annotationService.createAnnotation(docId, dto, userId, null);
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_setsFileHash_whenProvided() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.0, 0.0, 0.05, 0.05, "#ff0000");
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createAnnotation(docId, dto, userId, "abc123");
|
|
|
|
|
|
|
|
|
|
assertThat(result.getFileHash()).isEqualTo("abc123");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_setsNullFileHash_whenNoneProvided() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.0, 0.0, 0.05, 0.05, "#ff0000");
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createAnnotation(docId, dto, userId, null);
|
|
|
|
|
|
|
|
|
|
assertThat(result.getFileHash()).isNull();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── createOcrAnnotation ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createOcrAnnotation_savesWithPolygon() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.8, 0.04, "#00C7B1");
|
|
|
|
|
List<List<Double>> polygon = List.of(
|
|
|
|
|
List.of(0.1, 0.1), List.of(0.9, 0.11),
|
|
|
|
|
List.of(0.89, 0.14), List.of(0.11, 0.13));
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createOcrAnnotation(
|
|
|
|
|
docId, dto, userId, "filehash", polygon);
|
|
|
|
|
|
|
|
|
|
assertThat(result.getPolygon()).isEqualTo(polygon);
|
|
|
|
|
assertThat(result.getDocumentId()).isEqualTo(docId);
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createOcrAnnotation_savesWithNullPolygon_whenPolygonNotProvided() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.8, 0.04, "#00C7B1");
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createOcrAnnotation(
|
|
|
|
|
docId, dto, userId, "filehash", null);
|
|
|
|
|
|
|
|
|
|
assertThat(result.getPolygon()).isNull();
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── deleteAnnotation ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@@ -118,32 +167,19 @@ class AnnotationServiceTest {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_setsFileHash_whenProvided() {
|
|
|
|
|
void deleteAnnotation_throwsForbidden_whenUserIdIsNull() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.0, 0.0, 0.05, 0.05, "#ff0000");
|
|
|
|
|
String fileHash = "abc123";
|
|
|
|
|
UUID annotId = UUID.randomUUID();
|
|
|
|
|
UUID ownerId = UUID.randomUUID();
|
|
|
|
|
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1)).thenReturn(List.of());
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
DocumentAnnotation annotation = DocumentAnnotation.builder()
|
|
|
|
|
.id(annotId).documentId(docId).createdBy(ownerId).build();
|
|
|
|
|
when(annotationRepository.findByIdAndDocumentId(annotId, docId))
|
|
|
|
|
.thenReturn(Optional.of(annotation));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createAnnotation(docId, dto, userId, fileHash);
|
|
|
|
|
|
|
|
|
|
assertThat(result.getFileHash()).isEqualTo(fileHash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_setsNullFileHash_whenNoneProvided() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.0, 0.0, 0.05, 0.05, "#ff0000");
|
|
|
|
|
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1)).thenReturn(List.of());
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createAnnotation(docId, dto, userId, null);
|
|
|
|
|
|
|
|
|
|
assertThat(result.getFileHash()).isNull();
|
|
|
|
|
assertThatThrownBy(() -> annotationService.deleteAnnotation(docId, annotId, null))
|
|
|
|
|
.isInstanceOf(DomainException.class)
|
|
|
|
|
.satisfies(e -> assertThat(((DomainException) e).getStatus()).isEqualTo(FORBIDDEN));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── listAnnotations ──────────────────────────────────────────────────────
|
|
|
|
|
@@ -183,149 +219,4 @@ class AnnotationServiceTest {
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository, never()).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── deleteAnnotation — null userId ───────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void deleteAnnotation_throwsForbidden_whenUserIdIsNull() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID annotId = UUID.randomUUID();
|
|
|
|
|
UUID ownerId = UUID.randomUUID();
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation annotation = DocumentAnnotation.builder()
|
|
|
|
|
.id(annotId).documentId(docId).createdBy(ownerId).build();
|
|
|
|
|
when(annotationRepository.findByIdAndDocumentId(annotId, docId))
|
|
|
|
|
.thenReturn(Optional.of(annotation));
|
|
|
|
|
|
|
|
|
|
assertThatThrownBy(() -> annotationService.deleteAnnotation(docId, annotId, null))
|
|
|
|
|
.isInstanceOf(DomainException.class)
|
|
|
|
|
.satisfies(e -> assertThat(((DomainException) e).getStatus()).isEqualTo(FORBIDDEN));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── overlaps — partial overlap cases ────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_noConflict_whenAnnotationIsToTheLeft() {
|
|
|
|
|
// existing: x=0.5, w=0.3 (x2=0.8); dto: x=0.0, w=0.4 (dx2=0.4)
|
|
|
|
|
// existing.getX() < dx2 → 0.5 < 0.4 → FALSE → no overlap (first && fails)
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
DocumentAnnotation existing = DocumentAnnotation.builder()
|
|
|
|
|
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
|
|
|
|
.x(0.5).y(0.0).width(0.3).height(0.5).color("#ff0000").build();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.0, 0.0, 0.4, 0.5, "#0000ff");
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1)).thenReturn(List.of(existing));
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
annotationService.createAnnotation(docId, dto, UUID.randomUUID(), null);
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_noConflict_whenAnnotationIsToTheRight() {
|
|
|
|
|
// existing: x=0.0, w=0.1 (ex2=0.1); dto: x=0.2, w=0.3 (dx2=0.5)
|
|
|
|
|
// existing.getX() < dx2 → 0.0 < 0.5 → TRUE
|
|
|
|
|
// ex2 > dto.getX() → 0.1 > 0.2 → FALSE → no overlap (second && fails)
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
DocumentAnnotation existing = DocumentAnnotation.builder()
|
|
|
|
|
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
|
|
|
|
.x(0.0).y(0.0).width(0.1).height(0.5).color("#ff0000").build();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.2, 0.0, 0.3, 0.5, "#0000ff");
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1)).thenReturn(List.of(existing));
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
annotationService.createAnnotation(docId, dto, UUID.randomUUID(), null);
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_noConflict_whenAnnotationIsBelow() {
|
|
|
|
|
// x ranges overlap, but y ranges don't
|
|
|
|
|
// existing: x=0.0, w=0.5, y=0.5, h=0.2 (ey2=0.7)
|
|
|
|
|
// dto: x=0.1, w=0.3 (dx2=0.4), y=0.0, h=0.4 (dy2=0.4)
|
|
|
|
|
// existing.getX() < dx2 → 0.0 < 0.4 → TRUE
|
|
|
|
|
// ex2 > dto.getX() → 0.5 > 0.1 → TRUE
|
|
|
|
|
// existing.getY() < dy2 → 0.5 < 0.4 → FALSE → no overlap (third && fails)
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
DocumentAnnotation existing = DocumentAnnotation.builder()
|
|
|
|
|
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
|
|
|
|
.x(0.0).y(0.5).width(0.5).height(0.2).color("#ff0000").build();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.0, 0.3, 0.4, "#0000ff");
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1)).thenReturn(List.of(existing));
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
annotationService.createAnnotation(docId, dto, UUID.randomUUID(), null);
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── createOcrAnnotation ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createOcrAnnotation_skipsOverlapCheck_andSavesWithPolygon() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.8, 0.04, "#00C7B1");
|
|
|
|
|
List<List<Double>> polygon = List.of(
|
|
|
|
|
List.of(0.1, 0.1), List.of(0.9, 0.11),
|
|
|
|
|
List.of(0.89, 0.14), List.of(0.11, 0.13));
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createOcrAnnotation(
|
|
|
|
|
docId, dto, userId, "filehash", polygon);
|
|
|
|
|
|
|
|
|
|
assertThat(result.getPolygon()).isEqualTo(polygon);
|
|
|
|
|
assertThat(result.getDocumentId()).isEqualTo(docId);
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
verify(annotationRepository, never()).findByDocumentIdAndPageNumber(any(), any(int.class));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createOcrAnnotation_savesWithNullPolygon_whenPolygonNotProvided() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.8, 0.04, "#00C7B1");
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
DocumentAnnotation result = annotationService.createOcrAnnotation(
|
|
|
|
|
docId, dto, userId, "filehash", null);
|
|
|
|
|
|
|
|
|
|
assertThat(result.getPolygon()).isNull();
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createOcrAnnotation_doesNotCheckOverlap_evenWhenOverlappingAnnotationExists() {
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
UUID userId = UUID.randomUUID();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.3, 0.3, "#00C7B1");
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
annotationService.createOcrAnnotation(docId, dto, userId, "hash", null);
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository, never()).findByDocumentIdAndPageNumber(any(), any(int.class));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ─── overlaps — partial overlap cases ────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void createAnnotation_noConflict_whenAnnotationIsAbove() {
|
|
|
|
|
// x ranges overlap, y ranges don't — existing is ABOVE the new annotation
|
|
|
|
|
// existing: x=0.0, w=0.5, y=0.0, h=0.1 (ey2=0.1)
|
|
|
|
|
// dto: x=0.1, w=0.3 (dx2=0.4), y=0.2, h=0.3 (dy2=0.5)
|
|
|
|
|
// A: 0.0 < 0.4 → TRUE, B: 0.5 > 0.1 → TRUE, C: 0.0 < 0.5 → TRUE
|
|
|
|
|
// D: ey2 > dto.getY() → 0.1 > 0.2 → FALSE → no overlap (fourth && fails)
|
|
|
|
|
UUID docId = UUID.randomUUID();
|
|
|
|
|
DocumentAnnotation existing = DocumentAnnotation.builder()
|
|
|
|
|
.id(UUID.randomUUID()).documentId(docId).pageNumber(1)
|
|
|
|
|
.x(0.0).y(0.0).width(0.5).height(0.1).color("#ff0000").build();
|
|
|
|
|
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.2, 0.3, 0.3, "#0000ff");
|
|
|
|
|
when(annotationRepository.findByDocumentIdAndPageNumber(docId, 1)).thenReturn(List.of(existing));
|
|
|
|
|
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
|
|
|
|
|
annotationService.createAnnotation(docId, dto, UUID.randomUUID(), null);
|
|
|
|
|
|
|
|
|
|
verify(annotationRepository).save(any());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|