callman-core 1.0.2 → 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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runner/runCollection.d.ts.map +1 -1
- package/dist/runner/runCollection.js.map +1 -1
- package/dist/scenario-runner/condition.d.ts +67 -0
- package/dist/scenario-runner/condition.d.ts.map +1 -0
- package/dist/scenario-runner/condition.js +1514 -0
- package/dist/scenario-runner/condition.js.map +1 -0
- package/dist/scenario-runner/conditionTypes.d.ts +78 -0
- package/dist/scenario-runner/conditionTypes.d.ts.map +1 -0
- package/dist/scenario-runner/conditionTypes.js +2 -0
- package/dist/scenario-runner/conditionTypes.js.map +1 -0
- package/dist/scenario-runner/executionPolicyRunner.d.ts +25 -0
- package/dist/scenario-runner/executionPolicyRunner.d.ts.map +1 -0
- package/dist/scenario-runner/executionPolicyRunner.js +49 -0
- package/dist/scenario-runner/executionPolicyRunner.js.map +1 -0
- package/dist/scenario-runner/graphTraversal.d.ts +12 -0
- package/dist/scenario-runner/graphTraversal.d.ts.map +1 -0
- package/dist/scenario-runner/graphTraversal.js +21 -0
- package/dist/scenario-runner/graphTraversal.js.map +1 -0
- package/dist/scenario-runner/index.d.ts +8 -0
- package/dist/scenario-runner/index.d.ts.map +1 -0
- package/dist/scenario-runner/index.js +8 -0
- package/dist/scenario-runner/index.js.map +1 -0
- package/dist/scenario-runner/nodePolicy.d.ts +11 -0
- package/dist/scenario-runner/nodePolicy.d.ts.map +1 -0
- package/dist/scenario-runner/nodePolicy.js +108 -0
- package/dist/scenario-runner/nodePolicy.js.map +1 -0
- package/dist/scenario-runner/retryExecutor.d.ts +31 -0
- package/dist/scenario-runner/retryExecutor.d.ts.map +1 -0
- package/dist/scenario-runner/retryExecutor.js +53 -0
- package/dist/scenario-runner/retryExecutor.js.map +1 -0
- package/dist/scenario-runner/runScenario.d.ts +3 -0
- package/dist/scenario-runner/runScenario.d.ts.map +1 -0
- package/dist/scenario-runner/runScenario.js +1161 -0
- package/dist/scenario-runner/runScenario.js.map +1 -0
- package/dist/scenario-runner/templateResolver.d.ts +36 -0
- package/dist/scenario-runner/templateResolver.d.ts.map +1 -0
- package/dist/scenario-runner/templateResolver.js +440 -0
- package/dist/scenario-runner/templateResolver.js.map +1 -0
- package/dist/scenario-runner/types.d.ts +609 -0
- package/dist/scenario-runner/types.d.ts.map +1 -0
- package/dist/scenario-runner/types.js +2 -0
- package/dist/scenario-runner/types.js.map +1 -0
- package/dist/scenario-runner/waitNodeExecutor.d.ts +16 -0
- package/dist/scenario-runner/waitNodeExecutor.d.ts.map +1 -0
- package/dist/scenario-runner/waitNodeExecutor.js +65 -0
- package/dist/scenario-runner/waitNodeExecutor.js.map +1 -0
- package/package.json +3 -2
- package/tests/waitNode.test.mjs +311 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const MIN_SCENARIO_WAIT_DURATION_MS = 1;
|
|
2
|
+
export const MAX_SCENARIO_WAIT_DURATION_MS = 2_147_483_647;
|
|
3
|
+
const normalizeWaitDurationMs = (value) => Number.isFinite(value) ? Math.round(value) : Number.NaN;
|
|
4
|
+
const createWaitSummary = ({ configuredDurationMs, actualDurationMs, status, }) => ({
|
|
5
|
+
type: "wait",
|
|
6
|
+
configuredDurationMs,
|
|
7
|
+
actualDurationMs: Math.max(0, Math.round(actualDurationMs)),
|
|
8
|
+
status,
|
|
9
|
+
});
|
|
10
|
+
export class ScenarioWaitNodeValidationError extends Error {
|
|
11
|
+
summary;
|
|
12
|
+
constructor(durationMs) {
|
|
13
|
+
super(`Wait node duration must be a finite number between ${MIN_SCENARIO_WAIT_DURATION_MS} ms and ${MAX_SCENARIO_WAIT_DURATION_MS} ms.`);
|
|
14
|
+
this.name = "ScenarioWaitNodeValidationError";
|
|
15
|
+
this.summary = createWaitSummary({
|
|
16
|
+
configuredDurationMs: normalizeWaitDurationMs(durationMs),
|
|
17
|
+
actualDurationMs: 0,
|
|
18
|
+
status: "failed",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class ScenarioWaitNodeCancelledError extends Error {
|
|
23
|
+
summary;
|
|
24
|
+
constructor(durationMs, actualDurationMs) {
|
|
25
|
+
super("Wait node execution was cancelled.");
|
|
26
|
+
this.name = "ScenarioWaitNodeCancelledError";
|
|
27
|
+
this.summary = createWaitSummary({
|
|
28
|
+
configuredDurationMs: durationMs,
|
|
29
|
+
actualDurationMs,
|
|
30
|
+
status: "cancelled",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const waitWithSignal = (durationMs, signal, startedAtMs) => new Promise((resolve, reject) => {
|
|
35
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
36
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
37
|
+
resolve();
|
|
38
|
+
}, durationMs);
|
|
39
|
+
const onAbort = () => {
|
|
40
|
+
globalThis.clearTimeout(timeoutId);
|
|
41
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
42
|
+
reject(new ScenarioWaitNodeCancelledError(durationMs, Date.now() - startedAtMs));
|
|
43
|
+
};
|
|
44
|
+
if (signal?.aborted) {
|
|
45
|
+
onAbort();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
49
|
+
});
|
|
50
|
+
export const executeWaitNode = async ({ durationMs, signal, }) => {
|
|
51
|
+
const normalizedDurationMs = normalizeWaitDurationMs(durationMs);
|
|
52
|
+
if (!Number.isFinite(normalizedDurationMs) ||
|
|
53
|
+
normalizedDurationMs < MIN_SCENARIO_WAIT_DURATION_MS ||
|
|
54
|
+
normalizedDurationMs > MAX_SCENARIO_WAIT_DURATION_MS) {
|
|
55
|
+
throw new ScenarioWaitNodeValidationError(durationMs);
|
|
56
|
+
}
|
|
57
|
+
const startedAtMs = Date.now();
|
|
58
|
+
await waitWithSignal(normalizedDurationMs, signal, startedAtMs);
|
|
59
|
+
return createWaitSummary({
|
|
60
|
+
configuredDurationMs: normalizedDurationMs,
|
|
61
|
+
actualDurationMs: Date.now() - startedAtMs,
|
|
62
|
+
status: "success",
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=waitNodeExecutor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"waitNodeExecutor.js","sourceRoot":"","sources":["../../src/scenario-runner/waitNodeExecutor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "callman-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc -p tsconfig.json",
|
|
9
|
-
"sample": "node samples/runSample.mjs"
|
|
9
|
+
"sample": "node samples/runSample.mjs",
|
|
10
|
+
"test": "npm run build && node --test tests/*.test.mjs"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"axios": "^1.6.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
|
+
});
|