diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/TagCaseCollisionIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/TagCaseCollisionIntegrationTest.java index 994d4062..ebf18b10 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/TagCaseCollisionIntegrationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/TagCaseCollisionIntegrationTest.java @@ -85,7 +85,9 @@ class TagCaseCollisionIntegrationTest { 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. - 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 // branch with two candidates and must pick the lowest id deterministically, never throwing. diff --git a/backend/src/test/java/org/raddatz/familienarchiv/tag/TagServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/tag/TagServiceTest.java index aa9436f4..d0373f7b 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/tag/TagServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/tag/TagServiceTest.java @@ -65,6 +65,20 @@ class TagServiceTest { 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 void findOrCreate_usesSingleCaseInsensitiveMatch_whenNoExactCase() { // Stored name is "Weihnachten"; a save replays "weihnachten" (no exact-case row) → bind to the