Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:17:32 +08:00

731 lines
18 KiB
TypeScript

import { describe, test, expect, vi } from "vitest";
import { executeWorkflow } from "./workflow-executor";
import type {
OperatorNode,
NodeDataValue,
NodeStatus,
ReferenceNode,
WFEdge,
Workflow
} from "./workflow-types";
function emptyV2(
operators: OperatorNode[] = [],
references: ReferenceNode[] = [],
edges: WFEdge[] = []
): Workflow {
return {
schema_version: "2",
name: "Test",
runtime: { default: "client" },
references,
operators,
subjects: [],
edges,
view: { default: "canvas" }
};
}
function statusBag(): {
onStatus: (id: string, s: NodeStatus, err?: string) => void;
statuses: Record<string, NodeStatus>;
errors: Record<string, string>;
} {
const statuses: Record<string, NodeStatus> = {};
const errors: Record<string, string> = {};
return {
onStatus: (id, s, err) => {
statuses[id] = s;
if (err) errors[id] = err;
},
statuses,
errors
};
}
describe("executeWorkflow — dataset row_index input port", () => {
function datasetOpWithRowIndex(
id: string,
overrides: Partial<OperatorNode> = {}
): OperatorNode {
return {
id,
role: "operator",
kind: "dataset",
label: "ds",
dataset_id: "user/dataset",
dataset_config: "default",
dataset_split: "train",
inputs: [
{
id: "row_index",
label: "Row",
type: "number",
default_value: 0
}
],
outputs: [{ id: "out_0", label: "value", type: "text" }],
data: {},
x: 0,
y: 0,
width: 240,
height: 90,
runtime: "client",
...overrides
};
}
test("uses inline data value when row_index is unwired", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
const op = datasetOpWithRowIndex("d1", { data: { row_index: 11 } });
await executeWorkflow(
emptyV2([op]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][3]).toBe("11");
});
test("falls back to port default_value when neither edge nor inline data is set", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
// Default value on the port itself; no data, no edge.
const op = datasetOpWithRowIndex("d1", {
inputs: [
{
id: "row_index",
label: "Row",
type: "number",
default_value: 6
}
]
});
await executeWorkflow(
emptyV2([op]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][3]).toBe("6");
});
test("reads from upstream when row_index is wired", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
const ref: ReferenceNode = {
id: "r1",
role: "reference",
label: "Number",
asset_type: "number",
inputs: [{ id: "in", label: "n", type: "number" }],
outputs: [{ id: "out", label: "n", type: "number" }],
data: { in: 9 },
x: 0,
y: 0,
width: 200,
height: 130
};
const op = datasetOpWithRowIndex("d1");
const edge: WFEdge = {
id: "e1",
from_node_id: "r1",
from_port_id: "in",
to_node_id: "d1",
to_port_id: "row_index",
type: "number"
};
await executeWorkflow(
emptyV2([op], [ref], [edge]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][3]).toBe("9");
});
test("coerces string numerics from upstream", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
const op = datasetOpWithRowIndex("d1", {
data: { row_index: "14" as unknown as NodeDataValue }
});
await executeWorkflow(
emptyV2([op]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][3]).toBe("14");
});
test("falls back to offset 0 when the row_index port has no value", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
const op = datasetOpWithRowIndex("d1", {
inputs: [{ id: "row_index", label: "Row", type: "number" }]
});
await executeWorkflow(
emptyV2([op]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][3]).toBe("0");
});
test("negative row indices clamp to 0", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
await executeWorkflow(
emptyV2([datasetOpWithRowIndex("d1", { data: { row_index: -42 } })]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][3]).toBe("0");
});
test("fractional indices truncate", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
await executeWorkflow(
emptyV2([datasetOpWithRowIndex("d1", { data: { row_index: 3.7 } })]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][3]).toBe("3");
});
test("passes dataset_config and dataset_split through verbatim", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "x" }] }));
await executeWorkflow(
emptyV2([
datasetOpWithRowIndex("d1", {
dataset_config: "subset_a",
dataset_split: "test"
})
]),
() => {},
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(fetchStub.mock.calls[0][1]).toBe("subset_a");
expect(fetchStub.mock.calls[0][2]).toBe("test");
});
test("marks status error when fetch returns an error blob", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ error: "boom" }));
const { onStatus, statuses, errors } = statusBag();
await executeWorkflow(
emptyV2([datasetOpWithRowIndex("d1")]),
onStatus,
() => {},
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(statuses.d1).toBe("error");
expect(errors.d1).toBe("boom");
});
test("emits output by matching the row column to the port label", async () => {
const fetchStub = vi
.fn()
.mockResolvedValue(JSON.stringify({ rows: [{ value: "hello" }] }));
const onOutput = vi.fn();
await executeWorkflow(
emptyV2([datasetOpWithRowIndex("d1")]),
() => {},
onOutput,
undefined,
undefined,
undefined,
fetchStub as unknown as Parameters<typeof executeWorkflow>[6]
);
expect(onOutput).toHaveBeenCalledWith("d1", "out_0", "hello");
});
});
describe("executeWorkflow — model operator routing", () => {
function modelOp(
id: string,
overrides: Partial<OperatorNode> = {}
): OperatorNode {
return {
id,
role: "operator",
kind: "model",
label: "m",
model_id: "user/m",
pipeline_tag: "text-generation",
inputs: [
{
id: "in_0",
label: "Prompt",
type: "text",
default_value: "hello"
}
],
outputs: [{ id: "out_0", label: "Text", type: "text" }],
data: {},
x: 0,
y: 0,
width: 280,
height: 90,
runtime: "client",
...overrides
};
}
test("passes node.provider through to serverCallModel", async () => {
const callModel = vi
.fn()
.mockResolvedValue(JSON.stringify(["fake-output"]));
await executeWorkflow(
emptyV2([
modelOp("m1", { provider: "together", pipeline_tag: "summarization" })
]),
() => {},
() => {},
undefined,
undefined,
callModel as unknown as Parameters<typeof executeWorkflow>[5],
undefined,
undefined
);
// (modelId, pipelineTag, argsJson, provider)
expect(callModel).toHaveBeenCalledTimes(1);
expect(callModel.mock.calls[0][0]).toBe("user/m");
expect(callModel.mock.calls[0][1]).toBe("summarization");
expect(callModel.mock.calls[0][3]).toBe("together");
});
test("provider stays undefined when the node doesn't declare one", async () => {
const callModel = vi.fn().mockResolvedValue(JSON.stringify(["fake"]));
await executeWorkflow(
emptyV2([modelOp("m1", { pipeline_tag: "summarization" })]),
() => {},
() => {},
undefined,
undefined,
callModel as unknown as Parameters<typeof executeWorkflow>[5],
undefined,
undefined
);
expect(callModel.mock.calls[0][3]).toBeUndefined();
});
test("text-generation routes through stream_text_generation when provided", async () => {
const streamFn = vi
.fn()
.mockImplementation(
async (_modelId, _prompt, _provider, _signal, onChunk) => {
onChunk("hel", "hel");
onChunk("lo", "hello");
return "hello";
}
);
const callModel = vi.fn();
const onOutput = vi.fn();
await executeWorkflow(
emptyV2([modelOp("m1", { pipeline_tag: "text-generation" })]),
() => {},
onOutput,
undefined,
undefined,
callModel as unknown as Parameters<typeof executeWorkflow>[5],
undefined,
undefined,
streamFn as unknown as Parameters<typeof executeWorkflow>[8]
);
expect(callModel).not.toHaveBeenCalled();
expect(streamFn).toHaveBeenCalledTimes(1);
// emits accumulated text per chunk to the output port
expect(onOutput).toHaveBeenNthCalledWith(1, "m1", "out_0", "hel");
expect(onOutput).toHaveBeenNthCalledWith(2, "m1", "out_0", "hello");
});
test("non-streamable text tasks still use serverCallModel even when stream fn is provided", async () => {
const streamFn = vi.fn();
const callModel = vi.fn().mockResolvedValue(JSON.stringify(["summary"]));
await executeWorkflow(
emptyV2([modelOp("m1", { pipeline_tag: "summarization" })]),
() => {},
() => {},
undefined,
undefined,
callModel as unknown as Parameters<typeof executeWorkflow>[5],
undefined,
undefined,
streamFn as unknown as Parameters<typeof executeWorkflow>[8]
);
expect(streamFn).not.toHaveBeenCalled();
expect(callModel).toHaveBeenCalledTimes(1);
});
test("falls back to serverCallModel for text-generation when no stream fn is provided", async () => {
const callModel = vi
.fn()
.mockResolvedValue(JSON.stringify(["fallback text"]));
await executeWorkflow(
emptyV2([modelOp("m1", { pipeline_tag: "text-generation" })]),
() => {},
() => {},
undefined,
undefined,
callModel as unknown as Parameters<typeof executeWorkflow>[5],
undefined,
undefined
// no stream fn
);
expect(callModel).toHaveBeenCalledTimes(1);
});
test("passes the streaming model id + provider to the stream fn", async () => {
const streamFn = vi.fn().mockResolvedValue("done");
const callModel = vi.fn();
await executeWorkflow(
emptyV2([
modelOp("m1", {
pipeline_tag: "text-generation",
provider: "together"
})
]),
() => {},
() => {},
undefined,
undefined,
callModel as unknown as Parameters<typeof executeWorkflow>[5],
undefined,
undefined,
streamFn as unknown as Parameters<typeof executeWorkflow>[8]
);
const [modelId, prompt, provider] = streamFn.mock.calls[0];
expect(modelId).toBe("user/m");
expect(prompt).toBe("hello");
expect(provider).toBe("together");
});
});
describe("executeWorkflow — space multi-output mapping", () => {
function spaceOp(
id: string,
overrides: Partial<OperatorNode> = {}
): OperatorNode {
return {
id,
role: "operator",
kind: "space",
label: "s",
space_id: "user/s",
endpoint: "/predict",
inputs: [
{
id: "in_0",
label: "Prompt",
type: "text",
default_value: "hello"
}
],
outputs: [{ id: "out_0", label: "Image", type: "image" }],
data: {},
x: 0,
y: 0,
width: 280,
height: 90,
runtime: "client",
...overrides
};
}
async function run(
node: OperatorNode,
responseJson: string
): Promise<Record<string, NodeDataValue>> {
const callSpace = vi.fn().mockResolvedValue(responseJson);
const outputs: Record<string, NodeDataValue> = {};
await executeWorkflow(
emptyV2([node]),
() => {},
(_id, portId, value) => {
outputs[portId] = value;
},
undefined,
callSpace as unknown as Parameters<typeof executeWorkflow>[4],
undefined,
undefined,
undefined,
undefined
);
return outputs;
}
test("explicit output_index picks the right item over a trailing seed", async () => {
const node = spaceOp("s1", {
outputs: [
{
id: "out_0",
label: "Image",
type: "image",
output_index: 0
}
]
});
const response = JSON.stringify([
{ path: "/tmp/img.webp", url: "/file=/tmp/img.webp", is_file: true },
1686841721
]);
const outputs = await run(node, response);
expect((outputs.out_0 as { url: string }).url).toContain("img.webp");
});
test("output_index of a later position routes that item to the port", async () => {
const node = spaceOp("s1", {
outputs: [
{
id: "out_0",
label: "Image",
type: "image",
output_index: 1
}
]
});
const response = JSON.stringify([
1686841721,
{ path: "/tmp/img.webp", url: "/file=/tmp/img.webp", is_file: true }
]);
const outputs = await run(node, response);
expect((outputs.out_0 as { url: string }).url).toContain("img.webp");
});
test("legacy node (no output_index) shape-matches: picks image dict over trailing seed", async () => {
const node = spaceOp("s1");
const response = JSON.stringify([
{ path: "/tmp/img.webp", url: "/file=/tmp/img.webp", is_file: true },
1686841721
]);
const outputs = await run(node, response);
expect((outputs.out_0 as { url: string }).url).toContain("img.webp");
});
test("legacy node shape-matches: picks image when image is at index 1", async () => {
const node = spaceOp("s1");
const response = JSON.stringify([
1686841721,
{ path: "/tmp/img.webp", url: "/file=/tmp/img.webp", is_file: true }
]);
const outputs = await run(node, response);
expect((outputs.out_0 as { url: string }).url).toContain("img.webp");
});
test("legacy node falls back to first item when nothing matches port type", async () => {
const node = spaceOp("s1");
const response = JSON.stringify(["just a string", 42]);
const outputs = await run(node, response);
expect(outputs.out_0).toBeDefined();
});
test("two-port node maps response items by position", async () => {
const node = spaceOp("s1", {
outputs: [
{ id: "img", label: "Image", type: "image", output_index: 0 },
{ id: "seed", label: "Seed", type: "number", output_index: 1 }
]
});
const response = JSON.stringify([
{ path: "/tmp/img.webp", url: "/file=/tmp/img.webp", is_file: true },
1686841721
]);
const outputs = await run(node, response);
expect((outputs.img as { url: string }).url).toContain("img.webp");
expect(outputs.seed).toBe(1686841721);
});
test("stale output_index recovers via shape match (Space API drifted)", async () => {
const node = spaceOp("s1", {
outputs: [{ id: "out_0", label: "Image", type: "image", output_index: 0 }]
});
const response = JSON.stringify([
1686841721,
{ path: "/tmp/img.webp", url: "/file=/tmp/img.webp", is_file: true }
]);
const outputs = await run(node, response);
expect((outputs.out_0 as { url: string }).url).toContain("img.webp");
});
test("unwraps gr.update() wrapper objects", async () => {
const node = spaceOp("s1");
const response = JSON.stringify([
{
__type__: "update",
value: {
path: "/tmp/img.webp",
url: "/file=/tmp/img.webp",
is_file: true
},
visible: true
},
1686841721
]);
const outputs = await run(node, response);
expect((outputs.out_0 as { url: string }).url).toContain("img.webp");
});
test("nothing matches and primary is junk → falls back gracefully", async () => {
const node = spaceOp("s1", {
outputs: [{ id: "out_0", label: "Image", type: "image", output_index: 0 }]
});
const response = JSON.stringify(["unhelpful", 42]);
const outputs = await run(node, response);
expect(outputs.out_0).toBeDefined();
});
});
describe("executeWorkflow — cascading failure messages", () => {
test("downstream node's missing-input error names the failed upstream", async () => {
const failingFn = "boom";
const downstreamFn = "consumer";
const callFn = vi.fn().mockImplementation(async (name: string) => {
if (name === failingFn) {
return JSON.stringify({
error: "Upstream raised",
error_type: "unknown",
suggestion: ""
});
}
return JSON.stringify(["ok"]);
});
const upstream: OperatorNode = {
id: "u",
role: "operator",
kind: "fn",
label: "moondream2",
fn: failingFn,
inputs: [],
outputs: [{ id: "out", label: "Text", type: "text" }],
data: {},
x: 0,
y: 0,
width: 200,
height: 80,
runtime: "client"
};
const downstream: OperatorNode = {
id: "d",
role: "operator",
kind: "fn",
label: "FLUX",
fn: downstreamFn,
inputs: [{ id: "in", label: "Prompt", type: "text", required: true }],
outputs: [{ id: "out", label: "Image", type: "image" }],
data: {},
x: 0,
y: 0,
width: 200,
height: 80,
runtime: "client"
};
const edge: WFEdge = {
id: "e1",
from_node_id: "u",
from_port_id: "out",
to_node_id: "d",
to_port_id: "in",
type: "text"
};
const { onStatus, errors } = statusBag();
await executeWorkflow(
emptyV2([upstream, downstream], [], [edge]),
onStatus,
() => {},
undefined,
undefined,
undefined,
undefined,
callFn as unknown as Parameters<typeof executeWorkflow>[7],
undefined
);
expect(errors.d).toContain('"Prompt" is missing');
expect(errors.d).toContain("moondream2");
});
});