adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
303 lines
6.6 KiB
TypeScript
303 lines
6.6 KiB
TypeScript
import { describe, test, expect } from "vitest";
|
|
import {
|
|
isV2,
|
|
migrateToV2,
|
|
toLegacyShape,
|
|
allNodes,
|
|
findNode
|
|
} from "./workflow-migration";
|
|
import type {
|
|
OperatorNode,
|
|
ReferenceNode,
|
|
SubjectNode,
|
|
WFEdge,
|
|
Workflow,
|
|
WorkflowV1
|
|
} from "./workflow-types";
|
|
|
|
function makeV2(overrides: Partial<Workflow> = {}): Workflow {
|
|
return {
|
|
schema_version: "2",
|
|
name: "Test",
|
|
runtime: { default: "client" },
|
|
references: [],
|
|
operators: [],
|
|
subjects: [],
|
|
edges: [],
|
|
view: { default: "canvas" },
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
function datasetOperator(
|
|
id: string,
|
|
overrides: Partial<OperatorNode> = {}
|
|
): OperatorNode {
|
|
return {
|
|
id,
|
|
role: "operator",
|
|
kind: "dataset",
|
|
label: "Dataset",
|
|
dataset_id: "user/dataset",
|
|
dataset_config: "default",
|
|
dataset_split: "train",
|
|
inputs: [],
|
|
outputs: [{ id: "out_0", label: "Data", type: "json" }],
|
|
data: {},
|
|
x: 0,
|
|
y: 0,
|
|
width: 240,
|
|
height: 90,
|
|
runtime: "client",
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
describe("isV2", () => {
|
|
test("accepts a properly-shaped v2 workflow", () => {
|
|
expect(isV2(makeV2())).toBe(true);
|
|
});
|
|
|
|
test("rejects v1-shaped workflows", () => {
|
|
const v1: WorkflowV1 = { version: "1", name: "old", nodes: [], edges: [] };
|
|
expect(isV2(v1)).toBe(false);
|
|
});
|
|
|
|
test("rejects null / non-objects", () => {
|
|
expect(isV2(null)).toBe(false);
|
|
expect(isV2(undefined)).toBe(false);
|
|
expect(isV2(42)).toBe(false);
|
|
expect(isV2("hello")).toBe(false);
|
|
});
|
|
|
|
test("rejects v2 missing required arrays", () => {
|
|
expect(isV2({ schema_version: "2", references: [], operators: [] })).toBe(
|
|
false
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("migrateToV2 — v1 → v2 promotion", () => {
|
|
test("an empty v1 produces an empty v2", () => {
|
|
const result = migrateToV2({});
|
|
expect(result.schema_version).toBe("2");
|
|
expect(result.references).toEqual([]);
|
|
expect(result.operators).toEqual([]);
|
|
expect(result.subjects).toEqual([]);
|
|
expect(result.edges).toEqual([]);
|
|
});
|
|
|
|
test("v1 transforms become operators with inferred kind", () => {
|
|
const v1: WorkflowV1 = {
|
|
nodes: [
|
|
{
|
|
id: "n1",
|
|
kind: "transform",
|
|
label: "T2I",
|
|
source: "model",
|
|
model_id: "user/sd",
|
|
inputs: [],
|
|
outputs: [],
|
|
x: 0,
|
|
y: 0,
|
|
width: 200,
|
|
height: 100,
|
|
data: {}
|
|
}
|
|
],
|
|
edges: []
|
|
};
|
|
const wf = migrateToV2(v1);
|
|
expect(wf.operators).toHaveLength(1);
|
|
expect(wf.operators[0].kind).toBe("model");
|
|
expect(wf.operators[0].model_id).toBe("user/sd");
|
|
});
|
|
|
|
test("v1 components without incoming edges become references", () => {
|
|
const v1: WorkflowV1 = {
|
|
nodes: [
|
|
{
|
|
id: "n1",
|
|
kind: "component",
|
|
label: "Image",
|
|
source: "local",
|
|
inputs: [{ id: "in", label: "i", type: "image" }],
|
|
outputs: [{ id: "out", label: "o", type: "image" }],
|
|
x: 0,
|
|
y: 0,
|
|
width: 200,
|
|
height: 160,
|
|
data: {}
|
|
}
|
|
],
|
|
edges: []
|
|
};
|
|
const wf = migrateToV2(v1);
|
|
expect(wf.references).toHaveLength(1);
|
|
expect(wf.subjects).toHaveLength(0);
|
|
expect(wf.references[0].asset_type).toBe("image");
|
|
});
|
|
|
|
test("v1 components with incoming edges become subjects", () => {
|
|
const v1: WorkflowV1 = {
|
|
nodes: [
|
|
{
|
|
id: "n1",
|
|
kind: "component",
|
|
label: "Image",
|
|
source: "local",
|
|
inputs: [{ id: "in", label: "i", type: "image" }],
|
|
outputs: [{ id: "out", label: "o", type: "image" }],
|
|
x: 0,
|
|
y: 0,
|
|
width: 200,
|
|
height: 160,
|
|
data: {}
|
|
}
|
|
],
|
|
edges: [
|
|
{
|
|
id: "e1",
|
|
from_node_id: "x",
|
|
from_port_id: "out",
|
|
to_node_id: "n1",
|
|
to_port_id: "in",
|
|
type: "image"
|
|
}
|
|
]
|
|
};
|
|
const wf = migrateToV2(v1);
|
|
expect(wf.subjects).toHaveLength(1);
|
|
expect(wf.references).toHaveLength(0);
|
|
});
|
|
|
|
test("already-v2 input is returned (cleanups applied)", () => {
|
|
const wf = makeV2({
|
|
operators: [datasetOperator("d1")]
|
|
});
|
|
const result = migrateToV2(wf);
|
|
expect(result.schema_version).toBe("2");
|
|
expect(result.operators).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("toLegacyShape", () => {
|
|
test("projects references to component nodes", () => {
|
|
const ref: ReferenceNode = {
|
|
id: "r1",
|
|
role: "reference",
|
|
label: "Text",
|
|
asset_type: "text",
|
|
inputs: [{ id: "in", label: "t", type: "text" }],
|
|
outputs: [{ id: "out", label: "t", type: "text" }],
|
|
data: {},
|
|
x: 0,
|
|
y: 0,
|
|
width: 200,
|
|
height: 160
|
|
};
|
|
const legacy = toLegacyShape(makeV2({ references: [ref] }));
|
|
expect(legacy.nodes[0].kind).toBe("component");
|
|
expect(legacy.nodes[0].source).toBe("local");
|
|
});
|
|
|
|
test("projects operators with kind → source mapping", () => {
|
|
const op: OperatorNode = {
|
|
id: "d1",
|
|
role: "operator",
|
|
kind: "dataset",
|
|
label: "ds",
|
|
inputs: [],
|
|
outputs: [{ id: "out_0", label: "Data", type: "json" }],
|
|
data: {},
|
|
x: 0,
|
|
y: 0,
|
|
width: 240,
|
|
height: 90,
|
|
runtime: "client"
|
|
};
|
|
const legacy = toLegacyShape(makeV2({ operators: [op] }));
|
|
expect(legacy.nodes[0].kind).toBe("transform");
|
|
expect(legacy.nodes[0].source).toBe("dataset");
|
|
});
|
|
|
|
test("projects subjects to component nodes", () => {
|
|
const sub: SubjectNode = {
|
|
id: "s1",
|
|
role: "subject",
|
|
label: "Image",
|
|
asset_type: "image",
|
|
inputs: [{ id: "in", label: "i", type: "image" }],
|
|
outputs: [{ id: "out", label: "o", type: "image" }],
|
|
data: {},
|
|
x: 0,
|
|
y: 0,
|
|
width: 200,
|
|
height: 160
|
|
};
|
|
const legacy = toLegacyShape(makeV2({ subjects: [sub] }));
|
|
expect(legacy.nodes[0].kind).toBe("component");
|
|
});
|
|
|
|
test("preserves edges verbatim", () => {
|
|
const edges: WFEdge[] = [
|
|
{
|
|
id: "e1",
|
|
from_node_id: "a",
|
|
from_port_id: "p1",
|
|
to_node_id: "b",
|
|
to_port_id: "p2",
|
|
type: "text"
|
|
}
|
|
];
|
|
const legacy = toLegacyShape(makeV2({ edges }));
|
|
expect(legacy.edges).toEqual(edges);
|
|
});
|
|
});
|
|
|
|
describe("allNodes / findNode", () => {
|
|
test("allNodes concatenates references, operators, subjects", () => {
|
|
const wf = makeV2({
|
|
references: [
|
|
{
|
|
id: "r1",
|
|
role: "reference",
|
|
label: "x",
|
|
asset_type: "text",
|
|
inputs: [],
|
|
outputs: [],
|
|
data: {},
|
|
x: 0,
|
|
y: 0,
|
|
width: 200,
|
|
height: 100
|
|
} as ReferenceNode
|
|
],
|
|
operators: [datasetOperator("o1")],
|
|
subjects: [
|
|
{
|
|
id: "s1",
|
|
role: "subject",
|
|
label: "x",
|
|
asset_type: "text",
|
|
inputs: [],
|
|
outputs: [],
|
|
data: {},
|
|
x: 0,
|
|
y: 0,
|
|
width: 200,
|
|
height: 100
|
|
} as SubjectNode
|
|
]
|
|
});
|
|
const ids = allNodes(wf).map((n) => n.id);
|
|
expect(ids).toEqual(["r1", "o1", "s1"]);
|
|
});
|
|
|
|
test("findNode locates a node by id across roles", () => {
|
|
const wf = makeV2({ operators: [datasetOperator("o42")] });
|
|
expect(findNode(wf, "o42")?.id).toBe("o42");
|
|
expect(findNode(wf, "missing")).toBeUndefined();
|
|
});
|
|
});
|