feat(importing): add PersonRegisterImporter loader
Second canonical loader. Reads canonical-persons.xlsx by header name and upserts each register person via PersonService.upsertBySourceRef keyed on the normalizer person_id. provisional is driven by the sheet's clean value; Boolean.parseBoolean handles the capitalised Python "True"/"False". ISO birth/death dates are reduced to the year the Person entity stores. Refs #669 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package org.raddatz.familienarchiv.importing;
|
||||
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.raddatz.familienarchiv.person.Person;
|
||||
import org.raddatz.familienarchiv.person.PersonService;
|
||||
import org.raddatz.familienarchiv.person.PersonUpsertCommand;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PersonRegisterImporterTest {
|
||||
|
||||
@Test
|
||||
void load_upsertsPersonBySourceRef_withProvisionalFalse(@TempDir Path tempDir) throws Exception {
|
||||
PersonService personService = mock(PersonService.class);
|
||||
when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
|
||||
Path xlsx = writePersons(tempDir, row(
|
||||
"allemeyer-elsgard", "Allemeyer", "Elsgard", "Wöhler", "Nichte von Herbert", "False"));
|
||||
|
||||
new PersonRegisterImporter(personService).load(xlsx.toFile());
|
||||
|
||||
ArgumentCaptor<PersonUpsertCommand> captor = ArgumentCaptor.forClass(PersonUpsertCommand.class);
|
||||
verify(personService).upsertBySourceRef(captor.capture());
|
||||
PersonUpsertCommand cmd = captor.getValue();
|
||||
assertThat(cmd.sourceRef()).isEqualTo("allemeyer-elsgard");
|
||||
assertThat(cmd.lastName()).isEqualTo("Allemeyer");
|
||||
assertThat(cmd.firstName()).isEqualTo("Elsgard");
|
||||
assertThat(cmd.maidenName()).isEqualTo("Wöhler");
|
||||
assertThat(cmd.notes()).isEqualTo("Nichte von Herbert");
|
||||
assertThat(cmd.provisional()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void load_parsesCapitalisedPythonBool_True(@TempDir Path tempDir) throws Exception {
|
||||
PersonService personService = mock(PersonService.class);
|
||||
when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
|
||||
Path xlsx = writePersons(tempDir, row(
|
||||
"noise-geschirr", "Geschirr", "", "", "", "True"));
|
||||
|
||||
new PersonRegisterImporter(personService).load(xlsx.toFile());
|
||||
|
||||
ArgumentCaptor<PersonUpsertCommand> captor = ArgumentCaptor.forClass(PersonUpsertCommand.class);
|
||||
verify(personService).upsertBySourceRef(captor.capture());
|
||||
assertThat(captor.getValue().provisional()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void load_skipsRowWithBlankPersonId(@TempDir Path tempDir) throws Exception {
|
||||
PersonService personService = mock(PersonService.class);
|
||||
Path xlsx = writePersons(tempDir, row("", "NoId", "", "", "", "False"));
|
||||
|
||||
new PersonRegisterImporter(personService).load(xlsx.toFile());
|
||||
|
||||
verify(personService, times(0)).upsertBySourceRef(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void load_returnsCountOfProcessedRows(@TempDir Path tempDir) throws Exception {
|
||||
PersonService personService = mock(PersonService.class);
|
||||
when(personService.upsertBySourceRef(any())).thenAnswer(inv -> personOf(inv.getArgument(0)));
|
||||
Path xlsx = writePersons(tempDir,
|
||||
row("a-one", "One", "A", "", "", "False"),
|
||||
row("a-two", "Two", "B", "", "", "False"));
|
||||
|
||||
int processed = new PersonRegisterImporter(personService).load(xlsx.toFile());
|
||||
|
||||
assertThat(processed).isEqualTo(2);
|
||||
}
|
||||
|
||||
private static Person personOf(PersonUpsertCommand cmd) {
|
||||
return Person.builder().id(UUID.randomUUID()).sourceRef(cmd.sourceRef())
|
||||
.firstName(cmd.firstName()).lastName(cmd.lastName())
|
||||
.provisional(cmd.provisional()).build();
|
||||
}
|
||||
|
||||
private Map<String, String> row(String personId, String lastName, String firstName,
|
||||
String maidenName, String notes, String provisional) {
|
||||
Map<String, String> r = new LinkedHashMap<>();
|
||||
r.put("person_id", personId);
|
||||
r.put("last_name", lastName);
|
||||
r.put("first_name", firstName);
|
||||
r.put("maiden_name", maidenName);
|
||||
r.put("notes", notes);
|
||||
r.put("provisional", provisional);
|
||||
return r;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private Path writePersons(Path dir, Map<String, String>... rows) throws Exception {
|
||||
Path xlsx = dir.resolve("canonical-persons.xlsx");
|
||||
List<String> headers = List.of("person_id", "last_name", "first_name", "maiden_name", "notes", "provisional");
|
||||
try (XSSFWorkbook wb = new XSSFWorkbook()) {
|
||||
Sheet sheet = wb.createSheet("Sheet1");
|
||||
Row header = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
header.createCell(i).setCellValue(headers.get(i));
|
||||
}
|
||||
for (int r = 0; r < rows.length; r++) {
|
||||
Row row = sheet.createRow(r + 1);
|
||||
for (int c = 0; c < headers.size(); c++) {
|
||||
row.createCell(c).setCellValue(rows[r].getOrDefault(headers.get(c), ""));
|
||||
}
|
||||
}
|
||||
try (OutputStream out = Files.newOutputStream(xlsx)) {
|
||||
wb.write(out);
|
||||
}
|
||||
}
|
||||
return xlsx;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user