From 41b205becc7929c7afa03526e1670cff1493b585 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 22 May 2026 17:49:01 +0200 Subject: [PATCH] test(document): add LazyInit guard + detail regression tests; prune Document.list graph Remove trainingLabels from Document.list entity graph now that DocumentListItem does not touch that association. Integration tests guard against future LazyInitializationException regressions and confirm Document.full still loads trainingLabels for the detail endpoint. Co-Authored-By: Claude Sonnet 4.6 --- .../familienarchiv/document/Document.java | 3 +- .../DocumentListItemIntegrationTest.java | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java b/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java index 387e3547..71c6dead 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/Document.java @@ -31,8 +31,7 @@ import java.util.UUID; @NamedEntityGraph(name = "Document.list", attributeNodes = { @NamedAttributeNode("sender"), @NamedAttributeNode("receivers"), - @NamedAttributeNode("tags"), - @NamedAttributeNode("trainingLabels") + @NamedAttributeNode("tags") }) @Entity @Table(name = "documents") diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java new file mode 100644 index 00000000..485b865e --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java @@ -0,0 +1,98 @@ +package org.raddatz.familienarchiv.document; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.PostgresContainerConfig; +import org.raddatz.familienarchiv.audit.AuditLogQueryService; +import org.raddatz.familienarchiv.ocr.TrainingLabel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import software.amazon.awssdk.services.s3.S3Client; + +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +/** + * AC #2: Document with trainingLabels does not cause LazyInitializationException in search. + * AC #3: Detail API still returns trainingLabels after the Document.list graph change. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@ActiveProfiles("test") +@Import(PostgresContainerConfig.class) +class DocumentListItemIntegrationTest { + + @MockitoBean + S3Client s3Client; + + @MockitoBean + AuditLogQueryService auditLogQueryService; + + @Autowired + DocumentRepository documentRepository; + + @Autowired + DocumentService documentService; + + @AfterEach + void cleanup() { + documentRepository.deleteAll(); + } + + @Test + void search_doesNotThrow_whenDocumentHasTrainingLabels() { + documentRepository.save(Document.builder() + .title("Kurrent Brief") + .originalFilename("kurrent.pdf") + .status(DocumentStatus.UPLOADED) + .trainingLabels(new HashSet<>(Set.of(TrainingLabel.KURRENT_RECOGNITION))) + .build()); + + assertThatCode(() -> documentService.searchDocuments( + null, null, null, null, null, null, null, null, + DocumentSort.DATE, "DESC", null, + PageRequest.of(0, 50))) + .doesNotThrowAnyException(); + } + + @Test + void search_returns_list_item_without_sensitive_fields_when_document_has_training_labels() { + documentRepository.save(Document.builder() + .title("Kurrent Brief") + .originalFilename("kurrent2.pdf") + .status(DocumentStatus.UPLOADED) + .trainingLabels(new HashSet<>(Set.of(TrainingLabel.KURRENT_RECOGNITION))) + .build()); + + DocumentSearchResult result = documentService.searchDocuments( + null, null, null, null, null, null, null, null, + DocumentSort.DATE, "DESC", null, + PageRequest.of(0, 50)); + + assertThat(result.totalElements()).isGreaterThan(0); + DocumentListItem item = result.items().get(0); + assertThat(item.id()).isNotNull(); + assertThat(item.title()).isEqualTo("Kurrent Brief"); + } + + @Test + void detail_stillReturnsTrainingLabels() { + Document saved = documentRepository.save(Document.builder() + .title("Detail Test") + .originalFilename("detail_test.pdf") + .status(DocumentStatus.UPLOADED) + .trainingLabels(new HashSet<>(Set.of(TrainingLabel.KURRENT_RECOGNITION))) + .build()); + + // Document.full entity graph (used by findById) must still load trainingLabels + Document loaded = documentRepository.findById(saved.getId()).orElseThrow(); + + assertThat(loaded.getTrainingLabels()).containsExactly(TrainingLabel.KURRENT_RECOGNITION); + } +}