feat(tag): add subtree document-count rollup to tag tree (#698)
Add subtreeDocumentCount to TagTreeNodeDTO, populated by a new recursive-CTE aggregate query that builds a tag closure and counts distinct documents per ancestor subtree. The direct documentCount is unchanged; getTagTree now maps both counts onto each node from two aggregate queries (no N+1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -102,8 +102,8 @@ class TagControllerTest {
|
||||
void getTagTree_returns200_withTreeStructure() throws Exception {
|
||||
UUID parentId = UUID.randomUUID();
|
||||
UUID childId = UUID.randomUUID();
|
||||
TagTreeNodeDTO child = new TagTreeNodeDTO(childId, "Haus", null, 0, List.of(), parentId);
|
||||
TagTreeNodeDTO parent = new TagTreeNodeDTO(parentId, "Immobilie", "teal", 0, List.of(child), null);
|
||||
TagTreeNodeDTO child = new TagTreeNodeDTO(childId, "Haus", null, 0, 0, List.of(), parentId);
|
||||
TagTreeNodeDTO parent = new TagTreeNodeDTO(parentId, "Immobilie", "teal", 0, 0, List.of(child), null);
|
||||
when(tagService.getTagTree()).thenReturn(List.of(parent));
|
||||
|
||||
mockMvc.perform(get("/api/tags/tree"))
|
||||
|
||||
@@ -278,6 +278,53 @@ class TagServiceTest {
|
||||
verify(tagRepository, times(1)).findDocumentCountsPerTag();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTagTree_populatesSubtreeDocumentCount_fromRollupQuery() {
|
||||
UUID tagId = UUID.randomUUID();
|
||||
Tag tag = Tag.builder().id(tagId).name("Reisen").build();
|
||||
TagRepository.TagCount subtreeEntry = mock(TagRepository.TagCount.class);
|
||||
when(subtreeEntry.getTagId()).thenReturn(tagId);
|
||||
when(subtreeEntry.getCount()).thenReturn(7L);
|
||||
when(tagRepository.findAll()).thenReturn(List.of(tag));
|
||||
when(tagRepository.findDocumentCountsPerTag()).thenReturn(List.of());
|
||||
when(tagRepository.findSubtreeDocumentCountsPerTag()).thenReturn(List.of(subtreeEntry));
|
||||
|
||||
var tree = tagService.getTagTree();
|
||||
|
||||
assertThat(tree.get(0).subtreeDocumentCount()).isEqualTo(7);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTagTree_keepsDirectAndSubtreeCountsIndependent() {
|
||||
UUID tagId = UUID.randomUUID();
|
||||
Tag tag = Tag.builder().id(tagId).name("Reisen").build();
|
||||
TagRepository.TagCount directEntry = mock(TagRepository.TagCount.class);
|
||||
when(directEntry.getTagId()).thenReturn(tagId);
|
||||
when(directEntry.getCount()).thenReturn(2L);
|
||||
TagRepository.TagCount subtreeEntry = mock(TagRepository.TagCount.class);
|
||||
when(subtreeEntry.getTagId()).thenReturn(tagId);
|
||||
when(subtreeEntry.getCount()).thenReturn(7L);
|
||||
when(tagRepository.findAll()).thenReturn(List.of(tag));
|
||||
when(tagRepository.findDocumentCountsPerTag()).thenReturn(List.of(directEntry));
|
||||
when(tagRepository.findSubtreeDocumentCountsPerTag()).thenReturn(List.of(subtreeEntry));
|
||||
|
||||
var node = tagService.getTagTree().get(0);
|
||||
|
||||
assertThat(node.documentCount()).isEqualTo(2);
|
||||
assertThat(node.subtreeDocumentCount()).isEqualTo(7);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTagTree_callsFindSubtreeDocumentCountsPerTag_exactlyOnce() {
|
||||
when(tagRepository.findAll()).thenReturn(List.of());
|
||||
when(tagRepository.findDocumentCountsPerTag()).thenReturn(List.of());
|
||||
when(tagRepository.findSubtreeDocumentCountsPerTag()).thenReturn(List.of());
|
||||
|
||||
tagService.getTagTree();
|
||||
|
||||
verify(tagRepository, times(1)).findSubtreeDocumentCountsPerTag();
|
||||
}
|
||||
|
||||
// ─── resolveEffectiveColors ───────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user