From 73f614bc3aab89cc6d5dc5efeb0d0d06e6a8f6d5 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 1 Jun 2026 09:17:52 +0200 Subject: [PATCH] feat(document): reject end date without RANGE precision (#678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the second validateDateRange predicate mirroring chk_meta_date_end_only_for_range, so a direct API client that sets an end date without RANGE precision gets a clean 400 INVALID_DATE_RANGE instead of a 500 (AC6). Shares the code with the end-before-start branch. Also fix updateDocument_preservesStoredPrecision_whenDtoOmitsIt: its stored fixture (MONTH + end date) is a state the DB CHECK forbids, so the carried-over-state guard correctly rejects it. Switched to RANGE + end — the only DB-valid non-null-end combo — preserving the test's intent. Co-Authored-By: Claude Opus 4.8 --- .../document/DocumentService.java | 6 +++++ .../document/DocumentServiceTest.java | 26 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java index e947ddc8..2f3edbc2 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java @@ -486,6 +486,12 @@ public class DocumentService { throw DomainException.badRequest(ErrorCode.INVALID_DATE_RANGE, "meta_date_end must not be before meta_date"); } + // Mirrors chk_meta_date_end_only_for_range. API-only: the edit form clears the + // end field off-RANGE, so this branch closes the same 500 class for direct clients. + if (doc.getMetaDateEnd() != null && doc.getMetaDatePrecision() != DatePrecision.RANGE) { + throw DomainException.badRequest(ErrorCode.INVALID_DATE_RANGE, + "meta_date_end is only allowed when meta_date_precision is RANGE"); + } } @Transactional 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 58b64fd4..4c11bd30 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java @@ -204,10 +204,12 @@ class DocumentServiceTest { // Editing a doc (e.g. fixing a location typo) without touching the precision // controls must NOT fabricate a precision. The form omits the three precision // fields → they arrive null on the DTO → the stored values must be preserved. + // Stored combo is RANGE + end: the only DB-valid way to have a non-null end + // (chk_meta_date_end_only_for_range), so the carried-over state passes the guard. UUID id = UUID.randomUUID(); Document doc = Document.builder() .id(id) - .metaDatePrecision(DatePrecision.MONTH) + .metaDatePrecision(DatePrecision.RANGE) .metaDateEnd(LocalDate.of(1916, 6, 30)) .metaDateRaw("Juni 1916") .receivers(new HashSet<>()) @@ -221,7 +223,7 @@ class DocumentServiceTest { documentService.updateDocument(id, dto, null, null); - assertThat(doc.getMetaDatePrecision()).isEqualTo(DatePrecision.MONTH); + assertThat(doc.getMetaDatePrecision()).isEqualTo(DatePrecision.RANGE); assertThat(doc.getMetaDateEnd()).isEqualTo(LocalDate.of(1916, 6, 30)); assertThat(doc.getMetaDateRaw()).isEqualTo("Juni 1916"); } @@ -313,6 +315,26 @@ class DocumentServiceTest { verify(documentRepository, atLeastOnce()).save(any()); } + @Test + void updateDocument_rejectsEndDate_whenPrecisionNotRange() { + // AC6: an end date only makes sense for RANGE (mirrors chk_meta_date_end_only_for_range). + // API-only — the edit form clears the end field off-RANGE — so close the 500 class here too. + UUID id = UUID.randomUUID(); + Document doc = docForRangeUpdate(id); + when(documentRepository.findById(id)).thenReturn(Optional.of(doc)); + + DocumentUpdateDTO dto = new DocumentUpdateDTO(); + dto.setDocumentDate(LocalDate.of(1917, 1, 10)); + dto.setMetaDatePrecision(DatePrecision.MONTH); + dto.setMetaDateEnd(LocalDate.of(1917, 1, 31)); + + assertThatThrownBy(() -> documentService.updateDocument(id, dto, null, null)) + .isInstanceOf(DomainException.class) + .extracting(e -> ((DomainException) e).getCode()) + .isEqualTo(ErrorCode.INVALID_DATE_RANGE); + verify(documentRepository, never()).save(any()); + } + // ─── deleteTagCascading ─────────────────────────────────────────────────── @Test