-
Notifications
You must be signed in to change notification settings - Fork 236
Expand file tree
/
Copy pathworkflows-extractor.test.ts
More file actions
128 lines (113 loc) · 4.88 KB
/
workflows-extractor.test.ts
File metadata and controls
128 lines (113 loc) · 4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { extractWorkflowGraphs } from './workflows-extractor.js';
async function createWorkflowBundleFile(
workflowCode: string
): Promise<{ filePath: string; tempDir: string }> {
const tempDir = await mkdtemp(join(tmpdir(), 'workflow-extractor-'));
const filePath = join(tempDir, 'route.js');
const escapedWorkflowCode = workflowCode.replace(/[\\`$]/g, '\\$&');
const bundleCode = `const workflowCode = \`${escapedWorkflowCode}\`;
export const POST = workflowCode;`;
await writeFile(filePath, bundleCode, 'utf8');
return { filePath, tempDir };
}
describe('workflows-extractor', () => {
const tempDirs: string[] = [];
afterEach(async () => {
await Promise.all(
tempDirs
.splice(0)
.map((tempDir) => rm(tempDir, { recursive: true, force: true }))
);
});
it('detects step declarations that include @__PURE__ annotations', async () => {
const workflowCode = `
var stepWithPure = globalThis[/* @__PURE__ */ Symbol.for("WORKFLOW_USE_STEP")]("step//./workflows/demo//stepWithPure");
async function demo() {
const value = await stepWithPure();
return value;
}
demo.workflowId = "workflow//./workflows/demo//demo";
globalThis.__private_workflows.set("workflow//./workflows/demo//demo", demo);
`;
const { filePath, tempDir } = await createWorkflowBundleFile(workflowCode);
tempDirs.push(tempDir);
const graphs = await extractWorkflowGraphs(filePath);
const demoGraph = graphs['./workflows/demo']?.demo?.graph;
const labels = (demoGraph?.nodes || []).map((node) => node.data.label);
expect(labels).toContain('stepWithPure');
});
it('detects createHook wrapped by transpiled using helpers inside try/finally', async () => {
const workflowCode = `
var stepA = globalThis[/* @__PURE__ */ Symbol.for("WORKFLOW_USE_STEP")]("step//./workflows/hooks//stepA");
function _ts_add_disposable_resource(_env, value, _isAsync) {
return value;
}
function _ts_dispose_resources(_env) {}
async function withHook() {
const env = { stack: [] };
try {
const responseId = await stepA();
const hook = _ts_add_disposable_resource(env, createHook({ token: 'hook:' + responseId }), false);
const payload = await hook;
return payload;
} finally {
_ts_dispose_resources(env);
}
}
withHook.workflowId = "workflow//./workflows/hooks//withHook";
globalThis.__private_workflows.set("workflow//./workflows/hooks//withHook", withHook);
`;
const { filePath, tempDir } = await createWorkflowBundleFile(workflowCode);
tempDirs.push(tempDir);
const graphs = await extractWorkflowGraphs(filePath);
const hookGraph = graphs['./workflows/hooks']?.withHook?.graph;
const labels = (hookGraph?.nodes || []).map((node) => node.data.label);
expect(labels).toEqual(
expect.arrayContaining(['stepA', 'createHook', 'awaitWebhook'])
);
});
it('detects hook.create calls used directly inside Promise.race', async () => {
const workflowCode = `
async function waitForAuthWorkflow() {
const session = await Promise.race([
authCompleteHook.create({ token: 'auth:demo' }),
sleep('1h').then(() => null),
]);
return session;
}
waitForAuthWorkflow.workflowId = "workflow//./workflows/hooks//waitForAuthWorkflow";
globalThis.__private_workflows.set("workflow//./workflows/hooks//waitForAuthWorkflow", waitForAuthWorkflow);
`;
const { filePath, tempDir } = await createWorkflowBundleFile(workflowCode);
tempDirs.push(tempDir);
const graphs = await extractWorkflowGraphs(filePath);
const hookGraph = graphs['./workflows/hooks']?.waitForAuthWorkflow?.graph;
const labels = (hookGraph?.nodes || []).map((node) => node.data.label);
expect(labels).toEqual(expect.arrayContaining(['createHook']));
});
it('detects for-await hook consumption when hook is created via hook.create', async () => {
const workflowCode = `
var processPayload = globalThis[/* @__PURE__ */ Symbol.for("WORKFLOW_USE_STEP")]("step//./workflows/hooks//processPayload");
async function streamHookWorkflow() {
const hook = activeSubagentRunHook.create({ token: 'stream:demo' });
for await (const payload of hook) {
await processPayload(payload);
}
}
streamHookWorkflow.workflowId = "workflow//./workflows/hooks//streamHookWorkflow";
globalThis.__private_workflows.set("workflow//./workflows/hooks//streamHookWorkflow", streamHookWorkflow);
`;
const { filePath, tempDir } = await createWorkflowBundleFile(workflowCode);
tempDirs.push(tempDir);
const graphs = await extractWorkflowGraphs(filePath);
const hookGraph = graphs['./workflows/hooks']?.streamHookWorkflow?.graph;
const labels = (hookGraph?.nodes || []).map((node) => node.data.label);
expect(labels).toEqual(
expect.arrayContaining(['processPayload', 'createHook', 'awaitWebhook'])
);
});
});