feat(person): add paged search, confirm and delete to PersonService
PersonService.search maps a PersonFilter to the paired slice/count repository queries and returns a PersonSearchResult with a server-side total. confirmPerson clears the provisional flag (the state transition behind PATCH /confirm). deletePerson detaches sender/receiver document references before the hard delete so it cannot orphan an FK. Refs #667 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -63,4 +63,53 @@ class PersonServiceIntegrationTest {
|
||||
assertThat(result.getFirstName()).isEqualTo("Clara");
|
||||
assertThat(result.getLastName()).isEqualTo("Cram");
|
||||
}
|
||||
|
||||
// ─── #667: confirm round-trip + reader-default semantics ──────────────────
|
||||
|
||||
@Test
|
||||
void search_readerDefault_hidesProvisionalZeroDocumentPerson() {
|
||||
personRepository.save(Person.builder()
|
||||
.firstName("Unbe").lastName("Staetigt").provisional(true).build());
|
||||
|
||||
PersonSearchResult result = personService.search(PersonFilter.cleanDefault(), 0, 50, null);
|
||||
|
||||
assertThat(result.items()).noneMatch(p -> p.getLastName().equals("Staetigt"));
|
||||
assertThat(result.totalElements()).isEqualTo(result.items().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_showAll_includesProvisionalZeroDocumentPerson() {
|
||||
personRepository.save(Person.builder()
|
||||
.firstName("Unbe").lastName("Staetigt").provisional(true).build());
|
||||
|
||||
PersonSearchResult result = personService.search(PersonFilter.showAll(), 0, 50, null);
|
||||
|
||||
assertThat(result.items()).anyMatch(p -> p.getLastName().equals("Staetigt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmPerson_clearsProvisional_andShowAllTreatsItAsConfirmed() {
|
||||
Person provisional = personRepository.save(Person.builder()
|
||||
.firstName("Bald").lastName("Bestaetigt").provisional(true).build());
|
||||
|
||||
personService.confirmPerson(provisional.getId());
|
||||
|
||||
Person reloaded = personRepository.findById(provisional.getId()).orElseThrow();
|
||||
assertThat(reloaded.isProvisional()).isFalse();
|
||||
|
||||
PersonSearchResult showAll = personService.search(PersonFilter.showAll(), 0, 50, null);
|
||||
assertThat(showAll.items())
|
||||
.filteredOn(p -> p.getId().equals(provisional.getId()))
|
||||
.allMatch(p -> !p.isProvisional());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletePerson_removesPerson() {
|
||||
Person target = personRepository.save(Person.builder()
|
||||
.firstName("Weg").lastName("Person").provisional(true).build());
|
||||
|
||||
personService.deletePerson(target.getId());
|
||||
|
||||
assertThat(personRepository.findById(target.getId())).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,111 @@ class PersonServiceTest {
|
||||
verify(personRepository, never()).findAllWithDocumentCount();
|
||||
}
|
||||
|
||||
// ─── #667: search (filter + pagination) ──────────────────────────────────
|
||||
|
||||
@Test
|
||||
void search_returnsPagedResult_withTotalsFromCountQuery() {
|
||||
PersonFilter filter = PersonFilter.cleanDefault();
|
||||
when(personRepository.countByFilter(null, null, null, null, true, null)).thenReturn(120L);
|
||||
when(personRepository.findByFilter(null, null, null, null, true, null, 50, 0))
|
||||
.thenReturn(List.of());
|
||||
|
||||
PersonSearchResult result = personService.search(filter, 0, 50, null);
|
||||
|
||||
assertThat(result.totalElements()).isEqualTo(120L);
|
||||
assertThat(result.pageNumber()).isEqualTo(0);
|
||||
assertThat(result.pageSize()).isEqualTo(50);
|
||||
assertThat(result.totalPages()).isEqualTo(3); // ceil(120 / 50)
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_passesTypeAsEnumName_toRepository() {
|
||||
PersonFilter filter = PersonFilter.builder().type(PersonType.INSTITUTION).build();
|
||||
when(personRepository.countByFilter("INSTITUTION", null, null, null, false, null)).thenReturn(0L);
|
||||
when(personRepository.findByFilter("INSTITUTION", null, null, null, false, null, 50, 0))
|
||||
.thenReturn(List.of());
|
||||
|
||||
personService.search(filter, 0, 50, null);
|
||||
|
||||
verify(personRepository).findByFilter("INSTITUTION", null, null, null, false, null, 50, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_computesOffset_fromPageAndSize() {
|
||||
PersonFilter filter = PersonFilter.showAll();
|
||||
when(personRepository.countByFilter(null, null, null, null, false, null)).thenReturn(0L);
|
||||
when(personRepository.findByFilter(null, null, null, null, false, null, 20, 40))
|
||||
.thenReturn(List.of());
|
||||
|
||||
personService.search(filter, 2, 20, null); // offset = page * size = 40
|
||||
|
||||
verify(personRepository).findByFilter(null, null, null, null, false, null, 20, 40);
|
||||
}
|
||||
|
||||
@Test
|
||||
void search_trimsBlankQueryToNull() {
|
||||
PersonFilter filter = PersonFilter.showAll();
|
||||
when(personRepository.countByFilter(null, null, null, null, false, null)).thenReturn(0L);
|
||||
when(personRepository.findByFilter(null, null, null, null, false, null, 50, 0))
|
||||
.thenReturn(List.of());
|
||||
|
||||
personService.search(filter, 0, 50, " ");
|
||||
|
||||
verify(personRepository).findByFilter(null, null, null, null, false, null, 50, 0);
|
||||
}
|
||||
|
||||
// ─── #667: confirmPerson ──────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void confirmPerson_clearsProvisionalFlag() {
|
||||
UUID id = UUID.randomUUID();
|
||||
Person provisional = Person.builder().id(id).firstName("Inferred").lastName("Person").provisional(true).build();
|
||||
when(personRepository.findById(id)).thenReturn(Optional.of(provisional));
|
||||
when(personRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
Person result = personService.confirmPerson(id);
|
||||
|
||||
assertThat(result.isProvisional()).isFalse();
|
||||
verify(personRepository).save(argThat(p -> !p.isProvisional()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmPerson_throwsNotFound_whenMissing() {
|
||||
UUID id = UUID.randomUUID();
|
||||
when(personRepository.findById(id)).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> personService.confirmPerson(id))
|
||||
.isInstanceOf(DomainException.class)
|
||||
.extracting(e -> ((DomainException) e).getStatus().value())
|
||||
.isEqualTo(404);
|
||||
}
|
||||
|
||||
// ─── #667: deletePerson ───────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void deletePerson_deletes_whenPersonExists() {
|
||||
UUID id = UUID.randomUUID();
|
||||
Person person = Person.builder().id(id).firstName("Weg").lastName("Person").build();
|
||||
when(personRepository.findById(id)).thenReturn(Optional.of(person));
|
||||
|
||||
personService.deletePerson(id);
|
||||
|
||||
verify(personRepository).reassignSenderToNull(id);
|
||||
verify(personRepository).deleteReceiverReferences(id);
|
||||
verify(personRepository).deleteById(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletePerson_throwsNotFound_whenMissing() {
|
||||
UUID id = UUID.randomUUID();
|
||||
when(personRepository.findById(id)).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> personService.deletePerson(id))
|
||||
.isInstanceOf(DomainException.class)
|
||||
.extracting(e -> ((DomainException) e).getStatus().value())
|
||||
.isEqualTo(404);
|
||||
}
|
||||
|
||||
// ─── createPerson ─────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user