test(tag): close review-flagged gaps in case-collision coverage (#730)
Two adversarial gaps from PR #733 review: - Unit: exact-case must win even when its id is NOT the lowest, proving exact-case short-circuits before the lowest-id tie-break (a naive "lowest id across all CI matches" would pick the wrong row). - Integration: assert findAllByNameIgnoreCase folds the UPPERCASE "GLÜCKWÜNSCHE" — the exact string findOrCreate passes — so the umlaut proof matches the resolution path under test, not a lowercase probe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -85,7 +85,9 @@ class TagCaseCollisionIntegrationTest {
|
|||||||
Tag child = persistTag("glückwünsche", "Glückwünsche/glückwünsche", parent.getId());
|
Tag child = persistTag("glückwünsche", "Glückwünsche/glückwünsche", parent.getId());
|
||||||
|
|
||||||
// Proof that real Postgres LOWER() folds the umlaut so both rows match case-insensitively.
|
// Proof that real Postgres LOWER() folds the umlaut so both rows match case-insensitively.
|
||||||
assertThat(tagRepository.findAllByNameIgnoreCase("glückwünsche")).hasSize(2);
|
// Query with the UPPERCASE form findOrCreate actually passes — folding LOWER('GLÜCKWÜNSCHE')
|
||||||
|
// against LOWER(name) is the exact step under test; a lowercase probe wouldn't exercise it.
|
||||||
|
assertThat(tagRepository.findAllByNameIgnoreCase("GLÜCKWÜNSCHE")).hasSize(2);
|
||||||
|
|
||||||
// No exact-case "GLÜCKWÜNSCHE" row exists → resolution falls through to the case-insensitive
|
// No exact-case "GLÜCKWÜNSCHE" row exists → resolution falls through to the case-insensitive
|
||||||
// branch with two candidates and must pick the lowest id deterministically, never throwing.
|
// branch with two candidates and must pick the lowest id deterministically, never throwing.
|
||||||
|
|||||||
@@ -65,6 +65,20 @@ class TagServiceTest {
|
|||||||
verify(tagRepository, never()).save(any());
|
verify(tagRepository, never()).save(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findOrCreate_exactCaseWins_evenWhenItsIdIsNotTheLowest() {
|
||||||
|
// Adversarial guard: exact-case must short-circuit BEFORE the lowest-id rule. Here the exact row
|
||||||
|
// has the higher id, so a naive "always pick lowest id across all CI matches" would pick wrong.
|
||||||
|
Tag exactHigherId = Tag.builder().id(UUID.fromString("00000000-0000-0000-0000-000000000009")).name("geburt").build();
|
||||||
|
when(tagRepository.findByName("geburt")).thenReturn(Optional.of(exactHigherId));
|
||||||
|
|
||||||
|
Tag result = tagService.findOrCreate("geburt");
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(exactHigherId);
|
||||||
|
verify(tagRepository, never()).findAllByNameIgnoreCase(any()); // exact-case wins without consulting the CI list
|
||||||
|
verify(tagRepository, never()).save(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void findOrCreate_usesSingleCaseInsensitiveMatch_whenNoExactCase() {
|
void findOrCreate_usesSingleCaseInsensitiveMatch_whenNoExactCase() {
|
||||||
// Stored name is "Weihnachten"; a save replays "weihnachten" (no exact-case row) → bind to the
|
// Stored name is "Weihnachten"; a save replays "weihnachten" (no exact-case row) → bind to the
|
||||||
|
|||||||
Reference in New Issue
Block a user