From f656f7c1ffdc7aaf0cf62b7c45506436f3a87b40 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 4 Jun 2026 17:10:50 +0200 Subject: [PATCH] test(document): close review-flagged coverage gaps for auto-title sync (#726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - save-time: precision+raw carry-over when the DTO omits them (exercises the shared skip-null resolvers), and a RANGE label round-trip (Sara/Elicit) - factory: a bare Document with a null index builds "" rather than NPE-ing (Felix) - backfill matcher: negative near-misses — ASCII hyphen vs en dash, missing separator before trailing text, year-with-trailing-letters, index followed by text without a separator (Sara) - backfill integration: tighten the count assertion to exactly 1 on the clean test DB (Sara) Co-Authored-By: Claude Opus 4.8 --- .../document/DocumentServiceTest.java | 37 +++++++++++++++++++ .../DocumentTitleBackfillIntegrationTest.java | 2 +- .../DocumentTitleBackfillMatcherTest.java | 24 ++++++++++++ .../document/DocumentTitleFactoryTest.java | 7 ++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java index fced773a..023b2003 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java @@ -371,6 +371,43 @@ class DocumentServiceTest { assertThat(stored.getTitle()).isEqualTo("C-0029 – Frühling 1943"); } + @Test + void updateDocument_carriesStoredPrecisionAndRaw_whenDtoOmitsThem() throws Exception { + // Only the year changes; precision/end/raw are omitted from the DTO, so projectedState + // must carry them from the entity (exercises the skip-null effective* resolvers). + Document stored = makeStored("C-0029", LocalDate.of(1943, 4, 1), DatePrecision.SEASON, null); + stored.setMetaDateRaw("Frühling 1943"); + stored.setTitle(documentTitleFactory.build(stored)); // "C-0029 – Frühling 1943" + DocumentUpdateDTO dto = editDto(stored.getTitle(), LocalDate.of(1944, 4, 1), null, null); + + runUpdate(stored, dto); + + assertThat(stored.getTitle()).isEqualTo("C-0029 – Frühling 1944"); + } + + @Test + void updateDocument_roundTripsRangeLabel_atSaveTime() throws Exception { + Document stored = Document.builder() + .id(UUID.randomUUID()) + .originalFilename("C-0029") + .documentDate(LocalDate.of(1917, 1, 10)) + .metaDatePrecision(DatePrecision.RANGE) + .metaDateEnd(LocalDate.of(1917, 1, 11)) + .receivers(new HashSet<>()) + .tags(new HashSet<>()) + .build(); + stored.setTitle(documentTitleFactory.build(stored)); // "C-0029 – 10.–11. Jan. 1917" + DocumentUpdateDTO dto = new DocumentUpdateDTO(); + dto.setTitle(stored.getTitle()); + dto.setDocumentDate(LocalDate.of(1918, 1, 10)); + dto.setMetaDatePrecision(DatePrecision.RANGE); + dto.setMetaDateEnd(LocalDate.of(1918, 1, 11)); + + runUpdate(stored, dto); + + assertThat(stored.getTitle()).isEqualTo("C-0029 – 10.–11. Jan. 1918"); + } + @Test void updateDocument_doesNotRegenerateToBlank_whenSubmittedTitleEmpty() throws Exception { Document stored = makeStored("C-0029", LocalDate.of(1928, 1, 1), DatePrecision.YEAR, "Berlin"); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillIntegrationTest.java index a209e1a2..4dd556f8 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillIntegrationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillIntegrationTest.java @@ -52,7 +52,7 @@ class DocumentTitleBackfillIntegrationTest { int count = documentService.backfillTitles(); - assertThat(count).isGreaterThanOrEqualTo(1); + assertThat(count).isEqualTo(1); // exactly the one stale row seeded (clean test DB) assertThat(documentRepository.findById(stale.getId()).orElseThrow().getTitle()) .isEqualTo("C-0029 – 1928 – Berlin"); } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillMatcherTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillMatcherTest.java index be5e02be..1a87f2e2 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillMatcherTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleBackfillMatcherTest.java @@ -118,6 +118,30 @@ class DocumentTitleBackfillMatcherTest { assertThat(overwritable("Ganz anderer Titel", null)).isFalse(); } + // ─── near-miss: shapes that look almost machine-built but are not ────────── + + @Test + void ascii_hyphen_instead_of_en_dash_separator_is_skipped() { + // The separator is " – " (en dash); a plain " - " is not the machine separator. + assertThat(overwritable("C-0029 - 1916", null)).isFalse(); + } + + @Test + void date_label_without_separator_before_trailing_text_is_skipped() { + // "1916 Berlin" is not a date label and is not joined by " – "; prose, not machine. + assertThat(overwritable("C-0029 – 1916 Berlin", null)).isFalse(); + } + + @Test + void year_with_trailing_letters_is_not_a_year_label() { + assertThat(overwritable("C-0029 – 1916er Brief", null)).isFalse(); + } + + @Test + void index_immediately_followed_by_text_without_separator_is_skipped() { + assertThat(overwritable("C-0029x – 1916", null)).isFalse(); + } + // ─── fail-closed guards ─────────────────────────────────────────────────── @Test diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleFactoryTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleFactoryTest.java index d01801c9..525b791f 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleFactoryTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentTitleFactoryTest.java @@ -71,6 +71,13 @@ class DocumentTitleFactoryTest { assertThat(factory.build(d)).isEqualTo("C-0029 – 1928"); } + @Test + void bare_document_with_null_index_builds_empty_string_not_npe() { + // originalFilename is NOT NULL in production; the guard keeps a synthetic/partial entity + // from tripping StringBuilder(null) with an opaque NPE. + assertThat(factory.build(Document.builder().build())).isEqualTo(""); + } + @Test void day_precision_renders_the_full_german_label() { Document d = doc("C-0029")