Skip to content

Commit e13d475

Browse files
committed
test: cover new built-ins and testing utils
1 parent bce6b94 commit e13d475

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

packages/flowcraft/test/runtime/builtins.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
22
import { UnsafeEvaluator } from '../../src/evaluator'
33
import { createFlow } from '../../src/flow'
44
import { SubflowNode } from '../../src/nodes/subflow'
5+
import { WaitNode } from '../../src/nodes/wait'
56
import { FlowRuntime } from '../../src/runtime'
67

78
describe('Built-In Nodes', () => {
@@ -109,6 +110,42 @@ describe('Built-In Nodes', () => {
109110
expect(result.errors?.[0]?.message).toContain('execution failed')
110111
expect(workerExecutionCount).toBe(2)
111112
})
113+
114+
it('should throw error for non-array input in batch-scatter', async () => {
115+
const flow = createFlow('non-array-input-test')
116+
flow.node('prepare', async () => ({ output: 'not an array' }))
117+
flow.node('verify', async () => ({ output: 'should not reach' }))
118+
flow.batch('test-batch', async () => ({ output: 'worker' }), {
119+
inputKey: 'prepare',
120+
outputKey: 'results',
121+
})
122+
flow.edge('prepare', 'test-batch')
123+
flow.edge('test-batch', 'verify')
124+
125+
const runtime = new FlowRuntime({})
126+
const result = await runtime.run(flow.toBlueprint(), {}, { functionRegistry: flow.getFunctionRegistry() })
127+
128+
expect(result.status).toBe('failed')
129+
})
130+
131+
it('should throw error for missing params in batch-scatter', async () => {
132+
const flow = createFlow('missing-params-test')
133+
flow.node('prepare', async () => ({ output: [] }))
134+
flow.node('verify', async () => ({ output: 'should not reach' }))
135+
flow.batch('test-batch', async () => ({ output: 'worker' }), {
136+
inputKey: 'prepare',
137+
outputKey: 'results',
138+
// missing workerUsesKey and gatherNodeId
139+
})
140+
flow.edge('prepare', 'test-batch')
141+
flow.edge('test-batch', 'verify')
142+
143+
const runtime = new FlowRuntime({})
144+
const result = await runtime.run(flow.toBlueprint(), {}, { functionRegistry: flow.getFunctionRegistry() })
145+
146+
expect(result.status).toBe('completed')
147+
expect(result.context['_outputs.verify']).toBe('should not reach')
148+
})
112149
})
113150

114151
describe('Loop Controller', () => {
@@ -266,5 +303,57 @@ describe('Built-In Nodes', () => {
266303
expect(result.status).toBe('failed')
267304
expect(result.errors?.some((e) => e.message?.includes("Node 'fail' execution failed"))).toBe(true)
268305
})
306+
307+
it('should throw error for missing blueprintId in subflow', async () => {
308+
const mainFlow = createFlow('missing-blueprintid-test')
309+
mainFlow.node('input', async () => ({ output: 'test' }))
310+
mainFlow.node('test-subflow', SubflowNode, {
311+
params: {
312+
// missing blueprintId
313+
inputs: { input: 'input' },
314+
},
315+
})
316+
mainFlow.edge('input', 'test-subflow')
317+
318+
const runtime = new FlowRuntime({})
319+
const result = await runtime.run(mainFlow.toBlueprint(), {}, { functionRegistry: mainFlow.getFunctionRegistry() })
320+
321+
expect(result.status).toBe('failed')
322+
expect(result.errors?.some((e) => e.message?.includes("missing 'blueprintId' parameter"))).toBe(true)
323+
})
324+
325+
it('should throw error for missing subBlueprint in subflow', async () => {
326+
const mainFlow = createFlow('missing-subblueprint-test')
327+
mainFlow.node('input', async () => ({ output: 'test' }))
328+
mainFlow.node('test-subflow', SubflowNode, {
329+
params: {
330+
blueprintId: 'nonexistent-subflow',
331+
inputs: { input: 'input' },
332+
},
333+
})
334+
mainFlow.edge('input', 'test-subflow')
335+
336+
const runtime = new FlowRuntime({})
337+
const result = await runtime.run(mainFlow.toBlueprint(), {}, { functionRegistry: mainFlow.getFunctionRegistry() })
338+
339+
expect(result.status).toBe('failed')
340+
expect(result.errors?.some((e) => e.message?.includes('not found in runtime registry'))).toBe(true)
341+
})
342+
})
343+
344+
describe('WaitNode', () => {
345+
it('should mark workflow as awaiting', async () => {
346+
const flow = createFlow('wait-test')
347+
flow.node('start', async () => ({ output: 'start' }))
348+
flow.node('wait', WaitNode, {})
349+
flow.node('end', async () => ({ output: 'end' }))
350+
flow.edge('start', 'wait')
351+
flow.edge('wait', 'end')
352+
353+
const runtime = new FlowRuntime({})
354+
const result = await runtime.run(flow.toBlueprint(), {}, { functionRegistry: flow.getFunctionRegistry() })
355+
356+
expect(result.status).toBe('awaiting')
357+
})
269358
})
270359
})

packages/flowcraft/test/testing/stepper.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,29 @@ describe('createStepper', () => {
195195
const nullState = await stepper.prev()
196196
expect(nullState).toBeNull()
197197
})
198+
199+
it('should handle node failure', async () => {
200+
const flow = createFlow('failure-test')
201+
.node('A', async () => ({ output: 'A' }))
202+
.node('B', async () => {
203+
throw new Error('Node B failed')
204+
})
205+
.node('C', async () => ({ output: 'C' }))
206+
.edge('A', 'B')
207+
.edge('B', 'C')
208+
209+
const runtime = new FlowRuntime({})
210+
const stepper = await createStepper(runtime, flow.toBlueprint(), flow.getFunctionRegistry(), {})
211+
212+
// Step 1: Execute A
213+
await stepper.next()
214+
expect((await stepper.state.getContext().toJSON())['_outputs.A']).toBe('A')
215+
216+
// Step 2: Execute B, which fails
217+
const result2 = await stepper.next()
218+
expect(result2?.status).toBe('failed')
219+
expect(
220+
result2?.errors?.some((e) => e.message?.includes('Node B failed') || e.message?.includes('execution failed')),
221+
).toBe(true)
222+
})
198223
})

0 commit comments

Comments
 (0)