diff --git a/backend/src/test/java/org/raddatz/familienarchiv/importing/CanonicalSheetReaderTest.java b/backend/src/test/java/org/raddatz/familienarchiv/importing/CanonicalSheetReaderTest.java index 7c4d9d58..ee1d3650 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/importing/CanonicalSheetReaderTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/importing/CanonicalSheetReaderTest.java @@ -55,6 +55,21 @@ class CanonicalSheetReaderTest { assertThat(rows.get(0).get("does_not_exist")).isEmpty(); } + @Test + void get_returnsEmptyString_forTrailingColumns_whenRowShorterThanHeader(@TempDir Path tempDir) throws Exception { + // POI omits trailing empty cells, so a real-world artifact row can be narrower than + // the header. The missing columns must read as "" rather than throwing. + Path xlsx = write(tempDir, + List.of("index", "file", "summary"), + List.of(List.of("W-0001"))); + + List rows = CanonicalSheetReader.readRows(xlsx.toFile(), List.of("index", "file", "summary")); + + assertThat(rows.get(0).get("index")).isEqualTo("W-0001"); + assertThat(rows.get(0).get("file")).isEmpty(); + assertThat(rows.get(0).get("summary")).isEmpty(); + } + @Test void splitList_splitsOnPipe() { assertThat(CanonicalSheetReader.splitList("a|b|c")).containsExactly("a", "b", "c"); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/importing/PersonTreeImporterTest.java b/backend/src/test/java/org/raddatz/familienarchiv/importing/PersonTreeImporterTest.java index fbd06ba8..ce90d260 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/importing/PersonTreeImporterTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/importing/PersonTreeImporterTest.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; @@ -106,6 +107,31 @@ class PersonTreeImporterTest { verify(relationshipService).addRelationship(any(), any()); } + @Test + void load_propagatesUnexpectedDomainException_fromAddRelationship(@TempDir Path tempDir) throws Exception { + PersonService personService = mock(PersonService.class); + RelationshipService relationshipService = mock(RelationshipService.class); + when(personService.upsertBySourceRef(any())) + .thenAnswer(inv -> personOf(inv.getArgument(0))); + // An unexpected ErrorCode (not DUPLICATE/CIRCULAR) must NOT be swallowed. + doThrow(DomainException.internal(ErrorCode.INTERNAL_ERROR, "boom")) + .when(relationshipService).addRelationship(any(), any()); + Path json = write(tempDir, """ + {"persons":[ + {"rowId":"row_a","lastName":"A","familyMember":true,"personId":"a"}, + {"rowId":"row_b","lastName":"B","familyMember":true,"personId":"b"} + ],"relationships":[ + {"personId":"row_a","relatedPersonId":"row_b","type":"SPOUSE_OF","source":"verheiratet_mit"} + ]} + """); + + PersonTreeImporter importer = new PersonTreeImporter(personService, relationshipService); + + assertThatThrownBy(() -> importer.load(json.toFile())) + .isInstanceOf(DomainException.class) + .extracting("code").isEqualTo(ErrorCode.INTERNAL_ERROR); + } + @Test void load_skipsRelationship_whenRowIdUnresolved(@TempDir Path tempDir) throws Exception { PersonService personService = mock(PersonService.class);