Skip to content

Commit 827f4fa

Browse files
authored
test: add more flow example (#445)
* test: add progress flow to validate report_progress API Add test flow that calls context.report_progress() 4 times with values 0, 25, 50, 75 to verify BlockProgress events. * test: add warning flow to validate BlockWarning on undefined output handle When a block returns an output key not defined in outputs_def, the SDK should emit a BlockWarning event. This test returns 'c' when only 'a' and 'b' are defined. * test: add nullable flow to validate nullable input handling Adds test flow that verifies inputs with nullable: true can accept null/empty values. First node validates input is None, second node receives output from first node plus its own nullable input. * test: add subflow-progress flow to validate BlockProgress events in subflows Add test case with a subflow containing 2 nodes that each report progress 4 times (0%, 25%, 50%, 75%), generating at least 8 BlockProgress events total. The main flow calls the subflow and passes output to an end node. * test: add from flow to validate inputs_from modes Tests three inputs_from connection types: - value: direct input value (input=2) - from_node: output from another node (+python#2 outputs 3) - value+from_node: value node (+value#1=4) with fallback value (5) Expected result: 2 + 3 + 4 = 9 * test: add additional-block flow to validate dynamic inputs/outputs Add a block with additional_inputs: true and additional_outputs: true to test dynamic input/output handling at node level. * test: add test cases for progress, warning, nullable, subflow-progress, from, and additional-block flows Add 6 new test cases to flow.test.ts: - progress: validates BlockProgress events and output count - warning: validates BlockWarning message on undefined handle - nullable: validates nullable input handling - subflow-progress: validates BlockProgress in subflows (>=8 events, all in [0,100]) - from: validates from_node, value, and value node inputs - additional-block: validates dynamic inputs/outputs * test: strengthen test assertions for progress, from, and additional-block flows - Add BlockProgress event validation to progress test - Add output value assertion (9) to from flow test - Add merged output assertion (helloworld) to additional-block test * fix: update searchPaths to use array instead of string The new @oomol/oocana 0.24.1 expects searchPaths as an array * fix: correct progress flow test assertion The progress flow returns {"count": 3} via return statement, not via context.output(). BlockOutput events are only generated when calling context.output(), so the original assertion was checking a non-existent event. Changed to verify result.count from BlockFinished event instead. * fix: use toContain for warning message assertion The BlockWarning event may contain repeated warning messages. Changed from strict equality (toBe) to substring check (toContain) to handle cases where the warning message is duplicated. * fix: use substring check for additional-block test The iteration order of context.inputs_def is not guaranteed, so the merged output order may vary. Changed from strict equality to substring checks to verify both "hello" and "world" are present in the output. * fix: update bindPaths to use object array format The @oomol/oocana 0.24.1 expects bindPaths as BindPath[] (object array with src/dst properties) instead of string array.
1 parent 6c27902 commit 827f4fa

22 files changed

Lines changed: 499 additions & 2 deletions

File tree

flow-examples/test/flow.test.ts

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,94 @@ describe(
186186
const { code } = await run("pkg-dir");
187187
expect(code).toBe(0);
188188
});
189+
190+
it("run progress flow", async () => {
191+
files.delete("progress");
192+
const { code, events } = await run("progress");
193+
expect(code).toBe(0);
194+
195+
const progressEvents = events.filter(e => e.event === "BlockProgress");
196+
expect(progressEvents.length).greaterThanOrEqual(4);
197+
198+
const latestFinished = events.findLast(e => e.event === "BlockFinished");
199+
const lastNode = latestFinished?.data.stacks?.[0].node_id;
200+
expect(lastNode).toBe("end");
201+
202+
// Validate the return value: {"count": 3}
203+
expect(latestFinished?.data.result?.count).toBe(3);
204+
});
205+
206+
it("run warning flow", async () => {
207+
files.delete("warning");
208+
const { code, events } = await run("warning");
209+
expect(code).toBe(0);
210+
211+
const latestBlockWarning = events.findLast(
212+
e => e.event === "BlockWarning"
213+
)?.data?.warning;
214+
expect(latestBlockWarning).toContain(
215+
"Output handle key: [c] is not defined in Block outputs schema."
216+
);
217+
});
218+
219+
it("run nullable flow", async () => {
220+
files.delete("nullable");
221+
const { code, events } = await run("nullable");
222+
expect(code).toBe(0);
223+
224+
const latestFinished = events.findLast(e => e.event === "BlockFinished");
225+
const lastNode = latestFinished?.data.stacks?.[0].node_id;
226+
expect(lastNode).toBe("end");
227+
});
228+
229+
it("run subflow-progress flow", async () => {
230+
files.delete("subflow-progress");
231+
const { code, events } = await run("subflow-progress");
232+
expect(code).toBe(0);
233+
234+
const progressEvents = events.filter(e => e.event === "BlockProgress");
235+
236+
expect(
237+
progressEvents.every(
238+
e => e.data.progress >= 0 && e.data.progress <= 100
239+
)
240+
).toBe(true);
241+
242+
expect(progressEvents.length).greaterThanOrEqual(8);
243+
244+
const latestFinished = events.findLast(e => e.event === "BlockFinished");
245+
const lastNode = latestFinished?.data.stacks?.[0].node_id;
246+
expect(lastNode).toBe("end");
247+
});
248+
249+
it("run from flow", async () => {
250+
files.delete("from");
251+
const { code, events } = await run("from");
252+
expect(code).toBe(0);
253+
254+
const latestFinished = events.findLast(e => e.event === "BlockFinished");
255+
const lastNode = latestFinished?.data.stacks?.[0].node_id;
256+
expect(lastNode).toBe("end");
257+
258+
// Validate the computed result: input(2) + output(3) + value1(4) = 9
259+
expect(latestFinished?.data.result?.output).toBe(9);
260+
});
261+
262+
it("run additional-block flow", async () => {
263+
files.delete("additional-block");
264+
const { code, events } = await run("additional-block");
265+
expect(code).toBe(0);
266+
267+
const latestFinished = events.findLast(e => e.event === "BlockFinished");
268+
const lastNode = latestFinished?.data.stacks?.[0].node_id;
269+
expect(lastNode).toBe("end");
270+
271+
// Validate the dynamic I/O merged result contains both "hello" and "world"
272+
// Note: iteration order of inputs_def is not guaranteed, so we check for substrings
273+
const output = latestFinished?.data.result?.output;
274+
expect(output).toContain("hello");
275+
expect(output).toContain("world");
276+
});
189277
}
190278
);
191279

@@ -206,8 +294,11 @@ async function run(
206294

207295
const task = await cli.runFlow({
208296
flowPath: path.join(workspace, "flows", flow, "flow.oo.yaml"),
209-
searchPaths: [packages, path.join(workspace, "blocks")].join(","),
210-
bindPaths: [`src=${homedir()}/.oocana,dst=/root/.oocana`, `src=${tmpdir()},dst=${tmpdir()}`],
297+
searchPaths: [packages, path.join(workspace, "blocks")],
298+
bindPaths: [
299+
{ src: `${homedir()}/.oocana`, dst: "/root/.oocana" },
300+
{ src: tmpdir(), dst: tmpdir() },
301+
],
211302
excludePackages: [workspace],
212303
sessionId: flow,
213304
tempRoot: tmpdir(),
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from oocana import Context
2+
3+
4+
def main(inputs: dict, context: Context):
5+
print("inputs_def", context.inputs_def)
6+
print("outputs_def", context.outputs_def)
7+
8+
merged = ""
9+
for key in context.inputs_def:
10+
value = inputs.get(key)
11+
if value is not None:
12+
merged += str(value)
13+
14+
result = {}
15+
for key in context.outputs_def:
16+
result[key] = merged
17+
18+
return result
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
executor:
2+
name: python
3+
options:
4+
entry: __init__.py
5+
inputs_def:
6+
- handle: input
7+
json_schema:
8+
type: string
9+
ContentMediaType: oomol/secret
10+
outputs_def:
11+
- handle: output
12+
json_schema: {}
13+
title: additional-block
14+
additional_inputs: true
15+
additional_outputs: true
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
nodes:
2+
- node_id: +python#1
3+
task:
4+
inputs_def: []
5+
outputs_def:
6+
- handle: output
7+
json_schema: {}
8+
- handle: output1
9+
json_schema: {}
10+
executor:
11+
name: python
12+
options:
13+
entry: scriptlets/+python#1.py
14+
- node_id: a#1
15+
task: self::additional
16+
inputs_from:
17+
- handle: input
18+
from_node:
19+
- node_id: +python#1
20+
output_handle: output
21+
- handle: input1
22+
from_node:
23+
- node_id: +python#1
24+
output_handle: output1
25+
inputs_def:
26+
- handle: input1
27+
json_schema: {}
28+
nullable: false
29+
outputs_def:
30+
- handle: output1
31+
json_schema: {}
32+
- node_id: end
33+
task:
34+
inputs_def:
35+
- handle: input
36+
json_schema: {}
37+
- handle: output1
38+
json_schema: {}
39+
nullable: false
40+
outputs_def:
41+
- handle: output
42+
json_schema: {}
43+
executor:
44+
name: python
45+
options:
46+
entry: scriptlets/+python#2.py
47+
inputs_from:
48+
- handle: input
49+
from_node:
50+
- node_id: a#1
51+
output_handle: output
52+
- handle: output1
53+
from_node:
54+
- node_id: a#1
55+
output_handle: output1
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from oocana import Context
2+
3+
4+
def main(inputs: dict, context: Context):
5+
return {
6+
"output": "hello",
7+
"output1": "world",
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from oocana import Context
2+
3+
4+
def main(inputs: dict, context: Context):
5+
print("end node inputs:", inputs)
6+
return {
7+
"output": inputs.get("input") + inputs.get("output1"),
8+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
nodes:
2+
- node_id: +python#1
3+
title: "Python #1"
4+
task:
5+
inputs_def:
6+
- handle: input
7+
json_schema: {}
8+
- handle: output
9+
json_schema:
10+
type: number
11+
nullable: false
12+
- handle: value1
13+
json_schema:
14+
type: number
15+
nullable: false
16+
outputs_def:
17+
- handle: output
18+
json_schema:
19+
type: number
20+
executor:
21+
name: python
22+
options:
23+
entry: scriptlets/+python#1.py
24+
inputs_from:
25+
- handle: input
26+
schema_overrides:
27+
- schema:
28+
type: number
29+
value: 2
30+
- handle: output
31+
from_node:
32+
- node_id: +python#2
33+
output_handle: output
34+
- handle: value1
35+
value: 5
36+
from_node:
37+
- node_id: +value#1
38+
output_handle: value1
39+
- node_id: +python#2
40+
title: "Python #2"
41+
task:
42+
inputs_def: []
43+
outputs_def:
44+
- handle: output
45+
json_schema:
46+
type: number
47+
executor:
48+
name: python
49+
options:
50+
entry: scriptlets/+python#2.py
51+
- node_id: +value#1
52+
title: "Value #1"
53+
values:
54+
- handle: value1
55+
json_schema:
56+
type: number
57+
value: 4
58+
- node_id: end
59+
title: "end"
60+
task:
61+
inputs_def:
62+
- handle: input
63+
json_schema: {}
64+
outputs_def:
65+
- handle: output
66+
json_schema: {}
67+
executor:
68+
name: python
69+
options:
70+
entry: scriptlets/+python#3.py
71+
inputs_from:
72+
- handle: input
73+
from_node:
74+
- node_id: +python#1
75+
output_handle: output
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from oocana import Context
2+
3+
def main(inputs: dict, context: Context):
4+
# Receives inputs from three sources:
5+
# - input: direct value (2)
6+
# - output: from +python#2 node (3)
7+
# - value1: from +value#1 node (4), with fallback value 5
8+
input_val = inputs.get("input")
9+
output_val = inputs.get("output")
10+
value1_val = inputs.get("value1")
11+
12+
result = input_val + output_val + value1_val
13+
print(f"Calculating: {input_val} + {output_val} + {value1_val} = {result}")
14+
15+
return {"output": result}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from oocana import Context
2+
3+
def main(inputs: dict, context: Context):
4+
# Simple node that outputs a number
5+
return {"output": 3}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from oocana import Context
2+
3+
def main(inputs: dict, context: Context):
4+
# End node: receives output from +python#1 and passes through
5+
return {"output": inputs.get("input")}

0 commit comments

Comments
 (0)