feat(tag-search): expand children and surface ancestor path in search results
Modifies TagService.search() to enrich name-matches with tree relatives: root matches expand descendants, child matches prepend ancestors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -496,4 +496,71 @@ class TagServiceTest {
|
||||
.extracting(e -> ((DomainException) e).getCode())
|
||||
.isEqualTo(ErrorCode.TAG_NOT_FOUND);
|
||||
}
|
||||
|
||||
// ─── search ───────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void search_includes_children_when_root_matches() {
|
||||
UUID rootId = UUID.randomUUID();
|
||||
UUID childId = UUID.randomUUID();
|
||||
Tag root = Tag.builder().id(rootId).name("Briefe").build();
|
||||
Tag child = Tag.builder().id(childId).name("Familienbriefe").parentId(rootId).build();
|
||||
when(tagRepository.findByNameContainingIgnoreCase("Brief")).thenReturn(List.of(root));
|
||||
when(tagRepository.findDescendantIds(rootId)).thenReturn(List.of(rootId, childId));
|
||||
when(tagRepository.findAllById(Set.of(childId))).thenReturn(List.of(child));
|
||||
|
||||
List<Tag> result = tagService.search("Brief");
|
||||
|
||||
assertThat(result).extracting(Tag::getId).containsExactlyInAnyOrder(rootId, childId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_includes_ancestors_when_child_matches() {
|
||||
UUID rootId = UUID.randomUUID();
|
||||
UUID childId = UUID.randomUUID();
|
||||
Tag root = Tag.builder().id(rootId).name("Briefe").build();
|
||||
Tag child = Tag.builder().id(childId).name("Hochzeit").parentId(rootId).build();
|
||||
when(tagRepository.findByNameContainingIgnoreCase("Hochzeit")).thenReturn(List.of(child));
|
||||
when(tagRepository.findAncestorIds(childId)).thenReturn(List.of(rootId));
|
||||
when(tagRepository.findAllById(Set.of(rootId))).thenReturn(List.of(root));
|
||||
|
||||
List<Tag> result = tagService.search("Hochzeit");
|
||||
|
||||
assertThat(result).extracting(Tag::getId).containsExactlyInAnyOrder(rootId, childId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_deduplicates_when_root_also_in_descendant_result() {
|
||||
UUID rootId = UUID.randomUUID();
|
||||
UUID childId = UUID.randomUUID();
|
||||
Tag root = Tag.builder().id(rootId).name("Briefe").build();
|
||||
Tag child = Tag.builder().id(childId).name("Familienbriefe").parentId(rootId).build();
|
||||
when(tagRepository.findByNameContainingIgnoreCase("Brief")).thenReturn(List.of(root));
|
||||
// findDescendantIds includes the seed (rootId) — dedup must remove it from extras
|
||||
when(tagRepository.findDescendantIds(rootId)).thenReturn(List.of(rootId, childId));
|
||||
when(tagRepository.findAllById(Set.of(childId))).thenReturn(List.of(child));
|
||||
|
||||
List<Tag> result = tagService.search("Brief");
|
||||
|
||||
assertThat(result).extracting(Tag::getId).containsExactlyInAnyOrder(rootId, childId);
|
||||
assertThat(result).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_callsResolveEffectiveColors_onAllResults() {
|
||||
UUID rootId = UUID.randomUUID();
|
||||
UUID childId = UUID.randomUUID();
|
||||
Tag root = Tag.builder().id(rootId).name("Briefe").color("sage").build();
|
||||
Tag child = Tag.builder().id(childId).name("Familienbriefe").parentId(rootId).build();
|
||||
when(tagRepository.findByNameContainingIgnoreCase("Brief")).thenReturn(List.of(root));
|
||||
when(tagRepository.findDescendantIds(rootId)).thenReturn(List.of(rootId, childId));
|
||||
when(tagRepository.findAllById(Set.of(childId))).thenReturn(List.of(child));
|
||||
// resolveEffectiveColors will call findAllById for the child's parent color
|
||||
when(tagRepository.findAllById(Set.of(rootId))).thenReturn(List.of(root));
|
||||
|
||||
tagService.search("Brief");
|
||||
|
||||
// verify findAllById was called at least twice: once for extras, once inside resolveEffectiveColors
|
||||
verify(tagRepository, atLeastOnce()).findAllById(any());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user