test: improve reverse reference resolution handling

This commit is contained in:
hypercross 2026-04-19 14:19:45 +08:00
parent be8eb277d4
commit 5a1627c6f1
2 changed files with 59 additions and 36 deletions

View File

@ -1166,26 +1166,49 @@ describe("parseCsv - reverse reference resolution", () => {
}); });
it("should handle multiple comment lines including plain comments and reverse references", () => { it("should handle multiple comment lines including plain comments and reverse references", () => {
const csv = [ const orderCsvPath = path.join(fixturesDir, "order.csv");
"# id: id of user", const orderContent = [
"# orders: list of related orders", "id,user,total",
"# orders := ~order(user)", "string,string,number",
"id,name", "o01,u01,100",
"string,string", "o02,u01,50",
"u01,Alice", "o03,u02,75",
"u02,Bob",
].join("\n"); ].join("\n");
fs.writeFileSync(orderCsvPath, orderContent);
const result = parseCsv(csv, { try {
emitTypes: false, const csv = [
currentFilePath: path.join(fixturesDir, "test.csv"), "# id: id of user",
}); "# orders: list of related orders",
"# orders := ~order(user)",
"id,name",
"string,string",
"u01,Alice",
"u02,Bob",
].join("\n");
expect(result.reverseReferences).toHaveLength(1); const result = parseCsv(csv, {
expect(result.reverseReferences[0].fieldName).toBe("orders"); emitTypes: false,
expect(result.data).toHaveLength(2); currentFilePath: path.join(fixturesDir, "test.csv"),
expect(result.data[0].id).toBe("u01"); });
expect(result.data[1].id).toBe("u02");
expect(result.reverseReferences).toHaveLength(1);
expect(result.reverseReferences[0].fieldName).toBe("orders");
expect(result.data).toHaveLength(2);
expect(result.data[0].id).toBe("u01");
expect(result.data[1].id).toBe("u02");
// User u01 (Alice) should have 2 orders
const aliceOrders = result.data[0].orders as Record<string, unknown>[];
expect(aliceOrders).toHaveLength(2);
expect(aliceOrders[0]).toEqual({ id: "o01", user: "u01", total: 100 });
expect(aliceOrders[1]).toEqual({ id: "o02", user: "u01", total: 50 });
// User u02 (Bob) should have 1 order
const bobOrders = result.data[1].orders as Record<string, unknown>[];
expect(bobOrders).toHaveLength(1);
expect(bobOrders[0].id).toBe("o03");
} finally {
fs.unlinkSync(orderCsvPath);
}
}); });
}); });

View File

@ -802,25 +802,10 @@ export function parseCsv(
relax_column_count: true, relax_column_count: true,
}); });
if (records.length < 2) { // Filter out comment lines from all records, collecting reverse reference declarations
throw new Error("CSV must have at least 2 rows: headers and schemas");
}
const headers = records[0];
const schemas = records[1];
if (headers.length !== schemas.length) {
throw new Error(
`Header count (${headers.length}) does not match schema count (${schemas.length})`,
);
}
// Parse reverse reference declarations from comment lines between schema row and data rows
const reverseReferences: ReverseReferenceDeclaration[] = []; const reverseReferences: ReverseReferenceDeclaration[] = [];
const dataRows: string[][] = []; const filteredRecords: string[][] = [];
for (let i = 2; i < records.length; i++) { for (const row of records) {
const row = records[i];
// Check if this is a comment line (starting with the configured comment character)
const firstCell = (row[0] ?? "").trim(); const firstCell = (row[0] ?? "").trim();
if (comment && firstCell.startsWith(comment)) { if (comment && firstCell.startsWith(comment)) {
const decl = parseReverseReferenceDeclaration(firstCell, comment); const decl = parseReverseReferenceDeclaration(firstCell, comment);
@ -830,9 +815,24 @@ export function parseCsv(
// Skip comment lines (whether or not they're reverse ref declarations) // Skip comment lines (whether or not they're reverse ref declarations)
continue; continue;
} }
dataRows.push(row); filteredRecords.push(row);
} }
if (filteredRecords.length < 2) {
throw new Error("CSV must have at least 2 rows: headers and schemas");
}
const headers = filteredRecords[0];
const schemas = filteredRecords[1];
if (headers.length !== schemas.length) {
throw new Error(
`Header count (${headers.length}) does not match schema count (${schemas.length})`,
);
}
const dataRows = filteredRecords.slice(2);
// Also check schema row cells for comment-prefixed reverse reference declarations // Also check schema row cells for comment-prefixed reverse reference declarations
// (in case they appear as schema cells rather than separate rows) // (in case they appear as schema cells rather than separate rows)
for (let col = 0; col < schemas.length; col++) { for (let col = 0; col < schemas.length; col++) {