callman-core 1.18.0 → 1.19.0
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/scenario-runner/adapters/index.d.ts +1 -0
- package/dist/scenario-runner/adapters/index.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/index.js +3 -0
- package/dist/scenario-runner/adapters/index.js.map +1 -1
- package/dist/scenario-runner/adapters/types.d.ts +1 -0
- package/dist/scenario-runner/adapters/types.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/websocket-executor.d.ts +4 -0
- package/dist/scenario-runner/adapters/websocket-executor.d.ts.map +1 -0
- package/dist/scenario-runner/adapters/websocket-executor.js +20 -0
- package/dist/scenario-runner/adapters/websocket-executor.js.map +1 -0
- package/dist/scenario-runner/condition.d.ts.map +1 -1
- package/dist/scenario-runner/condition.js +11 -0
- package/dist/scenario-runner/condition.js.map +1 -1
- package/dist/scenario-runner/graphTraversal.d.ts +8 -0
- package/dist/scenario-runner/graphTraversal.d.ts.map +1 -1
- package/dist/scenario-runner/graphTraversal.js +9 -0
- package/dist/scenario-runner/graphTraversal.js.map +1 -1
- package/dist/scenario-runner/index.d.ts +1 -0
- package/dist/scenario-runner/index.d.ts.map +1 -1
- package/dist/scenario-runner/index.js +1 -0
- package/dist/scenario-runner/index.js.map +1 -1
- package/dist/scenario-runner/nodePolicy.d.ts +2 -2
- package/dist/scenario-runner/nodePolicy.d.ts.map +1 -1
- package/dist/scenario-runner/nodePolicy.js +2 -0
- package/dist/scenario-runner/nodePolicy.js.map +1 -1
- package/dist/scenario-runner/runScenario.d.ts.map +1 -1
- package/dist/scenario-runner/runScenario.js +160 -1
- package/dist/scenario-runner/runScenario.js.map +1 -1
- package/dist/scenario-runner/types.d.ts +115 -3
- package/dist/scenario-runner/types.d.ts.map +1 -1
- package/dist/scenario-runner/websocket.d.ts +17 -0
- package/dist/scenario-runner/websocket.d.ts.map +1 -0
- package/dist/scenario-runner/websocket.js +74 -0
- package/dist/scenario-runner/websocket.js.map +1 -0
- package/dist-cjs/scenario-runner/adapters/index.js +5 -1
- package/dist-cjs/scenario-runner/adapters/index.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/websocket-executor.js +24 -0
- package/dist-cjs/scenario-runner/adapters/websocket-executor.js.map +1 -0
- package/dist-cjs/scenario-runner/condition.js +11 -0
- package/dist-cjs/scenario-runner/condition.js.map +1 -1
- package/dist-cjs/scenario-runner/graphTraversal.js +10 -1
- package/dist-cjs/scenario-runner/graphTraversal.js.map +1 -1
- package/dist-cjs/scenario-runner/index.js +1 -0
- package/dist-cjs/scenario-runner/index.js.map +1 -1
- package/dist-cjs/scenario-runner/nodePolicy.js +2 -0
- package/dist-cjs/scenario-runner/nodePolicy.js.map +1 -1
- package/dist-cjs/scenario-runner/runScenario.js +159 -0
- package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
- package/dist-cjs/scenario-runner/websocket.js +79 -0
- package/dist-cjs/scenario-runner/websocket.js.map +1 -0
- package/package.json +1 -1
|
@@ -48,6 +48,8 @@ const RECOMMENDED_NEW_NODE_POLICY_PRESETS = {
|
|
|
48
48
|
// The item loop IS the repeat mechanism for this node type — retry stays
|
|
49
49
|
// off (and is force-disabled at execution time in runScenario).
|
|
50
50
|
foreach: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
|
|
51
|
+
// Retry re-runs the WHOLE conversation (connect -> steps -> close).
|
|
52
|
+
websocket: STRICT_SCENARIO_NODE_EXECUTION_POLICY,
|
|
51
53
|
};
|
|
52
54
|
const clonePolicy = (policy) => ({
|
|
53
55
|
onFailure: policy.onFailure,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodePolicy.js","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"nodePolicy.js","sourceRoot":"","sources":["../../src/scenario-runner/nodePolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runScenario.d.ts","sourceRoot":"","sources":["../../src/scenario-runner/runScenario.ts"],"names":[],"mappings":"AAyDA,OAAO,KAAK,EAKV,kBAAkB,EAUlB,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AAyuBpB,eAAO,MAAM,WAAW,GAAU,mGAQ/B,kBAAkB,KAAG,OAAO,CAAC,iBAAiB,CA06EhD,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { buildConditionOutputPayload, evaluateConditionConfig, evaluateConditionRule, resolveConditionRuntimeValue, validateConditionConfig, } from "./condition.js";
|
|
2
2
|
import { applyExtract } from "./extraction.js";
|
|
3
3
|
import { evaluateAssertionConfig, validateAssertionConfig, } from "./assertion.js";
|
|
4
|
-
import { createSubScenarioStepId, buildRuntimeStepDescriptors, DEFAULT_END_LOOP_DELAY_MS, DEFAULT_END_LOOP_ITERATIONS, flattenScenarioNodesForContext, MAX_END_LOOP_ITERATIONS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_MAX_ATTEMPTS, MAX_POLL_ATTEMPTS, DEFAULT_FOREACH_MAX_ITERATIONS, MAX_FOREACH_ITERATIONS, } from "./graphTraversal.js";
|
|
4
|
+
import { createSubScenarioStepId, buildRuntimeStepDescriptors, DEFAULT_END_LOOP_DELAY_MS, DEFAULT_END_LOOP_ITERATIONS, flattenScenarioNodesForContext, MAX_END_LOOP_ITERATIONS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_MAX_ATTEMPTS, MAX_POLL_ATTEMPTS, DEFAULT_FOREACH_MAX_ITERATIONS, MAX_FOREACH_ITERATIONS, DEFAULT_WS_CONNECT_TIMEOUT_MS, MAX_WS_CONNECT_TIMEOUT_MS, DEFAULT_WS_EXPECT_TIMEOUT_MS, MAX_WS_EXPECT_TIMEOUT_MS, MAX_WS_WAIT_DURATION_MS, MAX_WS_STEPS, DEFAULT_WS_CLOSE_CODE, } from "./graphTraversal.js";
|
|
5
|
+
import { evaluateWebsocketExpect } from "./websocket.js";
|
|
5
6
|
import { executeWithScenarioNodePolicy } from "./executionPolicyRunner.js";
|
|
6
7
|
import { getLegacyScenarioNodeExecutionPolicy, getScenarioNodeExecutionPolicy, supportsScenarioNodeExecutionPolicy, } from "./nodePolicy.js";
|
|
7
8
|
import { buildScenarioExecutionGraph, buildScenarioTemplateValues, collectExclusiveBranchNodeIds, evaluateScenarioExpression, resolveScenarioJsonTemplateString, resolveScenarioTemplateString, } from "./templateResolver.js";
|
|
@@ -44,6 +45,7 @@ const RESERVED_WORKFLOW_ROOT_LABELS = new Set([
|
|
|
44
45
|
"item",
|
|
45
46
|
"each",
|
|
46
47
|
"eaches",
|
|
48
|
+
"ws",
|
|
47
49
|
]);
|
|
48
50
|
const cloneNodeExecutionMeta = (meta) => ({
|
|
49
51
|
...meta,
|
|
@@ -1445,6 +1447,163 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
|
|
|
1445
1447
|
outputData = runtimeContext.kafkaEvent;
|
|
1446
1448
|
}
|
|
1447
1449
|
}
|
|
1450
|
+
if (node.type === "websocket") {
|
|
1451
|
+
const wsConfig = node.data.config;
|
|
1452
|
+
const resolvedUrl = resolveScenarioTemplateString(wsConfig.url ?? "", templateValues).trim();
|
|
1453
|
+
if (!resolvedUrl) {
|
|
1454
|
+
throw new ScenarioNodeFailureError("WebSocket URL is required.");
|
|
1455
|
+
}
|
|
1456
|
+
const configuredSteps = wsConfig.steps ?? [];
|
|
1457
|
+
if (configuredSteps.length > MAX_WS_STEPS) {
|
|
1458
|
+
throw new ScenarioNodeFailureError(`WebSocket conversations are capped at ${MAX_WS_STEPS} steps.`);
|
|
1459
|
+
}
|
|
1460
|
+
for (const [stepIndex, step] of configuredSteps.entries()) {
|
|
1461
|
+
if (step.kind === "expect" && (!step.rules || step.rules.length === 0)) {
|
|
1462
|
+
throw new ScenarioNodeFailureError(`WebSocket expect step ${stepIndex + 1} has no rules.`);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
const resolvedHeaders = (wsConfig.headers ?? [])
|
|
1466
|
+
.filter((header) => header.enabled !== false && header.key.trim())
|
|
1467
|
+
.map((header) => ({
|
|
1468
|
+
key: resolveScenarioTemplateString(header.key, templateValues).trim(),
|
|
1469
|
+
value: resolveScenarioTemplateString(header.value ?? "", templateValues),
|
|
1470
|
+
}));
|
|
1471
|
+
const resolvedSubprotocols = (wsConfig.subprotocols ?? [])
|
|
1472
|
+
.map((protocol) => resolveScenarioTemplateString(protocol, templateValues).trim())
|
|
1473
|
+
.filter((protocol) => protocol.length > 0);
|
|
1474
|
+
const connectTimeoutMs = Math.min(MAX_WS_CONNECT_TIMEOUT_MS, Math.max(100, Math.round(wsConfig.connectTimeoutMs ?? DEFAULT_WS_CONNECT_TIMEOUT_MS)));
|
|
1475
|
+
const resolvedSteps = configuredSteps.map((step) => {
|
|
1476
|
+
if (step.kind === "send") {
|
|
1477
|
+
return {
|
|
1478
|
+
id: step.id,
|
|
1479
|
+
kind: "send",
|
|
1480
|
+
payload: resolveScenarioJsonTemplateString(step.payload ?? "", templateValues),
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
if (step.kind === "wait") {
|
|
1484
|
+
return {
|
|
1485
|
+
id: step.id,
|
|
1486
|
+
kind: "wait",
|
|
1487
|
+
durationMs: Math.min(MAX_WS_WAIT_DURATION_MS, Math.max(0, Math.round(step.durationMs ?? 0))),
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
return {
|
|
1491
|
+
id: step.id,
|
|
1492
|
+
kind: "expect",
|
|
1493
|
+
timeoutMs: Math.min(MAX_WS_EXPECT_TIMEOUT_MS, Math.max(100, Math.round(step.timeoutMs ?? DEFAULT_WS_EXPECT_TIMEOUT_MS))),
|
|
1494
|
+
matchMode: step.matchMode === "any" ? "any" : "all",
|
|
1495
|
+
rules: step.rules,
|
|
1496
|
+
};
|
|
1497
|
+
});
|
|
1498
|
+
const wsExecution = await runtimeAdapters.websocketExecutor({
|
|
1499
|
+
runId,
|
|
1500
|
+
stepId,
|
|
1501
|
+
node,
|
|
1502
|
+
resolvedUrl,
|
|
1503
|
+
resolvedHeaders,
|
|
1504
|
+
subprotocols: resolvedSubprotocols,
|
|
1505
|
+
connectTimeoutMs,
|
|
1506
|
+
steps: resolvedSteps,
|
|
1507
|
+
closeCode: wsConfig.closeCode ?? DEFAULT_WS_CLOSE_CODE,
|
|
1508
|
+
rejectUnauthorized: wsConfig.rejectUnauthorized !== false,
|
|
1509
|
+
environment: { ...runtimeContext.environment },
|
|
1510
|
+
globals: { ...runtimeContext.globals },
|
|
1511
|
+
signal,
|
|
1512
|
+
});
|
|
1513
|
+
throwIfStopped(signal);
|
|
1514
|
+
// Authoritative expect evaluation over the recorded frames — the
|
|
1515
|
+
// host's early-stop is advisory only (kafka consume precedent).
|
|
1516
|
+
const evaluationScope = {
|
|
1517
|
+
environment: runtimeContext.environment,
|
|
1518
|
+
globals: runtimeContext.globals,
|
|
1519
|
+
workflowContext: runtimeContext.workflowContext,
|
|
1520
|
+
nodeMetaByLabel: runtimeContext.nodeMetaByLabel,
|
|
1521
|
+
responseRoot: runtimeContext.responseRoot,
|
|
1522
|
+
dbResult: runtimeContext.dbResult,
|
|
1523
|
+
kafkaEvent: runtimeContext.kafkaEvent,
|
|
1524
|
+
redisResult: runtimeContext.redisResult,
|
|
1525
|
+
};
|
|
1526
|
+
const outcomesByIndex = new Map(wsExecution.stepOutcomes.map((outcome) => [outcome.index, outcome]));
|
|
1527
|
+
let conversationBroken = !wsExecution.connected;
|
|
1528
|
+
const stepResults = resolvedSteps.map((step, index) => {
|
|
1529
|
+
const outcome = outcomesByIndex.get(index);
|
|
1530
|
+
const durationMs = outcome && outcome.startedAt && outcome.endedAt
|
|
1531
|
+
? Math.max(0, new Date(outcome.endedAt).getTime() - new Date(outcome.startedAt).getTime())
|
|
1532
|
+
: 0;
|
|
1533
|
+
if (!outcome) {
|
|
1534
|
+
return {
|
|
1535
|
+
index,
|
|
1536
|
+
kind: step.kind,
|
|
1537
|
+
status: "failed",
|
|
1538
|
+
durationMs: 0,
|
|
1539
|
+
error: conversationBroken
|
|
1540
|
+
? "Step not reached — the conversation ended early."
|
|
1541
|
+
: "Step was not executed by the host.",
|
|
1542
|
+
matchedMessageIndex: null,
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
if (step.kind !== "expect") {
|
|
1546
|
+
const failed = Boolean(outcome.error);
|
|
1547
|
+
if (failed)
|
|
1548
|
+
conversationBroken = true;
|
|
1549
|
+
return {
|
|
1550
|
+
index,
|
|
1551
|
+
kind: step.kind,
|
|
1552
|
+
status: failed ? "failed" : "passed",
|
|
1553
|
+
durationMs,
|
|
1554
|
+
error: outcome.error ?? null,
|
|
1555
|
+
matchedMessageIndex: null,
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
const candidates = wsExecution.messages.filter((message) => message.direction === "received" && message.stepIndex === index);
|
|
1559
|
+
let matchedMessageIndex = null;
|
|
1560
|
+
for (const candidate of candidates) {
|
|
1561
|
+
const evaluation = evaluateWebsocketExpect(step.rules ?? [], step.matchMode ?? "all", evaluationScope, candidate);
|
|
1562
|
+
if (evaluation.passed) {
|
|
1563
|
+
matchedMessageIndex = candidate.index;
|
|
1564
|
+
break;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
const failed = matchedMessageIndex === null;
|
|
1568
|
+
if (failed)
|
|
1569
|
+
conversationBroken = true;
|
|
1570
|
+
return {
|
|
1571
|
+
index,
|
|
1572
|
+
kind: step.kind,
|
|
1573
|
+
status: failed ? "failed" : "passed",
|
|
1574
|
+
durationMs,
|
|
1575
|
+
error: failed
|
|
1576
|
+
? (outcome.error ??
|
|
1577
|
+
`No message matched within ${step.timeoutMs}ms (${candidates.length} received).`)
|
|
1578
|
+
: null,
|
|
1579
|
+
matchedMessageIndex,
|
|
1580
|
+
};
|
|
1581
|
+
});
|
|
1582
|
+
const failedSteps = stepResults.filter((entry) => entry.status === "failed").length;
|
|
1583
|
+
const wsErrorMessage = !wsExecution.connected
|
|
1584
|
+
? (wsExecution.connectError ?? "WebSocket connection failed.")
|
|
1585
|
+
: failedSteps > 0
|
|
1586
|
+
? (() => {
|
|
1587
|
+
const firstFailed = stepResults.find((entry) => entry.status === "failed");
|
|
1588
|
+
return `WebSocket step ${(firstFailed?.index ?? 0) + 1} (${firstFailed?.kind}) failed: ${firstFailed?.error}`;
|
|
1589
|
+
})()
|
|
1590
|
+
: null;
|
|
1591
|
+
outputData = {
|
|
1592
|
+
url: resolvedUrl,
|
|
1593
|
+
connected: wsExecution.connected,
|
|
1594
|
+
closeCode: wsExecution.closeCode,
|
|
1595
|
+
closeReason: wsExecution.closeReason ?? null,
|
|
1596
|
+
durationMs: wsExecution.durationMs,
|
|
1597
|
+
messageCount: wsExecution.messages.length,
|
|
1598
|
+
messages: wsExecution.messages,
|
|
1599
|
+
steps: stepResults,
|
|
1600
|
+
failedSteps,
|
|
1601
|
+
errorMessage: wsErrorMessage,
|
|
1602
|
+
};
|
|
1603
|
+
if (wsErrorMessage) {
|
|
1604
|
+
throw new ScenarioNodeFailureError(wsErrorMessage);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1448
1607
|
if (node.type === "condition") {
|
|
1449
1608
|
const conditionIssues = validateConditionConfig(node.data.config);
|
|
1450
1609
|
const blockingConditionIssue = conditionIssues.find((issue) => issue.severity === "error");
|