feat(tag): add upsertBySourceRef keyed on canonical tag_path
Idempotent tag upsert for the Phase-3 importer (ADR-025). source_ref is the stable identity (the canonical tag_path); on re-import a human-renamed tag name is preserved while the parent link is refreshed. Refs #669 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,9 @@ public interface TagRepository extends JpaRepository<Tag, UUID> {
|
||||
|
||||
Optional<Tag> findByNameIgnoreCase(String name);
|
||||
|
||||
// Lookup by the canonical tag_path, used for idempotent canonical re-import (Phase 3).
|
||||
Optional<Tag> findBySourceRef(String sourceRef);
|
||||
|
||||
List<Tag> findByNameContainingIgnoreCase(String name);
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,26 @@ public class TagService {
|
||||
.orElseGet(() -> tagRepository.save(Tag.builder().name(cleanName).build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Idempotent upsert keyed on {@code sourceRef} (the canonical tag_path) for the
|
||||
* Phase-3 importer (ADR-025). On first import the canonical name and parent are
|
||||
* written; on re-import a human-renamed tag name is preserved (the source_ref is the
|
||||
* stable identity, the name is a human-editable label).
|
||||
*/
|
||||
@Transactional
|
||||
public Tag upsertBySourceRef(String sourceRef, String name, UUID parentId) {
|
||||
return tagRepository.findBySourceRef(sourceRef)
|
||||
.map(existing -> {
|
||||
existing.setParentId(parentId);
|
||||
return tagRepository.save(existing);
|
||||
})
|
||||
.orElseGet(() -> tagRepository.save(Tag.builder()
|
||||
.sourceRef(sourceRef)
|
||||
.name(name)
|
||||
.parentId(parentId)
|
||||
.build()));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Tag update(UUID id, TagUpdateDTO dto) {
|
||||
Tag tag = getById(id);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.raddatz.familienarchiv.tag;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TagImportUpsertTest {
|
||||
|
||||
@Mock TagRepository tagRepository;
|
||||
@InjectMocks TagService tagService;
|
||||
|
||||
@Test
|
||||
void upsertBySourceRef_insertsNewTag_whenSourceRefUnknown() {
|
||||
when(tagRepository.findBySourceRef("Themen/Brautbriefe")).thenReturn(Optional.empty());
|
||||
when(tagRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
UUID parentId = UUID.randomUUID();
|
||||
Tag result = tagService.upsertBySourceRef("Themen/Brautbriefe", "Brautbriefe", parentId);
|
||||
|
||||
assertThat(result.getSourceRef()).isEqualTo("Themen/Brautbriefe");
|
||||
assertThat(result.getName()).isEqualTo("Brautbriefe");
|
||||
assertThat(result.getParentId()).isEqualTo(parentId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void upsertBySourceRef_updatesInPlace_whenSourceRefExists() {
|
||||
Tag existing = Tag.builder().id(UUID.randomUUID()).name("Brautbriefe")
|
||||
.sourceRef("Themen/Brautbriefe").build();
|
||||
when(tagRepository.findBySourceRef("Themen/Brautbriefe")).thenReturn(Optional.of(existing));
|
||||
when(tagRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
tagService.upsertBySourceRef("Themen/Brautbriefe", "Brautbriefe", null);
|
||||
|
||||
verify(tagRepository).save(argThat(t -> t.getId().equals(existing.getId())));
|
||||
verify(tagRepository, never()).save(argThat(t -> t.getId() == null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upsertBySourceRef_preservesHumanRenamedTag_onReimport() {
|
||||
Tag humanRenamed = Tag.builder().id(UUID.randomUUID()).name("Verlobungsbriefe")
|
||||
.sourceRef("Themen/Brautbriefe").build();
|
||||
when(tagRepository.findBySourceRef("Themen/Brautbriefe")).thenReturn(Optional.of(humanRenamed));
|
||||
when(tagRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
Tag result = tagService.upsertBySourceRef("Themen/Brautbriefe", "Brautbriefe", null);
|
||||
|
||||
assertThat(result.getName()).isEqualTo("Verlobungsbriefe");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user