diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java index d02c36aa..0c691c03 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java @@ -94,6 +94,14 @@ public class DocumentService { return documentRepository.save(doc); } + public Optional findByOriginalFilename(String originalFilename) { + return documentRepository.findByOriginalFilename(originalFilename); + } + + public Document save(Document doc) { + return documentRepository.save(doc); + } + public List findSegmentationQueue(int limit) { return documentRepository.findSegmentationQueue(limit); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java index 7e847ff8..ebfca16e 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/MassImportService.java @@ -10,7 +10,6 @@ import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.model.Tag; -import org.raddatz.familienarchiv.repository.DocumentRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -55,7 +54,7 @@ public class MassImportService { return currentStatus; } - private final DocumentRepository documentRepository; + private final DocumentService documentService; private final PersonService personService; private final TagService tagService; private final S3Client s3Client; @@ -257,7 +256,7 @@ public class MassImportService { @Transactional protected void importSingleDocument(List cells, Optional file, String originalFilename, String index) { - Optional existing = documentRepository.findByOriginalFilename(originalFilename); + Optional existing = documentService.findByOriginalFilename(originalFilename); if (existing.isPresent() && existing.get().getStatus() != DocumentStatus.PLACEHOLDER) { log.info("Dokument {} existiert bereits, überspringe.", originalFilename); return; @@ -333,7 +332,7 @@ public class MassImportService { if (tag != null) doc.getTags().add(tag); doc.setMetadataComplete(metadataComplete); - Document saved = documentRepository.save(doc); + Document saved = documentService.save(doc); if (file.isPresent()) { thumbnailAsyncRunner.dispatchAfterCommit(saved.getId()); } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java index 7136d245..015d6302 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java @@ -2306,4 +2306,25 @@ class DocumentServiceTest { assertThat(documentService.updateThumbnailMetadata(doc)).isEqualTo(doc); verify(documentRepository).save(doc); } + + // ─── findByOriginalFilename ──────────────────────────────────────────────── + + @Test + void findByOriginalFilename_returnsRepositoryResult() { + Document doc = Document.builder().id(UUID.randomUUID()).title("T").build(); + when(documentRepository.findByOriginalFilename("scan.pdf")).thenReturn(Optional.of(doc)); + + assertThat(documentService.findByOriginalFilename("scan.pdf")).contains(doc); + } + + // ─── save ────────────────────────────────────────────────────────────────── + + @Test + void save_delegatesToRepository() { + Document doc = Document.builder().id(UUID.randomUUID()).title("T").build(); + when(documentRepository.save(doc)).thenReturn(doc); + + assertThat(documentService.save(doc)).isEqualTo(doc); + verify(documentRepository).save(doc); + } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/MassImportServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/MassImportServiceTest.java index 62b138b7..00416943 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/MassImportServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/MassImportServiceTest.java @@ -11,7 +11,6 @@ import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.Person; import org.raddatz.familienarchiv.model.Tag; -import org.raddatz.familienarchiv.repository.DocumentRepository; import org.springframework.test.util.ReflectionTestUtils; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; @@ -35,7 +34,7 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class MassImportServiceTest { - @Mock DocumentRepository documentRepository; + @Mock DocumentService documentService; @Mock PersonService personService; @Mock TagService tagService; @Mock S3Client s3Client; @@ -45,7 +44,7 @@ class MassImportServiceTest { @BeforeEach void setUp() { - service = new MassImportService(documentRepository, personService, tagService, s3Client, thumbnailAsyncRunner); + service = new MassImportService(documentService, personService, tagService, s3Client, thumbnailAsyncRunner); ReflectionTestUtils.setField(service, "bucketName", "test-bucket"); ReflectionTestUtils.setField(service, "colIndex", 0); ReflectionTestUtils.setField(service, "colBox", 1); @@ -96,23 +95,23 @@ class MassImportServiceTest { .originalFilename("doc001.pdf") .status(DocumentStatus.UPLOADED) .build(); - when(documentRepository.findByOriginalFilename("doc001.pdf")).thenReturn(Optional.of(existing)); + when(documentService.findByOriginalFilename("doc001.pdf")).thenReturn(Optional.of(existing)); service.importSingleDocument(minimalCells("doc001.pdf"), Optional.empty(), "doc001.pdf", "doc001"); - verify(documentRepository, never()).save(any()); + verify(documentService, never()).save(any()); } // ─── importSingleDocument — create new document (metadata only) ─────────── @Test void importSingleDocument_createsNewDocument_whenNotExists() { - when(documentRepository.findByOriginalFilename("doc002.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("doc002.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); service.importSingleDocument(minimalCells("doc002.pdf"), Optional.empty(), "doc002.pdf", "doc002"); - verify(documentRepository).save(argThat(d -> + verify(documentService).save(argThat(d -> d.getOriginalFilename().equals("doc002.pdf") && d.getStatus() == DocumentStatus.PLACEHOLDER)); } @@ -126,12 +125,12 @@ class MassImportServiceTest { .originalFilename("existing.pdf") .status(DocumentStatus.PLACEHOLDER) .build(); - when(documentRepository.findByOriginalFilename("existing.pdf")).thenReturn(Optional.of(placeholder)); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("existing.pdf")).thenReturn(Optional.of(placeholder)); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); service.importSingleDocument(minimalCells("existing.pdf"), Optional.empty(), "existing.pdf", "existing"); - verify(documentRepository).save(same(placeholder)); + verify(documentService).save(same(placeholder)); } // ─── importSingleDocument — with file (S3 upload) ───────────────────────── @@ -141,14 +140,14 @@ class MassImportServiceTest { Path tempFile = tempDir.resolve("doc003.pdf"); Files.write(tempFile, "PDF content".getBytes()); - when(documentRepository.findByOriginalFilename("doc003.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("doc003.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); service.importSingleDocument( minimalCells("doc003.pdf"), Optional.of(tempFile.toFile()), "doc003.pdf", "doc003"); verify(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class)); - verify(documentRepository).save(argThat(d -> d.getStatus() == DocumentStatus.UPLOADED)); + verify(documentService).save(argThat(d -> d.getStatus() == DocumentStatus.UPLOADED)); } @Test @@ -156,42 +155,42 @@ class MassImportServiceTest { Path tempFile = tempDir.resolve("fail.pdf"); Files.write(tempFile, "data".getBytes()); - when(documentRepository.findByOriginalFilename("fail.pdf")).thenReturn(Optional.empty()); + when(documentService.findByOriginalFilename("fail.pdf")).thenReturn(Optional.empty()); doThrow(new RuntimeException("S3 error")) .when(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class)); service.importSingleDocument( minimalCells("fail.pdf"), Optional.of(tempFile.toFile()), "fail.pdf", "fail"); - verify(documentRepository, never()).save(any()); + verify(documentService, never()).save(any()); } // ─── importSingleDocument — sender handling ─────────────────────────────── @Test void importSingleDocument_setsNullSender_whenSenderCellIsBlank() { - when(documentRepository.findByOriginalFilename("nosender.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("nosender.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); List cells = buildCells("nosender.pdf", "", "", ""); service.importSingleDocument(cells, Optional.empty(), "nosender.pdf", "nosender"); - verify(documentRepository).save(argThat(d -> d.getSender() == null)); + verify(documentService).save(argThat(d -> d.getSender() == null)); verify(personService, never()).findOrCreateByAlias(any()); } @Test void importSingleDocument_createsSender_whenSenderCellIsNonBlank() { Person sender = Person.builder().id(UUID.randomUUID()).firstName("Walter").lastName("Müller").build(); - when(documentRepository.findByOriginalFilename("withsender.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("withsender.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); when(personService.findOrCreateByAlias("Walter Müller")).thenReturn(sender); List cells = buildCells("withsender.pdf", "Walter Müller", "", ""); service.importSingleDocument(cells, Optional.empty(), "withsender.pdf", "withsender"); verify(personService).findOrCreateByAlias("Walter Müller"); - verify(documentRepository).save(argThat(d -> d.getSender() == sender)); + verify(documentService).save(argThat(d -> d.getSender() == sender)); } // ─── importSingleDocument — tag handling ───────────────────────────────── @@ -199,8 +198,8 @@ class MassImportServiceTest { @Test void importSingleDocument_createsTag_whenTagCellIsNonBlank() { Tag tag = Tag.builder().id(UUID.randomUUID()).name("Familie").build(); - when(documentRepository.findByOriginalFilename("tagged.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("tagged.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); when(tagService.findOrCreate("Familie")).thenReturn(tag); List cells = buildCells("tagged.pdf", "", "", "Familie"); @@ -211,8 +210,8 @@ class MassImportServiceTest { @Test void importSingleDocument_doesNotCreateTag_whenTagCellIsBlank() { - when(documentRepository.findByOriginalFilename("notag.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("notag.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); List cells = buildCells("notag.pdf", "", "", ""); service.importSingleDocument(cells, Optional.empty(), "notag.pdf", "notag"); @@ -225,38 +224,38 @@ class MassImportServiceTest { @Test void importSingleDocument_metadataComplete_whenSenderPresent() { Person sender = Person.builder().id(UUID.randomUUID()).firstName("A").lastName("B").build(); - when(documentRepository.findByOriginalFilename("meta.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("meta.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); when(personService.findOrCreateByAlias("A B")).thenReturn(sender); List cells = buildCells("meta.pdf", "A B", "", ""); service.importSingleDocument(cells, Optional.empty(), "meta.pdf", "meta"); - verify(documentRepository).save(argThat(Document::isMetadataComplete)); + verify(documentService).save(argThat(Document::isMetadataComplete)); } @Test void importSingleDocument_metadataIncomplete_whenNoKeyFieldsPresent() { - when(documentRepository.findByOriginalFilename("nometa.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("nometa.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); List cells = buildCells("nometa.pdf", "", "", ""); service.importSingleDocument(cells, Optional.empty(), "nometa.pdf", "nometa"); - verify(documentRepository).save(argThat(d -> !d.isMetadataComplete())); + verify(documentService).save(argThat(d -> !d.isMetadataComplete())); } // ─── importSingleDocument — blank fields set to null ───────────────────── @Test void importSingleDocument_setsBlankFieldsToNull() { - when(documentRepository.findByOriginalFilename("blank.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("blank.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); List cells = buildCells("blank.pdf", "", "", ""); service.importSingleDocument(cells, Optional.empty(), "blank.pdf", "blank"); - verify(documentRepository).save(argThat(d -> + verify(documentService).save(argThat(d -> d.getLocation() == null && d.getSummary() == null && d.getTranscription() == null && @@ -281,13 +280,13 @@ class MassImportServiceTest { ); Integer result = ReflectionTestUtils.invokeMethod(service, "processRows", rows); assertThat(result).isEqualTo(0); - verify(documentRepository, never()).findByOriginalFilename(any()); + verify(documentService, never()).findByOriginalFilename(any()); } @Test void processRows_addsExtension_whenIndexHasNoDot() { - when(documentRepository.findByOriginalFilename("doc001.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("doc001.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); List> rows = List.of( List.of("header"), @@ -296,13 +295,13 @@ class MassImportServiceTest { Integer result = ReflectionTestUtils.invokeMethod(service, "processRows", rows); assertThat(result).isEqualTo(1); - verify(documentRepository).findByOriginalFilename("doc001.pdf"); + verify(documentService).findByOriginalFilename("doc001.pdf"); } @Test void processRows_usesFilenameAsIs_whenIndexHasDot() { - when(documentRepository.findByOriginalFilename("doc002.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("doc002.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); List> rows = List.of( List.of("header"), @@ -311,15 +310,15 @@ class MassImportServiceTest { Integer result = ReflectionTestUtils.invokeMethod(service, "processRows", rows); assertThat(result).isEqualTo(1); - verify(documentRepository).findByOriginalFilename("doc002.pdf"); + verify(documentService).findByOriginalFilename("doc002.pdf"); } // ─── importSingleDocument — non-blank optional fields ──────────────────── @Test void importSingleDocument_setsNonNullOptionalFields_whenPresent() { - when(documentRepository.findByOriginalFilename("rich.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("rich.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); // box=1, folder=2, location=9, summary=11, transcription=13 List cells = List.of( @@ -341,7 +340,7 @@ class MassImportServiceTest { service.importSingleDocument(cells, Optional.empty(), "rich.pdf", "rich"); - verify(documentRepository).save(argThat(d -> + verify(documentService).save(argThat(d -> "Box A".equals(d.getArchiveBox()) && "Folder B".equals(d.getArchiveFolder()) && "Hamburg".equals(d.getLocation()) && @@ -352,27 +351,27 @@ class MassImportServiceTest { @Test void importSingleDocument_setsMetadataComplete_whenReceiversArePresent() { Person receiver = Person.builder().id(UUID.randomUUID()).firstName("Walter").lastName("Müller").build(); - when(documentRepository.findByOriginalFilename("rcv.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("rcv.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); when(personService.findOrCreateByAlias("Walter Müller")).thenReturn(receiver); List cells = List.of( "rcv.pdf", "", "", "", "", "Walter Müller", "", "", "", "", "", "", "", ""); service.importSingleDocument(cells, Optional.empty(), "rcv.pdf", "rcv"); - verify(documentRepository).save(argThat(Document::isMetadataComplete)); + verify(documentService).save(argThat(Document::isMetadataComplete)); } @Test void importSingleDocument_setsMetadataComplete_whenDateIsPresent() { - when(documentRepository.findByOriginalFilename("dated.pdf")).thenReturn(Optional.empty()); - when(documentRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(documentService.findByOriginalFilename("dated.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); List cells = List.of( "dated.pdf", "", "", "", "", "", "", "2024-03-15", "", "", "", "", "", ""); service.importSingleDocument(cells, Optional.empty(), "dated.pdf", "dated"); - verify(documentRepository).save(argThat(Document::isMetadataComplete)); + verify(documentService).save(argThat(Document::isMetadataComplete)); } // ─── buildTitle — null location ───────────────────────────────────────────