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[]; 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[]; 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[]; 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[]; 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(); }); });