diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java index f295715f..51bebc24 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/PersonService.java @@ -4,11 +4,14 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.raddatz.familienarchiv.dto.PersonNameAliasDTO; import org.raddatz.familienarchiv.dto.PersonSummaryDTO; import org.raddatz.familienarchiv.dto.PersonUpdateDTO; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.Person; +import org.raddatz.familienarchiv.model.PersonNameAlias; +import org.raddatz.familienarchiv.repository.PersonNameAliasRepository; import org.raddatz.familienarchiv.repository.PersonRepository; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -22,6 +25,7 @@ import lombok.RequiredArgsConstructor; public class PersonService { private final PersonRepository personRepository; + private final PersonNameAliasRepository aliasRepository; public List findAll(String q) { if (q == null) { @@ -137,4 +141,35 @@ public class PersonService { personRepository.deleteById(sourceId); } + + // ─── Alias management ─────────────────────────────────────────────────── + + public List getAliases(UUID personId) { + getById(personId); + return aliasRepository.findByPersonIdOrderBySortOrderAscCreatedAtAsc(personId); + } + + @Transactional + public PersonNameAlias addAlias(UUID personId, PersonNameAliasDTO dto) { + Person person = getById(personId); + int nextSortOrder = aliasRepository.findMaxSortOrder(personId) + 1; + PersonNameAlias alias = PersonNameAlias.builder() + .person(person) + .lastName(dto.lastName()) + .firstName(dto.firstName()) + .type(dto.type()) + .sortOrder(nextSortOrder) + .build(); + return aliasRepository.save(alias); + } + + @Transactional + public void removeAlias(UUID personId, UUID aliasId) { + PersonNameAlias alias = aliasRepository.findById(aliasId) + .orElseThrow(() -> DomainException.notFound(ErrorCode.ALIAS_NOT_FOUND, "Alias not found: " + aliasId)); + if (!alias.getPerson().getId().equals(personId)) { + throw DomainException.forbidden("Alias does not belong to this person"); + } + aliasRepository.delete(alias); + } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java index f229f8b9..33288bf6 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/PersonServiceTest.java @@ -5,10 +5,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.raddatz.familienarchiv.dto.PersonNameAliasDTO; import org.raddatz.familienarchiv.dto.PersonSummaryDTO; import org.raddatz.familienarchiv.dto.PersonUpdateDTO; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.model.Person; +import org.raddatz.familienarchiv.model.PersonNameAlias; +import org.raddatz.familienarchiv.model.PersonNameAliasType; +import org.raddatz.familienarchiv.repository.PersonNameAliasRepository; import org.raddatz.familienarchiv.repository.PersonRepository; import org.springframework.web.server.ResponseStatusException; @@ -25,6 +29,7 @@ import static org.mockito.Mockito.*; class PersonServiceTest { @Mock PersonRepository personRepository; + @Mock PersonNameAliasRepository aliasRepository; @InjectMocks PersonService personService; // ─── getById ───────────────────────────────────────────────────────────── @@ -436,4 +441,99 @@ class PersonServiceTest { verify(personRepository).deleteReceiverReferences(sourceId); verify(personRepository).deleteById(sourceId); } + + // ─── getAliases ───────────────────────────────────────────────────────── + + @Test + void getAliases_returnsSortedAliases() { + UUID personId = UUID.randomUUID(); + when(personRepository.findById(personId)).thenReturn(Optional.of( + Person.builder().id(personId).firstName("Clara").lastName("Cram").build())); + List aliases = List.of( + PersonNameAlias.builder().id(UUID.randomUUID()).lastName("de Gruyter").type(PersonNameAliasType.BIRTH).sortOrder(0).build()); + when(aliasRepository.findByPersonIdOrderBySortOrderAscCreatedAtAsc(personId)).thenReturn(aliases); + + List result = personService.getAliases(personId); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getLastName()).isEqualTo("de Gruyter"); + } + + @Test + void getAliases_throwsNotFound_whenPersonMissing() { + UUID personId = UUID.randomUUID(); + when(personRepository.findById(personId)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> personService.getAliases(personId)) + .isInstanceOf(DomainException.class); + } + + // ─── addAlias ─────────────────────────────────────────────────────────── + + @Test + void addAlias_savesWithAutoIncrementedSortOrder() { + UUID personId = UUID.randomUUID(); + Person person = Person.builder().id(personId).firstName("Clara").lastName("Cram").build(); + when(personRepository.findById(personId)).thenReturn(Optional.of(person)); + when(aliasRepository.findMaxSortOrder(personId)).thenReturn(2); + when(aliasRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + + PersonNameAliasDTO dto = new PersonNameAliasDTO("de Gruyter", null, PersonNameAliasType.BIRTH); + PersonNameAlias result = personService.addAlias(personId, dto); + + assertThat(result.getSortOrder()).isEqualTo(3); + assertThat(result.getLastName()).isEqualTo("de Gruyter"); + assertThat(result.getPerson()).isEqualTo(person); + } + + @Test + void addAlias_throwsNotFound_whenPersonMissing() { + UUID personId = UUID.randomUUID(); + when(personRepository.findById(personId)).thenReturn(Optional.empty()); + + PersonNameAliasDTO dto = new PersonNameAliasDTO("de Gruyter", null, PersonNameAliasType.BIRTH); + + assertThatThrownBy(() -> personService.addAlias(personId, dto)) + .isInstanceOf(DomainException.class); + } + + // ─── removeAlias ──────────────────────────────────────────────────────── + + @Test + void removeAlias_deletesAlias_whenBelongsToPerson() { + UUID personId = UUID.randomUUID(); + UUID aliasId = UUID.randomUUID(); + Person person = Person.builder().id(personId).firstName("Clara").lastName("Cram").build(); + PersonNameAlias alias = PersonNameAlias.builder().id(aliasId).person(person).lastName("de Gruyter").build(); + when(aliasRepository.findById(aliasId)).thenReturn(Optional.of(alias)); + + personService.removeAlias(personId, aliasId); + + verify(aliasRepository).delete(alias); + } + + @Test + void removeAlias_throwsNotFound_whenAliasMissing() { + UUID personId = UUID.randomUUID(); + UUID aliasId = UUID.randomUUID(); + when(aliasRepository.findById(aliasId)).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> personService.removeAlias(personId, aliasId)) + .isInstanceOf(DomainException.class); + } + + @Test + void removeAlias_throwsForbidden_whenAliasDoesNotBelongToPerson() { + UUID personId = UUID.randomUUID(); + UUID otherPersonId = UUID.randomUUID(); + UUID aliasId = UUID.randomUUID(); + Person otherPerson = Person.builder().id(otherPersonId).firstName("Other").lastName("Person").build(); + PersonNameAlias alias = PersonNameAlias.builder().id(aliasId).person(otherPerson).lastName("de Gruyter").build(); + when(aliasRepository.findById(aliasId)).thenReturn(Optional.of(alias)); + + assertThatThrownBy(() -> personService.removeAlias(personId, aliasId)) + .isInstanceOf(DomainException.class) + .extracting(e -> ((DomainException) e).getStatus().value()) + .isEqualTo(403); + } }