import { describe, expect, it } from "vitest"; import type { CatalogComponent, FlowDefinition } from "../../src/types"; import { register } from "../../src/engine/registry"; import { runFlow } from "../../src/engine/runtime"; const components = new Map(); function component(id: string): CatalogComponent { const value = { id, name: id, kind: "vendor", group: "data", categories: [], categorySignature: "", description: "", messageTemplate: "", baseType: id, functionName: id, functionCalls: [], version: "", tags: [], platformTag: null, icon: "data", risk: "low", fields: [], output: { label: "", key: "", suggestedName: "", valueTypes: [], fields: [] }, slots: [] } as unknown as CatalogComponent; components.set(id, value); return value; } for (const id of ["Test.Start", "Test.Fail", "Test.Normal", "Test.Recover"]) component(id); register("Test.Start", () => true); register("Test.Fail", () => { throw new Error("expected failure"); }); register("Test.Normal", (ctx) => { ctx.vars.normalRan = true; }); register("Test.Recover", (ctx) => { ctx.vars.recovered = true; }); describe("runtime error route", () => { it("uses the error handle after failure and does not execute the normal branch", async () => { const now = new Date().toISOString(); const flow: FlowDefinition = { schemaVersion: 1, id: "error-route", name: "error route", variables: [], nodes: [ { id: "start", componentId: "Test.Start", position: { x: 0, y: 0 }, values: {}, options: {} }, { id: "fail", componentId: "Test.Fail", position: { x: 0, y: 100 }, values: {}, options: { errorRouteEnabled: true } }, { id: "normal", componentId: "Test.Normal", position: { x: 0, y: 200 }, values: {}, options: {} }, { id: "recover", componentId: "Test.Recover", position: { x: 200, y: 100 }, values: {}, options: {} } ], edges: [ { id: "e1", source: "start", target: "fail" }, { id: "e2", source: "fail", target: "normal" }, { id: "e3", source: "fail", sourceHandle: "error", target: "recover" } ], meta: { createdAt: now, updatedAt: now, appVersion: "test" } }; const result = await runFlow(flow, { runId: "test", evidenceDir: ".", signal: new AbortController().signal, emit: () => {}, log: () => {}, componentOf: (node) => components.get(node.componentId) }); expect(result.status).toBe("succeeded"); expect(result.variables.recovered).toBe(true); expect(result.variables.normalRan).toBeUndefined(); expect(result.variables.error).toMatchObject({ reason: "expected failure" }); }); it("uses the normal handle after success and leaves error and disconnected nodes untouched", async () => { const now = new Date().toISOString(); const flow: FlowDefinition = { schemaVersion: 1, id: "normal-route", name: "normal route", variables: [], nodes: [ { id: "start", componentId: "Test.Start", position: { x: 0, y: 0 }, values: {}, options: {} }, { id: "normal", componentId: "Test.Normal", position: { x: 0, y: 100 }, values: {}, options: {} }, { id: "recover", componentId: "Test.Recover", position: { x: 200, y: 0 }, values: {}, options: {} }, { id: "orphan", componentId: "Test.Recover", position: { x: 400, y: 400 }, values: {}, options: {} } ], edges: [ { id: "e1", source: "start", target: "normal" }, { id: "e2", source: "start", sourceHandle: "error", target: "recover" } ], meta: { createdAt: now, updatedAt: now, appVersion: "test" } }; const result = await runFlow(flow, { runId: "test", evidenceDir: ".", signal: new AbortController().signal, emit: () => {}, log: () => {}, componentOf: (node) => components.get(node.componentId) }); expect(result.status).toBe("succeeded"); expect(result.variables.normalRan).toBe(true); expect(result.variables.recovered).toBeUndefined(); expect(result.nodes.map((state) => state.nodeId)).toEqual(["start", "normal"]); }); });