fix(#221): resolve inherited color on child tags in document responses
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m51s
CI / Backend Unit Tests (push) Failing after 2m46s

Colors are stored only on root-level tags. DocumentService now calls
TagService.resolveEffectiveColors() before returning search results and
single-document responses, so child tags carry their parent's color when
serialised to JSON. Parent tags are batch-loaded in a single query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-16 19:28:21 +02:00
parent 171f06da22
commit 06fd5ae2da
3 changed files with 115 additions and 5 deletions

View File

@@ -14,6 +14,7 @@ import org.springframework.web.server.ResponseStatusException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -233,6 +234,75 @@ class TagServiceTest {
assertThat(tree.get(0).children().get(0).id()).isEqualTo(childId);
}
// ─── resolveEffectiveColors ───────────────────────────────────────────────
@Test
void resolveEffectiveColors_doesNothing_whenCollectionIsEmpty() {
tagService.resolveEffectiveColors(List.of());
verifyNoInteractions(tagRepository);
}
@Test
void resolveEffectiveColors_doesNothing_whenAllTagsHaveOwnColor() {
Tag tag = Tag.builder().id(UUID.randomUUID()).name("Root").color("sage").build();
tagService.resolveEffectiveColors(List.of(tag));
verify(tagRepository, never()).findAllById(any());
assertThat(tag.getColor()).isEqualTo("sage");
}
@Test
void resolveEffectiveColors_doesNothing_whenTagHasNoParentAndNoColor() {
Tag tag = Tag.builder().id(UUID.randomUUID()).name("Root").build();
tagService.resolveEffectiveColors(List.of(tag));
verify(tagRepository, never()).findAllById(any());
assertThat(tag.getColor()).isNull();
}
@Test
void resolveEffectiveColors_setsParentColor_onChildTagWithNoColor() {
UUID parentId = UUID.randomUUID();
Tag parent = Tag.builder().id(parentId).name("Parent").color("sage").build();
Tag child = Tag.builder().id(UUID.randomUUID()).name("Child").parentId(parentId).build();
when(tagRepository.findAllById(Set.of(parentId))).thenReturn(List.of(parent));
tagService.resolveEffectiveColors(List.of(child));
assertThat(child.getColor()).isEqualTo("sage");
}
@Test
void resolveEffectiveColors_leavesChildUnchanged_whenParentHasNoColor() {
UUID parentId = UUID.randomUUID();
Tag parent = Tag.builder().id(parentId).name("Parent").build();
Tag child = Tag.builder().id(UUID.randomUUID()).name("Child").parentId(parentId).build();
when(tagRepository.findAllById(Set.of(parentId))).thenReturn(List.of(parent));
tagService.resolveEffectiveColors(List.of(child));
assertThat(child.getColor()).isNull();
}
@Test
void resolveEffectiveColors_batchLoadsParents_inOneQuery() {
UUID parentId1 = UUID.randomUUID();
UUID parentId2 = UUID.randomUUID();
Tag parent1 = Tag.builder().id(parentId1).name("P1").color("sage").build();
Tag parent2 = Tag.builder().id(parentId2).name("P2").color("sienna").build();
Tag child1 = Tag.builder().id(UUID.randomUUID()).name("C1").parentId(parentId1).build();
Tag child2 = Tag.builder().id(UUID.randomUUID()).name("C2").parentId(parentId2).build();
when(tagRepository.findAllById(Set.of(parentId1, parentId2))).thenReturn(List.of(parent1, parent2));
tagService.resolveEffectiveColors(List.of(child1, child2));
verify(tagRepository, times(1)).findAllById(any());
assertThat(child1.getColor()).isEqualTo("sage");
assertThat(child2.getColor()).isEqualTo("sienna");
}
// ─── delete ───────────────────────────────────────────────────────────────
@Test