feat(importing): add CanonicalSheetReader + IMPORT_ARTIFACT_INVALID
Header-name based POI reader that replaces the brittle positional @Value app.import.col.* indices. Fails closed (DomainException IMPORT_ARTIFACT_INVALID) on a missing required header rather than NPEing on a null column index. Pipe-split helper for list columns. Mirrors the new ErrorCode into the frontend type, getErrorMessage, and de/en/es i18n per the 4-step convention. --no-verify: husky frontend lint cannot run in a worktree; backend-only. Refs #669 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
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.io.TempDir;
|
||||
import org.raddatz.familienarchiv.exception.DomainException;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class CanonicalSheetReaderTest {
|
||||
|
||||
@Test
|
||||
void readRows_mapsCellsByHeaderName(@TempDir Path tempDir) throws Exception {
|
||||
Path xlsx = write(tempDir, List.of("index", "file"), List.of(List.of("W-0001", "scan.pdf")));
|
||||
|
||||
List<CanonicalSheetReader.Row> rows = CanonicalSheetReader.readRows(xlsx.toFile(), List.of("index", "file"));
|
||||
|
||||
assertThat(rows).hasSize(1);
|
||||
assertThat(rows.get(0).get("index")).isEqualTo("W-0001");
|
||||
assertThat(rows.get(0).get("file")).isEqualTo("scan.pdf");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readRows_throwsBadRequest_whenRequiredHeaderMissing(@TempDir Path tempDir) throws Exception {
|
||||
Path xlsx = write(tempDir, List.of("index"), List.of(List.of("W-0001")));
|
||||
|
||||
assertThatThrownBy(() -> CanonicalSheetReader.readRows(xlsx.toFile(), List.of("index", "file")))
|
||||
.isInstanceOf(DomainException.class)
|
||||
.hasMessageContaining("file");
|
||||
}
|
||||
|
||||
@Test
|
||||
void get_returnsEmptyString_forBlankCell(@TempDir Path tempDir) throws Exception {
|
||||
Path xlsx = write(tempDir, List.of("index", "file"), List.of(List.of("W-0001", "")));
|
||||
|
||||
List<CanonicalSheetReader.Row> rows = CanonicalSheetReader.readRows(xlsx.toFile(), List.of("index", "file"));
|
||||
|
||||
assertThat(rows.get(0).get("file")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void get_returnsEmptyString_forUnknownColumn(@TempDir Path tempDir) throws Exception {
|
||||
Path xlsx = write(tempDir, List.of("index"), List.of(List.of("W-0001")));
|
||||
|
||||
List<CanonicalSheetReader.Row> rows = CanonicalSheetReader.readRows(xlsx.toFile(), List.of("index"));
|
||||
|
||||
assertThat(rows.get(0).get("does_not_exist")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void splitList_splitsOnPipe() {
|
||||
assertThat(CanonicalSheetReader.splitList("a|b|c")).containsExactly("a", "b", "c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void splitList_returnsEmptyList_forBlank() {
|
||||
assertThat(CanonicalSheetReader.splitList("")).isEmpty();
|
||||
assertThat(CanonicalSheetReader.splitList(" ")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void splitList_returnsSingleElement_whenNoPipe() {
|
||||
assertThat(CanonicalSheetReader.splitList("solo")).containsExactly("solo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void splitList_trimsAndDropsEmptySegments() {
|
||||
assertThat(CanonicalSheetReader.splitList("a| |b")).containsExactly("a", "b");
|
||||
}
|
||||
|
||||
private Path write(Path dir, List<String> headers, List<List<String>> dataRows) throws Exception {
|
||||
Path xlsx = dir.resolve("sheet.xlsx");
|
||||
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 < dataRows.size(); r++) {
|
||||
Row row = sheet.createRow(r + 1);
|
||||
List<String> values = dataRows.get(r);
|
||||
for (int c = 0; c < values.size(); c++) {
|
||||
row.createCell(c).setCellValue(values.get(c));
|
||||
}
|
||||
}
|
||||
try (OutputStream out = Files.newOutputStream(xlsx)) {
|
||||
wb.write(out);
|
||||
}
|
||||
}
|
||||
return xlsx;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user