feat(document): reject end date without RANGE precision (#678)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -486,6 +486,12 @@ public class DocumentService {
|
|||||||
throw DomainException.badRequest(ErrorCode.INVALID_DATE_RANGE,
|
throw DomainException.badRequest(ErrorCode.INVALID_DATE_RANGE,
|
||||||
"meta_date_end must not be before meta_date");
|
"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
|
@Transactional
|
||||||
|
|||||||
@@ -204,10 +204,12 @@ class DocumentServiceTest {
|
|||||||
// Editing a doc (e.g. fixing a location typo) without touching the precision
|
// 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
|
// 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.
|
// 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();
|
UUID id = UUID.randomUUID();
|
||||||
Document doc = Document.builder()
|
Document doc = Document.builder()
|
||||||
.id(id)
|
.id(id)
|
||||||
.metaDatePrecision(DatePrecision.MONTH)
|
.metaDatePrecision(DatePrecision.RANGE)
|
||||||
.metaDateEnd(LocalDate.of(1916, 6, 30))
|
.metaDateEnd(LocalDate.of(1916, 6, 30))
|
||||||
.metaDateRaw("Juni 1916")
|
.metaDateRaw("Juni 1916")
|
||||||
.receivers(new HashSet<>())
|
.receivers(new HashSet<>())
|
||||||
@@ -221,7 +223,7 @@ class DocumentServiceTest {
|
|||||||
|
|
||||||
documentService.updateDocument(id, dto, null, null);
|
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.getMetaDateEnd()).isEqualTo(LocalDate.of(1916, 6, 30));
|
||||||
assertThat(doc.getMetaDateRaw()).isEqualTo("Juni 1916");
|
assertThat(doc.getMetaDateRaw()).isEqualTo("Juni 1916");
|
||||||
}
|
}
|
||||||
@@ -313,6 +315,26 @@ class DocumentServiceTest {
|
|||||||
verify(documentRepository, atLeastOnce()).save(any());
|
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 ───────────────────────────────────────────────────
|
// ─── deleteTagCascading ───────────────────────────────────────────────────
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user