inline-schema/src/csv-loader/tests/parseCsv-reverseRefs.test.ts

270 lines
7.5 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { parseCsv } from "../loader";
import * as path from "path";
import { fixturesDir } from "../test-utils";
import fs from "fs";
describe("parseCsv - reverse reference resolution", () => {
it("should resolve reverse reference from comment declaration", () => {
const ordersCsvPath = path.join(fixturesDir, "rev_orders.csv");
const ordersContent = [
"id,customer,total",
"string,string,number",
"1,1,100",
"2,1,50",
"3,2,75",
].join("\n");
fs.writeFileSync(ordersCsvPath, ordersContent);
try {
const csv = [
"id,name",
"string,string",
"# orders := ~rev_orders(customer)",
"1,Alice",
"2,Bob",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
expect(result.data).toHaveLength(2);
const aliceOrders = result.data[0].orders as Record<string, unknown>[];
expect(aliceOrders).toHaveLength(2);
expect(aliceOrders[0]).toEqual({ id: "1", customer: "1", total: 100 });
expect(aliceOrders[1]).toEqual({ id: "2", customer: "1", total: 50 });
const bobOrders = result.data[1].orders as Record<string, unknown>[];
expect(bobOrders).toHaveLength(1);
expect(bobOrders[0].id).toBe("3");
} finally {
fs.unlinkSync(ordersCsvPath);
}
});
it("should return empty array for reverse reference with no matches", () => {
const ordersCsvPath = path.join(fixturesDir, "rev_orders.csv");
const ordersContent = [
"id,customer,total",
"string,string,number",
"1,1,100",
"2,1,50",
].join("\n");
fs.writeFileSync(ordersCsvPath, ordersContent);
try {
const csv = [
"id,name",
"string,string",
"# orders := ~rev_orders(customer)",
"1,Alice",
"99,Nobody",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
expect(result.data).toHaveLength(2);
expect(result.data[0].orders).toHaveLength(2);
expect(result.data[1].orders).toEqual([]);
} finally {
fs.unlinkSync(ordersCsvPath);
}
});
it("should return null for optional reverse reference with no matches", () => {
const csv = [
"id,name",
"string,string",
"# orders := ~orders(customer)?",
"99,Nobody",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
expect(result.data).toHaveLength(1);
expect(result.data[0].orders).toBeNull();
});
it("should populate reverseReferences in result", () => {
const csv = [
"id,name",
"string,string",
"# orders := ~orders(customer)",
"1,Alice",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
expect(result.reverseReferences).toHaveLength(1);
expect(result.reverseReferences[0]).toEqual({
fieldName: "orders",
tableName: "orders",
foreignKey: "customer",
isOptional: false,
schema: {
type: "reverseReference",
tableName: "orders",
foreignKey: "customer",
isOptional: false,
},
});
});
it("should include reverse reference tables in references set", () => {
const csv = [
"id,name",
"string,string",
"# orders := ~orders(customer)",
"1,Alice",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
expect(result.references.has("orders")).toBe(true);
});
it("should support multiple reverse reference declarations", () => {
const csv = [
"id,name",
"string,string",
"# orders := ~orders(customer)",
"# parts := ~parts(user)",
"1,Alice",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
expect(result.reverseReferences).toHaveLength(2);
expect(result.reverseReferences[0].fieldName).toBe("orders");
expect(result.reverseReferences[1].fieldName).toBe("parts");
expect(result.data[0]).toHaveProperty("orders");
expect(result.data[0]).toHaveProperty("parts");
});
it("should ignore comment lines that are not reverse reference declarations", () => {
const csv = [
"id,name",
"string,string",
"# This is just a comment",
"# orders := ~orders(customer)",
"1,Alice",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
expect(result.reverseReferences).toHaveLength(1);
expect(result.data[0]).toHaveProperty("orders");
});
it("should handle multiple comment lines including plain comments and reverse references", () => {
const orderCsvPath = path.join(fixturesDir, "order.csv");
const orderContent = [
"id,user,total",
"string,string,number",
"o01,u01,100",
"o02,u01,50",
"o03,u02,75",
].join("\n");
fs.writeFileSync(orderCsvPath, orderContent);
try {
const csv = [
"# id: id of user",
"# orders: list of related orders",
"# orders := ~order(user)",
"id,name",
"string,string",
"u01,Alice",
"u02,Bob",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
currentFilePath: path.join(fixturesDir, "test.csv"),
});
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");
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 });
const bobOrders = result.data[1].orders as Record<string, unknown>[];
expect(bobOrders).toHaveLength(1);
expect(bobOrders[0].id).toBe("o03");
} finally {
fs.unlinkSync(orderCsvPath);
}
});
});
describe("parseCsv - reverse reference with resolveReferences: false", () => {
it("should populate referenceFields for reverse references", () => {
const csv = [
"id,name",
"string,string",
"# orders := ~orders(customer)",
"1,Alice",
].join("\n");
const result = parseCsv(csv, {
emitTypes: false,
resolveReferences: false,
});
const withForeignKey = result.referenceFields.filter(
(f) => f.foreignKey === "customer",
);
expect(withForeignKey).toHaveLength(1);
expect(withForeignKey[0]).toEqual({
name: "orders",
tableName: "orders",
isArray: true,
foreignKey: "customer",
schema: expect.objectContaining({
type: "reverseReference",
tableName: "orders",
foreignKey: "customer",
}),
});
});
it("should not load referenced CSV files for reverse references", () => {
const csv = [
"id,name",
"string,string",
"# nonexistent := ~nonexistent(some_key)",
"1,Alice",
].join("\n");
expect(() =>
parseCsv(csv, {
emitTypes: false,
resolveReferences: false,
}),
).not.toThrow();
});
});