import { describe, expect, it } from "vitest"; import { importRecipe, records, renderPipelineSvg, runPipeline, type Stage, } from "../../src/core/pipeline"; describe("pipeline", () => { const input = records([ { name: "Ada", team: "a", score: 2 }, { name: "Grace", team: "a", score: 4 }, { name: "Linus", team: "b", score: 9 }, ]); it("filters sorts groups and exposes snapshots", () => { const stages: Stage[] = [ { id: "1", type: "filter", enabled: true, config: { field: "score", operator: ">=", value: "4" }, }, { id: "2", type: "group", enabled: true, config: { key: "team", fn: "avg", field: "score", output: "mean" }, }, ]; const result = runPipeline(input, stages); expect(result.rows).toEqual([ { team: "a", mean: 4 }, { team: "b", mean: 9 }, ]); expect(result.snapshots).toHaveLength(3); }); it("joins with prefixed right values", () => { const result = runPipeline( input, [ { id: "j", type: "join", enabled: true, config: { left: "team", right: "id", mode: "left", prefix: "r_" }, }, ], records([{ id: "a", lead: "Mina" }]), ); expect(result.rows[0]?.r_lead).toBe("Mina"); expect(result.rows[2]?.r_lead).toBeUndefined(); }); it("validates recipes and rejects code stages", () => { expect(() => importRecipe( '{"schema":"de.add-ideas.flow-tools.recipe.v1","stages":[{"type":"javascript","config":{}}]}', ), ).toThrow(/Invalid recipe/iu); expect(() => importRecipe( '{"schema":"de.add-ideas.flow-tools.recipe.v1","stages":[{"id":"same","type":"sort","enabled":true,"config":{}},{"id":"same","type":"sort","enabled":true,"config":{}}]}', ), ).toThrow(/safe unique id/iu); }); it("deduplicates, explodes arrays and slices deterministically", () => { const result = runPipeline( records([ { id: 1, tags: ["a", "b"] }, { id: 1, tags: ["a", "b"] }, { id: 2, tags: ["c"] }, ]), [ { id: "d", type: "deduplicate", enabled: true, config: { fields: "id" }, }, { id: "e", type: "explode", enabled: true, config: { field: "tags", target: "tag" }, }, { id: "s", type: "slice", enabled: true, config: { offset: "1", count: "2" }, }, ], ); expect(result.rows.map((row) => row.tag)).toEqual(["b", "c"]); expect(result.analysis).toMatchObject({ inputRows: 3, outputRows: 2, duplicateRows: 1, }); expect(result.snapshots.at(-1)?.losses).toContain( "1 rows fall outside the selected slice.", ); }); it("exports an inert pipeline SVG", () => { const svg = renderPipelineSvg([ { id: "x", type: "map", enabled: true, config: {} }, ]); expect(svg).toContain("Flow pipeline"); expect(svg).not.toContain("