diff --git a/csv-table-hub-main/src/__tests__/log-step-contract.test.ts b/csv-table-hub-main/src/__tests__/log-step-contract.test.ts new file mode 100644 index 0000000..669532b --- /dev/null +++ b/csv-table-hub-main/src/__tests__/log-step-contract.test.ts @@ -0,0 +1,41 @@ +// The upload response is validated against a schema. A log step the API emits but the schema does not +// list makes zod reject the WHOLE response, so an import that succeeded on the server is reported as +// "Failed: Unexpected response shape" (what #186 did when it added the header_mapping step). + +import { readdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { processingLogSchema } from "@/lib/csv.functions"; + +const API = join(__dirname, "../../../api"); + +/** Every step name the loaders pass to _log(logs, "", ...). */ +function stepsTheApiEmits(): Set { + const steps = new Set(); + for (const dir of ["services", "routers"]) { + for (const file of readdirSync(join(API, dir)).filter((f) => f.endsWith(".py"))) { + const source = readFileSync(join(API, dir, file), "utf8"); + for (const m of source.matchAll(/_log\(\s*logs,\s*"([a-z_]+)"/g)) steps.add(m[1]); + } + } + return steps; +} + +describe("processing log steps", () => { + it("finds the steps the API emits (guards the scan itself)", () => { + const steps = stepsTheApiEmits(); + expect(steps.size).toBeGreaterThan(8); + expect(steps.has("receive")).toBe(true); + }); + + it("the schema accepts every step the API can emit", () => { + const known = new Set(processingLogSchema.shape.step.options); + const unknown = [...stepsTheApiEmits()].filter((s) => !known.has(s)); + expect(unknown).toEqual([]); + }); + + it("accepts the header_mapping entry a dynamic upload with a header mapping returns", () => { + const entry = { ts: 1, step: "header_mapping", level: "info", message: "Applied 2 header renames" }; + expect(processingLogSchema.safeParse(entry).success).toBe(true); + }); +}); diff --git a/csv-table-hub-main/src/lib/csv.functions.ts b/csv-table-hub-main/src/lib/csv.functions.ts index bd07654..6b65bec 100644 --- a/csv-table-hub-main/src/lib/csv.functions.ts +++ b/csv-table-hub-main/src/lib/csv.functions.ts @@ -13,13 +13,14 @@ const rowErrorSchema = z.object({ }); export type RowError = z.infer; -const processingLogSchema = z.object({ +export const processingLogSchema = z.object({ ts: z.number(), step: z.enum([ "receive", "hash", "duplicate_check", "parse", + "header_mapping", "validate_columns", "create_table", "cast_rows",