test(importing): pin relationship error propagation and short-row reads
Add a negative test that an unexpected DomainException from addRelationshipIdempotently propagates rather than being swallowed (only DUPLICATE/CIRCULAR are caught for idempotency), guarding against a future swallow-all refactor. Add a CanonicalSheetReader test for a row narrower than the header (POI omits trailing empty cells) reading absent columns as "". Refs #669 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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<CanonicalSheetReader.Row> 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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user