147 lines
4.4 KiB
TypeScript
147 lines
4.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import {
|
|
ExternalCsvParseError,
|
|
parseExternalCsv,
|
|
serializeExternalCsv,
|
|
} from "../src/external-model/csv/external-csv-transport.js";
|
|
import {
|
|
externalCsvTestConfiguration,
|
|
firstIfcGuid,
|
|
quotedRevitCsv,
|
|
utf8Bytes,
|
|
} from "./fixtures/revit-csv-fixtures.js";
|
|
|
|
describe("external CSV transport", () => {
|
|
it("detects the observed Revit dialect and classifies every row", () => {
|
|
const document = parseExternalCsv(
|
|
utf8Bytes(quotedRevitCsv, true),
|
|
externalCsvTestConfiguration
|
|
);
|
|
|
|
assert.deepEqual(document.dialect, {
|
|
encoding: "utf-8",
|
|
hasBom: true,
|
|
delimiter: ";",
|
|
lineEnding: "\r\n",
|
|
quoteCharacter: '"',
|
|
quoteAllFields: true,
|
|
hasTrailingLineEnding: true,
|
|
});
|
|
assert.equal(document.headerRowIndex, 1);
|
|
assert.deepEqual(
|
|
document.rows.map((row) => row.classification),
|
|
[
|
|
"metadata",
|
|
"header",
|
|
"passthrough",
|
|
"object",
|
|
"passthrough",
|
|
"suspect-object",
|
|
"object",
|
|
]
|
|
);
|
|
assert.equal(document.rows[6].cells[2].value, 'Büro "Nord"');
|
|
});
|
|
|
|
it("round-trips BOM, CRLF, quotation, order and cell values byte for byte", () => {
|
|
const source = utf8Bytes(quotedRevitCsv, true);
|
|
const document = parseExternalCsv(source, externalCsvTestConfiguration);
|
|
|
|
assert.deepEqual(serializeExternalCsv(document), source);
|
|
});
|
|
|
|
it("parses exports without an optional quantity column", () => {
|
|
const configuration = {
|
|
...externalCsvTestConfiguration,
|
|
columns: {
|
|
...externalCsvTestConfiguration.columns,
|
|
quantity: null,
|
|
},
|
|
};
|
|
const csv = [
|
|
'"Projekt-Export";"";"";"";"";"";""',
|
|
'"Stromkreis";"Raumnummer";"Raumname";"Familie und Typ";"CAx_Auswahlkenner";"Elektrische Leistung";"IfcGUID"',
|
|
`"UV_AV_01-2F1";"01/101";"Technik";"Steckdose: Standard";"Arbeitsplatz";"100";"${firstIfcGuid}"`,
|
|
].join("\r\n") + "\r\n";
|
|
|
|
const document = parseExternalCsv(utf8Bytes(csv, true), configuration);
|
|
|
|
assert.equal(
|
|
document.rows.filter((row) => row.classification === "object").length,
|
|
1
|
|
);
|
|
});
|
|
|
|
it("preserves embedded delimiters and line endings inside quoted cells", () => {
|
|
const csv = quotedRevitCsv.replace(
|
|
'"Steckdose: Doppelsteckdose"',
|
|
'"Steckdose; Doppel\r\nsteckdose"'
|
|
);
|
|
const source = utf8Bytes(csv, true);
|
|
const document = parseExternalCsv(source, externalCsvTestConfiguration);
|
|
|
|
assert.equal(
|
|
document.rows[3].cells[3].value,
|
|
"Steckdose; Doppel\r\nsteckdose"
|
|
);
|
|
assert.deepEqual(serializeExternalCsv(document), source);
|
|
});
|
|
|
|
it("blocks missing and ambiguous configured headers", () => {
|
|
assertParseError(
|
|
utf8Bytes(quotedRevitCsv.replace("IfcGUID", "Andere ID")),
|
|
"header-not-found"
|
|
);
|
|
const duplicatedHeader = quotedRevitCsv.replace(
|
|
'"";"";"";"";"";"";"";""',
|
|
quotedRevitCsv.split("\r\n")[1]
|
|
);
|
|
assertParseError(utf8Bytes(duplicatedHeader), "ambiguous-header");
|
|
|
|
const duplicateConfiguredColumn = quotedRevitCsv.replace(
|
|
'"Stromkreis";"Raumnummer"',
|
|
'"Stromkreis";"Raumnummer";"Raumnummer"'
|
|
);
|
|
assertParseError(
|
|
utf8Bytes(duplicateConfiguredColumn),
|
|
"ambiguous-header"
|
|
);
|
|
});
|
|
|
|
it("blocks invalid and duplicate non-empty IfcGUIDs", () => {
|
|
assertParseError(
|
|
utf8Bytes(quotedRevitCsv.replace(firstIfcGuid, "not-an-ifc-guid")),
|
|
"invalid-ifc-guid"
|
|
);
|
|
assertParseError(
|
|
utf8Bytes(quotedRevitCsv.replace(firstIfcGuid, ` ${firstIfcGuid}`)),
|
|
"invalid-ifc-guid"
|
|
);
|
|
const duplicate = quotedRevitCsv.replace(
|
|
"1AbCdEfGhIjKlMnOpQrStu",
|
|
firstIfcGuid
|
|
);
|
|
assertParseError(utf8Bytes(duplicate), "duplicate-ifc-guid");
|
|
});
|
|
|
|
it("blocks invalid UTF-8, unterminated quotes and mixed row endings", () => {
|
|
assertParseError(new Uint8Array([0xff, 0xfe, 0xfd]), "invalid-encoding");
|
|
assertParseError(
|
|
utf8Bytes(quotedRevitCsv.replace(/"\r\n$/, "\r\n")),
|
|
"invalid-csv"
|
|
);
|
|
assertParseError(
|
|
utf8Bytes(quotedRevitCsv.replace("\r\n", "\n")),
|
|
"invalid-csv"
|
|
);
|
|
});
|
|
});
|
|
|
|
function assertParseError(input: Uint8Array, code: ExternalCsvParseError["code"]) {
|
|
assert.throws(
|
|
() => parseExternalCsv(input, externalCsvTestConfiguration),
|
|
(error: unknown) => error instanceof ExternalCsvParseError && error.code === code
|
|
);
|
|
}
|