/** * 三型分支零件「真的接上引擎」的實測(SDD workflow-discovery 3.11) * * 為什麼要另立這一支(總管 08-01 抽驗要求,正確的要求): * conditional-edges.test.ts 是用 Input 節點**手餵分支形狀**測引擎走邊邏輯, * 那證明的是「引擎依標籤選邊」,**沒有證明「真零件吐出來的標籤真的對得上」**。 * leo 特別點名 switch/try_catch,且 `ON_CASE`/`ON_CATCH` grep=0 * ⇒ 必須排除「機制通用所以理論上支援」這種推論。 * * 本檔的 given 全部是**真 WASM 零件的實跑輸出**(wasmtime 執行 .component-builds/*.wasm * 抓回來的原文,非杜撰),再送進引擎驗證走對邊。 * * 真零件實跑指令(可復驗): * cd .component-builds * echo '{"condition":"status == active","input":{"status":"active"}}' | wasmtime if_control/component.wasm * echo '{"value":"pending","cases":[...],"default_branch":"branch_default"}' | wasmtime switch/component.wasm * echo '{"result":null,"error":"boom"}' | wasmtime try_catch/component.wasm */ import { SELF } from 'cloudflare:test'; import { describe, it, expect } from 'vitest'; async function run(graph: unknown) { const res = await SELF.fetch('http://localhost/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ graph, context: {} }), }); const body = (await res.json()) as { success: boolean; trace?: Array<{ nodeId: string }>; }; return { body, visited: (body.trace ?? []).map(t => t.nodeId) }; } /** 真零件輸出 → 當作上游節點的 output 餵進圖 */ function graphWith(realOutput: unknown, edges: Array>, extraNodes: string[]) { return { id: 'real-branch', name: '真零件輸出走邊', nodes: [ { id: 'ctrl', type: 'Input', data: realOutput }, ...extraNodes.map(id => ({ id, type: 'Component', componentId: 'comp_uppercase', data: { text: id }, })), ], edges, }; } describe('if_control 真輸出 → 引擎走對邊', () => { // 真跑:echo '{"condition":"status == active","input":{"status":"active"}}' | wasmtime if_control/component.wasm const REAL_TRUE = { data: { branch: 'true', result: true }, success: true }; // 真跑:input.status = "inactive" const REAL_FALSE = { data: { branch: 'false', result: false }, success: true }; const edges = [ { from: 'ctrl', to: 'yes', type: 'ON_TRUE' }, { from: 'ctrl', to: 'no', type: 'ON_FALSE' }, ]; it('條件成立(真輸出 branch="true")→ 走 ON_TRUE', async () => { const { body, visited } = await run(graphWith(REAL_TRUE, edges, ['yes', 'no'])); expect(body.success).toBe(true); expect(visited).toContain('yes'); expect(visited).not.toContain('no'); }); it('條件不成立(真輸出 branch="false")→ 走 ON_FALSE', async () => { const { visited } = await run(graphWith(REAL_FALSE, edges, ['yes', 'no'])); expect(visited).toContain('no'); expect(visited).not.toContain('yes'); }); }); describe('switch 真輸出 → 引擎走對邊(多路+default,leo:「switch 更嚴重」)', () => { // 真跑(三個 case + default_branch): // value="active" → {"data":{"branch":"branch_active"},"success":true} // value="pending" → {"data":{"branch":"branch_pending"},"success":true} // value="zzz" → {"data":{"branch":"branch_default"},"success":true} const REAL_CASE1 = { data: { branch: 'branch_active' }, success: true }; const REAL_CASE3 = { data: { branch: 'branch_pending' }, success: true }; const REAL_DEFAULT = { data: { branch: 'branch_default' }, success: true }; const targets = ['p_active', 'p_inactive', 'p_pending', 'p_default']; const edges = [ { from: 'ctrl', to: 'p_active', type: 'ON_BRANCH', branch: 'branch_active' }, { from: 'ctrl', to: 'p_inactive', type: 'ON_BRANCH', branch: 'branch_inactive' }, { from: 'ctrl', to: 'p_pending', type: 'ON_BRANCH', branch: 'branch_pending' }, { from: 'ctrl', to: 'p_default', type: 'ON_BRANCH', branch: 'branch_default' }, ]; it('第 1 條 case(真輸出 branch_active)→ 只走 p_active', async () => { const { body, visited } = await run(graphWith(REAL_CASE1, edges, targets)); expect(body.success).toBe(true); expect(visited).toContain('p_active'); expect(visited).not.toContain('p_inactive'); expect(visited).not.toContain('p_pending'); expect(visited).not.toContain('p_default'); }); it('第 3 條 case(真輸出 branch_pending)→ 只走 p_pending(證明第 N 條路走得對)', async () => { const { visited } = await run(graphWith(REAL_CASE3, edges, targets)); expect(visited).toContain('p_pending'); expect(visited).not.toContain('p_active'); expect(visited).not.toContain('p_inactive'); expect(visited).not.toContain('p_default'); }); it('無匹配(真輸出 branch_default)→ 只走 p_default', async () => { const { visited } = await run(graphWith(REAL_DEFAULT, edges, targets)); expect(visited).toContain('p_default'); expect(visited).not.toContain('p_active'); expect(visited).not.toContain('p_pending'); }); }); describe('try_catch 真輸出 → 引擎走對邊(ok/catch 兩路都驗)', () => { // 真跑:echo '{"result":{"value":42},"error":""}' | wasmtime try_catch/component.wasm const REAL_TRY = { data: { branch: 'try', result: { value: 42 } }, success: true }; // 真跑:echo '{"result":null,"error":"boom"}' | wasmtime try_catch/component.wasm const REAL_CATCH = { data: { branch: 'catch', error: 'boom' }, success: true }; const edges = [ { from: 'ctrl', to: 'normal', type: 'ON_BRANCH', branch: 'try' }, { from: 'ctrl', to: 'rescue', type: 'ON_BRANCH', branch: 'catch' }, ]; it('成功(真輸出 branch="try")→ 走 normal,不走 rescue', async () => { const { body, visited } = await run(graphWith(REAL_TRY, edges, ['normal', 'rescue'])); expect(body.success).toBe(true); expect(visited).toContain('normal'); expect(visited).not.toContain('rescue'); }); it('失敗(真輸出 branch="catch")→ 走 rescue,不走 normal', async () => { const { visited } = await run(graphWith(REAL_CATCH, edges, ['normal', 'rescue'])); expect(visited).toContain('rescue'); expect(visited).not.toContain('normal'); }); it('try_catch 的 catch 路承接了「上游失敗」——不必寫 code try 一遍', async () => { // 這是 leo 點名 try_catch 的原因:schema 用文字寫「走 catch 分支」但機器層沒有那條路。 // 現在有了:catch 標籤 → ON_BRANCH branch="catch" → 補救節點。 const { visited } = await run(graphWith(REAL_CATCH, edges, ['normal', 'rescue'])); expect(visited).toContain('rescue'); }); });