import { describe, expect, it } from "vitest"; import { generateFixtures, modelJson, parseFixtureModel, serializeDataset, type GeneratedDataset, } from "../../src/fixture/model"; const SQL = `CREATE TABLE users ( id INTEGER PRIMARY KEY, email VARCHAR(80) NOT NULL UNIQUE, score DECIMAL(5,2) ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) );`; describe("fixture model", () => { it("imports focused SQL constraints and preserves foreign keys", () => { const parsed = parseFixtureModel(SQL, "sql"); expect(parsed.model.tables).toHaveLength(2); expect(parsed.model.tables[0]?.fields[0]).toMatchObject({ name: "id", type: "integer", required: true, unique: true, distribution: "sequential", }); expect(parsed.model.tables[0]?.fields[1]).toMatchObject({ name: "email", type: "email", unique: true, maxLength: 80, }); expect(parsed.model.tables[1]?.fields[1]).toMatchObject({ type: "foreignKey", reference: "users.id", }); }); it("generates replayable records with referential integrity", () => { const model = parseFixtureModel(SQL, "sql").model; const options = { seed: "repeat-me", rows: 20, boundaryPercent: 15, invalidPercent: 0, }; const first = generateFixtures(model, options); const second = generateFixtures(model, options); expect(first).toEqual(second); expect( generateFixtures(model, { ...options, seed: "different" }), ).not.toEqual(first); const ids = new Set(first.tables.users?.map((row) => row.id)); expect(first.tables.orders?.every((row) => ids.has(row.user_id))).toBe( true, ); expect(new Set(first.tables.users?.map((row) => row.email)).size).toBe(20); }); it("imports JSON Schema bounds and round-trips the editable model", () => { const parsed = parseFixtureModel( JSON.stringify({ title: "People", type: "object", required: ["age"], properties: { age: { type: "integer", minimum: 18, maximum: 90 }, state: { enum: ["draft", "active"] }, nickname: { type: "string", minLength: 2, maxLength: 8 }, }, }), "json-schema", ); expect(parsed.model.tables[0]).toMatchObject({ name: "People" }); expect(parsed.model.tables[0]?.fields[0]).toMatchObject({ minimum: 18, maximum: 90, required: true, }); expect( parseFixtureModel(modelJson(parsed.model), "fixture-model").model, ).toEqual(parsed.model); }); it("records every intentional mutation and emits common formats", () => { const model = parseFixtureModel("id,email\n", "csv").model; const dataset = generateFixtures(model, { seed: "invalid", rows: 3, boundaryPercent: 0, invalidPercent: 100, }); expect(dataset.violations).toHaveLength(6); expect(serializeDataset(dataset, "json")).toMatch(/^\[/u); expect(serializeDataset(dataset, "ndjson").trim().split("\n")).toHaveLength( 3, ); expect(serializeDataset(dataset, "xml")).toContain(" { const dataset: GeneratedDataset = { seed: "safe", rowsPerTable: 1, tables: { records: [{ value: '=HYPERLINK("https://invalid")', quote: "O'Brien" }], }, violations: [], }; expect(serializeDataset(dataset, "csv")).toContain("'=HYPERLINK"); expect(serializeDataset(dataset, "sql")).toContain("O''Brien"); }); it("imports bounded XSD and rejects active document declarations", () => { const xsd = ``; const parsed = parseFixtureModel(xsd, "xsd"); expect(parsed.model.tables[0]?.fields).toMatchObject([ { name: "age", type: "integer", required: true }, { name: "email", required: false }, ]); expect(() => parseFixtureModel(']>', "xsd"), ).toThrow(/DOCTYPE/u); }); it("rejects misleading recipes and caps wide generations", () => { expect(() => parseFixtureModel( JSON.stringify({ tables: [ { name: "records", fields: [ { name: "id", type: "integer", required: true, unique: true, distribution: "constant", constant: "wrong type", }, ], }, ], }), "fixture-model", ), ).toThrow(/constant/u); const wide = { tables: [ { name: "wide", fields: Array.from({ length: 100 }, (_unused, index) => ({ name: `field_${index}`, type: "string" as const, required: true, unique: false, distribution: "sequential" as const, })), }, ], }; expect(() => generateFixtures(wide, { seed: "bounded", rows: 1_001, boundaryPercent: 0, invalidPercent: 0, }), ).toThrow(/100,000 field values/u); }); });