111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
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("<script");
|
|
});
|
|
});
|