callman-core 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/scenario-runner/condition.d.ts.map +1 -1
  2. package/dist/scenario-runner/condition.js +6 -0
  3. package/dist/scenario-runner/condition.js.map +1 -1
  4. package/dist/scenario-runner/executionPolicyRunner.d.ts +2 -1
  5. package/dist/scenario-runner/executionPolicyRunner.d.ts.map +1 -1
  6. package/dist/scenario-runner/executionPolicyRunner.js +2 -1
  7. package/dist/scenario-runner/executionPolicyRunner.js.map +1 -1
  8. package/dist/scenario-runner/index.d.ts +1 -0
  9. package/dist/scenario-runner/index.d.ts.map +1 -1
  10. package/dist/scenario-runner/index.js +1 -0
  11. package/dist/scenario-runner/index.js.map +1 -1
  12. package/dist/scenario-runner/nodePolicy.d.ts +2 -2
  13. package/dist/scenario-runner/nodePolicy.d.ts.map +1 -1
  14. package/dist/scenario-runner/nodePolicy.js +1 -0
  15. package/dist/scenario-runner/nodePolicy.js.map +1 -1
  16. package/dist/scenario-runner/retryExecutor.d.ts +2 -1
  17. package/dist/scenario-runner/retryExecutor.d.ts.map +1 -1
  18. package/dist/scenario-runner/retryExecutor.js +4 -1
  19. package/dist/scenario-runner/retryExecutor.js.map +1 -1
  20. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  21. package/dist/scenario-runner/runScenario.js +28 -0
  22. package/dist/scenario-runner/runScenario.js.map +1 -1
  23. package/dist/scenario-runner/types.d.ts +16 -3
  24. package/dist/scenario-runner/types.d.ts.map +1 -1
  25. package/dist/scenario-runner/waitNodeExecutor.d.ts +16 -0
  26. package/dist/scenario-runner/waitNodeExecutor.d.ts.map +1 -0
  27. package/dist/scenario-runner/waitNodeExecutor.js +65 -0
  28. package/dist/scenario-runner/waitNodeExecutor.js.map +1 -0
  29. package/package.json +3 -2
  30. package/tests/waitNode.test.mjs +311 -0
@@ -0,0 +1,311 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { runScenario } from "../dist/index.js";
5
+
6
+ const STRICT_POLICY = {
7
+ onFailure: "stop",
8
+ retry: {
9
+ enabled: false,
10
+ maxAttempts: 0,
11
+ delayMs: 0,
12
+ },
13
+ };
14
+
15
+ const createWaitNode = (id, durationMs, label = "Wait") => ({
16
+ id,
17
+ type: "wait",
18
+ position: { x: 0, y: 0 },
19
+ data: {
20
+ label,
21
+ config: {
22
+ durationMs,
23
+ executionPolicy: STRICT_POLICY,
24
+ },
25
+ },
26
+ });
27
+
28
+ const createScriptNode = (id, label = "Script") => ({
29
+ id,
30
+ type: "script",
31
+ position: { x: 240, y: 0 },
32
+ data: {
33
+ label,
34
+ config: {
35
+ script: "return { ok: true };",
36
+ timeoutMs: 3000,
37
+ executionPolicy: STRICT_POLICY,
38
+ },
39
+ },
40
+ });
41
+
42
+ const createEndNode = (id, label = "End") => ({
43
+ id,
44
+ type: "end",
45
+ position: { x: 480, y: 0 },
46
+ data: {
47
+ label,
48
+ config: {
49
+ loop: false,
50
+ loopTargetNodeId: null,
51
+ loopIterations: 1,
52
+ loopDelayMs: 0,
53
+ executionPolicy: STRICT_POLICY,
54
+ },
55
+ },
56
+ });
57
+
58
+ const createConditionNode = (id, label = "Condition") => ({
59
+ id,
60
+ type: "condition",
61
+ position: { x: 0, y: 0 },
62
+ data: {
63
+ label,
64
+ config: {
65
+ mode: "builder",
66
+ expertExpression: 'env.runWait == "yes"',
67
+ rootGroup: {
68
+ id: `${id}-group`,
69
+ type: "group",
70
+ operator: "AND",
71
+ children: [
72
+ {
73
+ id: `${id}-rule`,
74
+ type: "rule",
75
+ left: {
76
+ mode: "runtime",
77
+ valueType: "string",
78
+ value: "env.runWait",
79
+ },
80
+ operator: "equals",
81
+ right: {
82
+ mode: "literal",
83
+ valueType: "string",
84
+ value: "yes",
85
+ },
86
+ },
87
+ ],
88
+ },
89
+ },
90
+ },
91
+ });
92
+
93
+ const createRuntimeAdapters = ({ onScript } = {}) => ({
94
+ requestExecutor: async () => {
95
+ throw new Error("Request executor should not be called in wait node tests.");
96
+ },
97
+ dbExecutor: async () => ({
98
+ rawResult: {
99
+ success: true,
100
+ rows: [],
101
+ rowCount: 0,
102
+ },
103
+ dbResult: null,
104
+ outputData: null,
105
+ }),
106
+ redisExecutor: async () => ({
107
+ commandResults: [],
108
+ redisResult: null,
109
+ outputData: null,
110
+ }),
111
+ scriptExecutor: async ({ node }) => {
112
+ onScript?.(node);
113
+
114
+ return {
115
+ variableMutations: [],
116
+ testResults: [],
117
+ logs: [],
118
+ error: null,
119
+ hasReturnValue: true,
120
+ returnValue: { ok: true },
121
+ outputData: { ok: true },
122
+ scriptSource: node.data.config.script,
123
+ timeoutMs: node.data.config.timeoutMs ?? 3000,
124
+ };
125
+ },
126
+ kafkaExecutor: async () => ({
127
+ captureResult: {
128
+ topic: "test",
129
+ durationMs: 0,
130
+ timeoutMs: 0,
131
+ messages: [],
132
+ timedOut: false,
133
+ errorMessage: null,
134
+ },
135
+ }),
136
+ });
137
+
138
+ const findNodeExecution = (report, stepId) => {
139
+ const execution = report.nodeExecutions.find((entry) => entry.stepId === stepId);
140
+ assert.ok(execution, `Expected node execution record for ${stepId}.`);
141
+ return execution;
142
+ };
143
+
144
+ test("wait node delays execution and reports timing summary", async () => {
145
+ const durationMs = 50;
146
+ const scriptStartsAt = [];
147
+ const scenario = {
148
+ id: "scenario-wait-success",
149
+ name: "Wait Success",
150
+ nodes: [
151
+ createWaitNode("wait-1", durationMs, "Pause"),
152
+ createScriptNode("script-1", "AfterWait"),
153
+ ],
154
+ edges: [
155
+ {
156
+ id: "edge-wait-script",
157
+ source: "wait-1",
158
+ target: "script-1",
159
+ type: "default",
160
+ },
161
+ ],
162
+ };
163
+
164
+ const startedAtMs = Date.now();
165
+ const report = await runScenario({
166
+ scenario,
167
+ runtimeAdapters: createRuntimeAdapters({
168
+ onScript: () => {
169
+ scriptStartsAt.push(Date.now());
170
+ },
171
+ }),
172
+ });
173
+
174
+ assert.equal(report.scenarioStatus, "success");
175
+ assert.equal(scriptStartsAt.length, 1);
176
+
177
+ const waitExecution = findNodeExecution(report, "wait-1");
178
+ assert.equal(waitExecution.status, "success");
179
+ assert.equal(waitExecution.outputData?.type, "wait");
180
+ assert.equal(waitExecution.outputData?.configuredDurationMs, durationMs);
181
+ assert.equal(waitExecution.outputData?.status, "success");
182
+ assert.ok(waitExecution.outputData.actualDurationMs >= durationMs - 10);
183
+ assert.ok(scriptStartsAt[0] - startedAtMs >= durationMs - 10);
184
+ assert.ok(
185
+ report.events.some(
186
+ (event) => event.type === "step:start" && event.stepId === "wait-1",
187
+ ),
188
+ );
189
+ assert.ok(
190
+ report.events.some(
191
+ (event) => event.type === "step:success" && event.stepId === "wait-1",
192
+ ),
193
+ );
194
+ });
195
+
196
+ test("wait node rejects zero duration", async () => {
197
+ const scenario = {
198
+ id: "scenario-wait-invalid",
199
+ name: "Wait Invalid",
200
+ nodes: [createWaitNode("wait-1", 0, "InvalidWait")],
201
+ edges: [],
202
+ };
203
+
204
+ const report = await runScenario({
205
+ scenario,
206
+ runtimeAdapters: createRuntimeAdapters(),
207
+ });
208
+
209
+ const waitExecution = findNodeExecution(report, "wait-1");
210
+
211
+ assert.equal(report.scenarioStatus, "failed");
212
+ assert.equal(waitExecution.status, "failed");
213
+ assert.match(waitExecution.errorMessage ?? "", /Wait node duration must be a finite number/);
214
+ assert.equal(waitExecution.outputData?.type, "wait");
215
+ assert.equal(waitExecution.outputData?.status, "failed");
216
+ assert.equal(waitExecution.outputData?.actualDurationMs, 0);
217
+ });
218
+
219
+ test("wait node executes correctly inside a condition branch", async () => {
220
+ const scriptStartsAt = [];
221
+ const scenario = {
222
+ id: "scenario-wait-branch",
223
+ name: "Wait Branch",
224
+ nodes: [
225
+ createConditionNode("condition-1", "CheckFlag"),
226
+ createWaitNode("wait-1", 25, "BranchWait"),
227
+ createScriptNode("script-1", "BranchScript"),
228
+ createEndNode("end-1", "BranchEnd"),
229
+ ],
230
+ edges: [
231
+ {
232
+ id: "edge-condition-yes",
233
+ source: "condition-1",
234
+ target: "wait-1",
235
+ type: "yes",
236
+ },
237
+ {
238
+ id: "edge-wait-script",
239
+ source: "wait-1",
240
+ target: "script-1",
241
+ type: "default",
242
+ },
243
+ {
244
+ id: "edge-condition-no",
245
+ source: "condition-1",
246
+ target: "end-1",
247
+ type: "no",
248
+ },
249
+ ],
250
+ };
251
+
252
+ const report = await runScenario({
253
+ scenario,
254
+ environment: {
255
+ runWait: "yes",
256
+ },
257
+ runtimeAdapters: createRuntimeAdapters({
258
+ onScript: () => {
259
+ scriptStartsAt.push(Date.now());
260
+ },
261
+ }),
262
+ });
263
+
264
+ assert.equal(report.scenarioStatus, "success");
265
+ assert.equal(scriptStartsAt.length, 1);
266
+ assert.equal(findNodeExecution(report, "condition-1").status, "success");
267
+ assert.equal(findNodeExecution(report, "wait-1").status, "success");
268
+ assert.equal(findNodeExecution(report, "script-1").status, "success");
269
+ assert.equal(findNodeExecution(report, "end-1").status, "skipped");
270
+ });
271
+
272
+ test("wait node captures cancellation and stops the scenario", async () => {
273
+ const controller = new AbortController();
274
+ const abortTimer = setTimeout(() => controller.abort(), 30);
275
+ const scenario = {
276
+ id: "scenario-wait-cancelled",
277
+ name: "Wait Cancelled",
278
+ nodes: [
279
+ createWaitNode("wait-1", 200, "CancelableWait"),
280
+ createScriptNode("script-1", "ShouldNotRun"),
281
+ ],
282
+ edges: [
283
+ {
284
+ id: "edge-wait-script",
285
+ source: "wait-1",
286
+ target: "script-1",
287
+ type: "default",
288
+ },
289
+ ],
290
+ };
291
+
292
+ try {
293
+ const report = await runScenario({
294
+ scenario,
295
+ runtimeAdapters: createRuntimeAdapters(),
296
+ signal: controller.signal,
297
+ });
298
+
299
+ const waitExecution = findNodeExecution(report, "wait-1");
300
+ const scriptExecution = findNodeExecution(report, "script-1");
301
+
302
+ assert.equal(report.scenarioStatus, "stopped");
303
+ assert.equal(waitExecution.status, "skipped");
304
+ assert.equal(waitExecution.outputData?.type, "wait");
305
+ assert.equal(waitExecution.outputData?.status, "cancelled");
306
+ assert.ok(waitExecution.outputData.actualDurationMs < 200);
307
+ assert.equal(scriptExecution.status, "skipped");
308
+ } finally {
309
+ clearTimeout(abortTimer);
310
+ }
311
+ });