Import normalizer: offline tool to normalize the raw archive spreadsheets #663
@@ -22,6 +22,9 @@ public interface TagRepository extends JpaRepository<Tag, UUID> {
|
|||||||
|
|
||||||
Optional<Tag> findByNameIgnoreCase(String name);
|
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);
|
List<Tag> findByNameContainingIgnoreCase(String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,6 +55,26 @@ public class TagService {
|
|||||||
.orElseGet(() -> tagRepository.save(Tag.builder().name(cleanName).build()));
|
.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
|
@Transactional
|
||||||
public Tag update(UUID id, TagUpdateDTO dto) {
|
public Tag update(UUID id, TagUpdateDTO dto) {
|
||||||
Tag tag = getById(id);
|
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