callman-core 1.19.0 → 1.21.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/ai/jsonRepair.d.ts +2 -0
- package/dist/ai/jsonRepair.d.ts.map +1 -0
- package/dist/ai/jsonRepair.js +61 -0
- package/dist/ai/jsonRepair.js.map +1 -0
- package/dist/host/db/connectionMapping.d.ts +4 -0
- package/dist/host/db/connectionMapping.d.ts.map +1 -1
- package/dist/host/db/connectionMapping.js +4 -0
- package/dist/host/db/connectionMapping.js.map +1 -1
- package/dist/host/db/pgClientConfig.d.ts +11 -0
- package/dist/host/db/pgClientConfig.d.ts.map +1 -0
- package/dist/host/db/pgClientConfig.js +83 -0
- package/dist/host/db/pgClientConfig.js.map +1 -0
- package/dist/host/db/pgSessionManager.d.ts +18 -0
- package/dist/host/db/pgSessionManager.d.ts.map +1 -0
- package/dist/host/db/pgSessionManager.js +162 -0
- package/dist/host/db/pgSessionManager.js.map +1 -0
- package/dist/host/db/postgresAdapter.d.ts.map +1 -1
- package/dist/host/db/postgresAdapter.js +7 -36
- package/dist/host/db/postgresAdapter.js.map +1 -1
- package/dist/host/db/queryExecutor.d.ts +4 -0
- package/dist/host/db/queryExecutor.d.ts.map +1 -1
- package/dist/host/db/queryExecutor.js +40 -2
- package/dist/host/db/queryExecutor.js.map +1 -1
- package/dist/host/db/sqlState.d.ts +2 -0
- package/dist/host/db/sqlState.d.ts.map +1 -0
- package/dist/host/db/sqlState.js +14 -0
- package/dist/host/db/sqlState.js.map +1 -0
- package/dist/host/db/types.d.ts +13 -0
- package/dist/host/db/types.d.ts.map +1 -1
- package/dist/host/index.d.ts +3 -0
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +7 -0
- package/dist/host/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/scenario-runner/adapters/ai-executor.d.ts +9 -0
- package/dist/scenario-runner/adapters/ai-executor.d.ts.map +1 -0
- package/dist/scenario-runner/adapters/ai-executor.js +119 -0
- package/dist/scenario-runner/adapters/ai-executor.js.map +1 -0
- package/dist/scenario-runner/adapters/connection-config.d.ts +4 -0
- package/dist/scenario-runner/adapters/connection-config.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/db-executor.d.ts +7 -1
- package/dist/scenario-runner/adapters/db-executor.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/db-executor.js +106 -1
- package/dist/scenario-runner/adapters/db-executor.js.map +1 -1
- package/dist/scenario-runner/adapters/helpers/aiExample.d.ts +12 -0
- package/dist/scenario-runner/adapters/helpers/aiExample.d.ts.map +1 -0
- package/dist/scenario-runner/adapters/helpers/aiExample.js +79 -0
- package/dist/scenario-runner/adapters/helpers/aiExample.js.map +1 -0
- package/dist/scenario-runner/adapters/index.d.ts +4 -2
- package/dist/scenario-runner/adapters/index.d.ts.map +1 -1
- package/dist/scenario-runner/adapters/index.js +15 -1
- package/dist/scenario-runner/adapters/index.js.map +1 -1
- package/dist/scenario-runner/adapters/types.d.ts +22 -0
- package/dist/scenario-runner/adapters/types.d.ts.map +1 -1
- package/dist/scenario-runner/condition.d.ts +1 -0
- package/dist/scenario-runner/condition.d.ts.map +1 -1
- package/dist/scenario-runner/condition.js +164 -4
- package/dist/scenario-runner/condition.js.map +1 -1
- package/dist/scenario-runner/conditionTypes.d.ts +1 -1
- package/dist/scenario-runner/conditionTypes.d.ts.map +1 -1
- package/dist/scenario-runner/graphTraversal.d.ts +6 -0
- package/dist/scenario-runner/graphTraversal.d.ts.map +1 -1
- package/dist/scenario-runner/graphTraversal.js +7 -0
- package/dist/scenario-runner/graphTraversal.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 +70 -1
- package/dist/scenario-runner/runScenario.js.map +1 -1
- package/dist/scenario-runner/types.d.ts +75 -3
- package/dist/scenario-runner/types.d.ts.map +1 -1
- package/dist-cjs/ai/jsonRepair.js +65 -0
- package/dist-cjs/ai/jsonRepair.js.map +1 -0
- package/dist-cjs/host/db/connectionMapping.js +4 -0
- package/dist-cjs/host/db/connectionMapping.js.map +1 -1
- package/dist-cjs/host/db/pgClientConfig.js +90 -0
- package/dist-cjs/host/db/pgClientConfig.js.map +1 -0
- package/dist-cjs/host/db/pgSessionManager.js +170 -0
- package/dist-cjs/host/db/pgSessionManager.js.map +1 -0
- package/dist-cjs/host/db/postgresAdapter.js +7 -36
- package/dist-cjs/host/db/postgresAdapter.js.map +1 -1
- package/dist-cjs/host/db/queryExecutor.js +40 -2
- package/dist-cjs/host/db/queryExecutor.js.map +1 -1
- package/dist-cjs/host/db/sqlState.js +18 -0
- package/dist-cjs/host/db/sqlState.js.map +1 -0
- package/dist-cjs/host/index.js +16 -1
- package/dist-cjs/host/index.js.map +1 -1
- package/dist-cjs/index.js +5 -1
- package/dist-cjs/index.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/ai-executor.js +123 -0
- package/dist-cjs/scenario-runner/adapters/ai-executor.js.map +1 -0
- package/dist-cjs/scenario-runner/adapters/db-executor.js +108 -2
- package/dist-cjs/scenario-runner/adapters/db-executor.js.map +1 -1
- package/dist-cjs/scenario-runner/adapters/helpers/aiExample.js +85 -0
- package/dist-cjs/scenario-runner/adapters/helpers/aiExample.js.map +1 -0
- package/dist-cjs/scenario-runner/adapters/index.js +21 -1
- package/dist-cjs/scenario-runner/adapters/index.js.map +1 -1
- package/dist-cjs/scenario-runner/condition.js +166 -5
- package/dist-cjs/scenario-runner/condition.js.map +1 -1
- package/dist-cjs/scenario-runner/graphTraversal.js +8 -1
- package/dist-cjs/scenario-runner/graphTraversal.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 +70 -1
- package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,17 +3,90 @@
|
|
|
3
3
|
// Resolves the workspace connection, defers the actual query execution
|
|
4
4
|
// to the host-supplied `databaseExecutor` driver (mongoose / oracledb /
|
|
5
5
|
// postgres / etc.), wraps the result in the scenario adapter shape.
|
|
6
|
+
//
|
|
7
|
+
// Also owns the two stored-function-testing semantics that must behave
|
|
8
|
+
// identically on every host: session capability detection (a `useSession`
|
|
9
|
+
// node on a host without `databaseSessionRelease` fails fast instead of
|
|
10
|
+
// silently running autocommit) and expected-error evaluation (pgTAP
|
|
11
|
+
// `throws_ok` equivalent — a matching failure PASSES the node).
|
|
6
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.buildDbExecutor = void 0;
|
|
13
|
+
exports.buildDbExecutor = exports.matchDbExpectedError = exports.extractSqlState = void 0;
|
|
8
14
|
const abort_js_1 = require("./helpers/abort.js");
|
|
15
|
+
const sqlState_js_1 = require("../../host/db/sqlState.js");
|
|
16
|
+
Object.defineProperty(exports, "extractSqlState", { enumerable: true, get: function () { return sqlState_js_1.extractSqlState; } });
|
|
17
|
+
// Shared with hosts that build their own dbExecutor (desktop's IPC-backed
|
|
18
|
+
// adapter) so expectError semantics never drift between runners.
|
|
19
|
+
const matchDbExpectedError = (expectation, message, sqlState) => {
|
|
20
|
+
const wantedState = expectation.sqlState?.trim();
|
|
21
|
+
const wantedPattern = expectation.messagePattern?.trim();
|
|
22
|
+
if (wantedState && wantedState.toUpperCase() !== (sqlState ?? "").toUpperCase()) {
|
|
23
|
+
return {
|
|
24
|
+
matched: false,
|
|
25
|
+
mismatchReason: `expected SQLSTATE ${wantedState}, got ${sqlState ?? "none"}`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (wantedPattern) {
|
|
29
|
+
let re;
|
|
30
|
+
try {
|
|
31
|
+
re = new RegExp(wantedPattern);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return {
|
|
35
|
+
matched: false,
|
|
36
|
+
mismatchReason: `invalid expectError.messagePattern regex: ${wantedPattern}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!re.test(message)) {
|
|
40
|
+
return {
|
|
41
|
+
matched: false,
|
|
42
|
+
mismatchReason: `error message did not match /${wantedPattern}/`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!wantedState && !wantedPattern) {
|
|
47
|
+
// An empty expectation still means "this query must fail".
|
|
48
|
+
return { matched: true, mismatchReason: null };
|
|
49
|
+
}
|
|
50
|
+
return { matched: true, mismatchReason: null };
|
|
51
|
+
};
|
|
52
|
+
exports.matchDbExpectedError = matchDbExpectedError;
|
|
9
53
|
const buildDbExecutor = (options) => async (input) => {
|
|
10
54
|
const connection = await options.resolveConnection(input.node.data.config.connectionId);
|
|
55
|
+
const expectError = input.node.data.config.expectError;
|
|
56
|
+
const hasExpectation = Boolean(expectError && (expectError.sqlState?.trim() || expectError.messagePattern?.trim()));
|
|
57
|
+
if (input.session && !options.drivers.databaseSessionRelease) {
|
|
58
|
+
return {
|
|
59
|
+
rawResult: { success: false, rows: [], rowCount: 0, error: "sessions unsupported" },
|
|
60
|
+
dbResult: null,
|
|
61
|
+
outputData: null,
|
|
62
|
+
failureMessage: "This node uses a scenario DB session, but the current runner does not support DB sessions. Run it via a host with session support or disable 'Run in scenario transaction'.",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
11
65
|
try {
|
|
12
66
|
const result = await (0, abort_js_1.withAbortAndTimeout)(options.drivers.databaseExecutor({
|
|
13
67
|
connection,
|
|
14
68
|
resolvedQuery: input.resolvedQuery,
|
|
15
69
|
resolvedParams: input.resolvedParams,
|
|
70
|
+
session: input.session,
|
|
16
71
|
}), options.nodeTimeoutMs, `Database node ${input.stepId}`, input.signal);
|
|
72
|
+
if (hasExpectation) {
|
|
73
|
+
return {
|
|
74
|
+
rawResult: {
|
|
75
|
+
success: false,
|
|
76
|
+
rows: result.rows,
|
|
77
|
+
rowCount: result.rowCount,
|
|
78
|
+
error: "expected an error but the query succeeded",
|
|
79
|
+
},
|
|
80
|
+
dbResult: result.rows,
|
|
81
|
+
outputData: { rows: result.rows, rowCount: result.rowCount },
|
|
82
|
+
failureMessage: `Expected the query to fail (${[
|
|
83
|
+
expectError?.sqlState ? `SQLSTATE ${expectError.sqlState}` : null,
|
|
84
|
+
expectError?.messagePattern ? `message /${expectError.messagePattern}/` : null,
|
|
85
|
+
]
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.join(", ")}), but it succeeded.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
17
90
|
return {
|
|
18
91
|
rawResult: {
|
|
19
92
|
success: true,
|
|
@@ -30,16 +103,49 @@ const buildDbExecutor = (options) => async (input) => {
|
|
|
30
103
|
}
|
|
31
104
|
catch (error) {
|
|
32
105
|
const message = error instanceof Error ? error.message : String(error);
|
|
106
|
+
const sqlState = (0, sqlState_js_1.extractSqlState)(error);
|
|
107
|
+
if (hasExpectation && expectError) {
|
|
108
|
+
const { matched, mismatchReason } = (0, exports.matchDbExpectedError)(expectError, message, sqlState);
|
|
109
|
+
if (matched) {
|
|
110
|
+
const expectedError = { sqlState: sqlState ?? null, message };
|
|
111
|
+
return {
|
|
112
|
+
rawResult: {
|
|
113
|
+
success: true,
|
|
114
|
+
rows: [],
|
|
115
|
+
rowCount: 0,
|
|
116
|
+
...(sqlState ? { sqlState } : {}),
|
|
117
|
+
},
|
|
118
|
+
dbResult: null,
|
|
119
|
+
outputData: { expectedError, rows: [], rowCount: 0 },
|
|
120
|
+
failureMessage: null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
rawResult: {
|
|
125
|
+
success: false,
|
|
126
|
+
rows: [],
|
|
127
|
+
rowCount: 0,
|
|
128
|
+
error: message,
|
|
129
|
+
...(sqlState ? { sqlState } : {}),
|
|
130
|
+
},
|
|
131
|
+
dbResult: null,
|
|
132
|
+
outputData: null,
|
|
133
|
+
failureMessage: `Query failed, but not as expected (${mismatchReason}). Actual error: ${message}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
33
136
|
return {
|
|
34
137
|
rawResult: {
|
|
35
138
|
success: false,
|
|
36
139
|
rows: [],
|
|
37
140
|
rowCount: 0,
|
|
38
141
|
error: message,
|
|
142
|
+
...(sqlState ? { sqlState } : {}),
|
|
39
143
|
},
|
|
40
144
|
dbResult: null,
|
|
41
145
|
outputData: null,
|
|
42
|
-
failureMessage:
|
|
146
|
+
failureMessage: sqlState
|
|
147
|
+
? `${sqlState}: ${message || "Database query failed."}`
|
|
148
|
+
: message || "Database query failed.",
|
|
43
149
|
};
|
|
44
150
|
}
|
|
45
151
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-executor.js","sourceRoot":"","sources":["../../../src/scenario-runner/adapters/db-executor.ts"],"names":[],"mappings":";AAAA,wEAAwE;AACxE,uEAAuE;AACvE,wEAAwE;AACxE,oEAAoE;;;
|
|
1
|
+
{"version":3,"file":"db-executor.js","sourceRoot":"","sources":["../../../src/scenario-runner/adapters/db-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// AI-node response-example helpers, shared by the executor, the condition
|
|
3
|
+
// suggestion builder, and host UIs:
|
|
4
|
+
// - parseJsonExample: tolerant parse of the user-authored JSON example
|
|
5
|
+
// - deriveSchemaFromExample: example -> lenient JSON Schema (top-level keys
|
|
6
|
+
// required, extras allowed; numbers never tighten to integers; nested
|
|
7
|
+
// objects recurse WITHOUT nested `required` so only the contract the
|
|
8
|
+
// downstream `{{Label.field}}` references depend on is enforced)
|
|
9
|
+
// - buildAiMessages: the fixed system instruction + resolved user prompt
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.buildAiMessages = exports.deriveSchemaFromExample = exports.parseJsonExample = void 0;
|
|
12
|
+
const jsonRepair_js_1 = require("../../../ai/jsonRepair.js");
|
|
13
|
+
const parseJsonExample = (exampleText) => {
|
|
14
|
+
const trimmed = (exampleText ?? "").trim();
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return { value: null, error: "Response example is required." };
|
|
17
|
+
}
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse((0, jsonRepair_js_1.extractJsonPayload)(trimmed));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { value: null, error: "Response example is not valid JSON." };
|
|
24
|
+
}
|
|
25
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
26
|
+
return {
|
|
27
|
+
value: null,
|
|
28
|
+
error: "Response example must be a JSON object (e.g. {\"sentiment\": \"positive\"}).",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { value: parsed, error: null };
|
|
32
|
+
};
|
|
33
|
+
exports.parseJsonExample = parseJsonExample;
|
|
34
|
+
const deriveValueSchema = (value) => {
|
|
35
|
+
if (value === null || value === undefined) {
|
|
36
|
+
return {}; // no constraint — the example gives no type signal
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === "string")
|
|
39
|
+
return { type: "string" };
|
|
40
|
+
if (typeof value === "boolean")
|
|
41
|
+
return { type: "boolean" };
|
|
42
|
+
// Never "integer": an example value of 1 must not reject a reply of 0.9.
|
|
43
|
+
if (typeof value === "number")
|
|
44
|
+
return { type: "number" };
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.length > 0
|
|
47
|
+
? { type: "array", items: deriveValueSchema(value[0]) }
|
|
48
|
+
: { type: "array" };
|
|
49
|
+
}
|
|
50
|
+
const properties = {};
|
|
51
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
52
|
+
properties[key] = deriveValueSchema(nested);
|
|
53
|
+
}
|
|
54
|
+
return { type: "object", properties, additionalProperties: true };
|
|
55
|
+
};
|
|
56
|
+
const deriveSchemaFromExample = (example) => {
|
|
57
|
+
const properties = {};
|
|
58
|
+
for (const [key, value] of Object.entries(example)) {
|
|
59
|
+
properties[key] = deriveValueSchema(value);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties,
|
|
64
|
+
required: Object.keys(example),
|
|
65
|
+
additionalProperties: true,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
exports.deriveSchemaFromExample = deriveSchemaFromExample;
|
|
69
|
+
// The word "JSON" in the system message also satisfies OpenAI's
|
|
70
|
+
// `response_format: json_object` requirement (it rejects prompts that never
|
|
71
|
+
// mention JSON).
|
|
72
|
+
const buildAiMessages = ({ resolvedPrompt, exampleText, }) => [
|
|
73
|
+
{
|
|
74
|
+
role: "system",
|
|
75
|
+
content: [
|
|
76
|
+
"You are a step inside an automated API-testing scenario.",
|
|
77
|
+
"Reply with ONLY a single JSON object — no markdown, no code fences, no prose.",
|
|
78
|
+
"The reply must use the same top-level keys and value types as this example (values may differ):",
|
|
79
|
+
exampleText.trim(),
|
|
80
|
+
].join("\n"),
|
|
81
|
+
},
|
|
82
|
+
{ role: "user", content: resolvedPrompt },
|
|
83
|
+
];
|
|
84
|
+
exports.buildAiMessages = buildAiMessages;
|
|
85
|
+
//# sourceMappingURL=aiExample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aiExample.js","sourceRoot":"","sources":["../../../../src/scenario-runner/adapters/helpers/aiExample.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// (HTTP, DB, Redis, Kafka). Script + notification executors default to
|
|
9
9
|
// canonical core implementations but can be overridden.
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.summarizeContractResults = exports.buildRequestFailureMessage = exports.evaluateScenarioExpectedStatus = exports.buildWebsocketExecutor = exports.buildScriptExecutor = exports.buildRequestExecutor = exports.buildRedisExecutor = exports.buildNotificationExecutor = exports.buildKafkaExecutor = exports.buildDbExecutor = exports.buildScenarioRuntimeAdapters = void 0;
|
|
11
|
+
exports.summarizeContractResults = exports.buildRequestFailureMessage = exports.evaluateScenarioExpectedStatus = exports.buildWebsocketExecutor = exports.buildScriptExecutor = exports.buildRequestExecutor = exports.buildRedisExecutor = exports.buildNotificationExecutor = exports.buildKafkaExecutor = exports.parseJsonExample = exports.deriveSchemaFromExample = exports.buildAiMessages = exports.buildAiExecutor = exports.matchDbExpectedError = exports.extractSqlState = exports.buildDbExecutor = exports.buildScenarioRuntimeAdapters = void 0;
|
|
12
|
+
const ai_executor_js_1 = require("./ai-executor.js");
|
|
12
13
|
const db_executor_js_1 = require("./db-executor.js");
|
|
13
14
|
const kafka_executor_js_1 = require("./kafka-executor.js");
|
|
14
15
|
const notification_executor_js_1 = require("./notification-executor.js");
|
|
@@ -24,11 +25,30 @@ const buildScenarioRuntimeAdapters = (options) => ({
|
|
|
24
25
|
websocketExecutor: (0, websocket_executor_js_1.buildWebsocketExecutor)(options),
|
|
25
26
|
scriptExecutor: options.scriptExecutor ?? (0, script_executor_js_1.buildScriptExecutor)(options),
|
|
26
27
|
notificationExecutor: options.notificationExecutor ?? (0, notification_executor_js_1.buildNotificationExecutor)(options),
|
|
28
|
+
aiExecutor: (0, ai_executor_js_1.buildAiExecutor)({
|
|
29
|
+
chatTransport: options.drivers.aiChatTransport,
|
|
30
|
+
nodeTimeoutMs: options.nodeTimeoutMs,
|
|
31
|
+
}),
|
|
32
|
+
// Present only when the host driver supports run-scoped DB sessions —
|
|
33
|
+
// its absence is how the runner detects "sessions unsupported here".
|
|
34
|
+
...(options.drivers.databaseSessionRelease
|
|
35
|
+
? {
|
|
36
|
+
dbSessionRelease: (input) => options.drivers.databaseSessionRelease(input),
|
|
37
|
+
}
|
|
38
|
+
: {}),
|
|
27
39
|
});
|
|
28
40
|
exports.buildScenarioRuntimeAdapters = buildScenarioRuntimeAdapters;
|
|
29
41
|
// Re-exports so callers don't have to import from sibling files.
|
|
30
42
|
var db_executor_js_2 = require("./db-executor.js");
|
|
31
43
|
Object.defineProperty(exports, "buildDbExecutor", { enumerable: true, get: function () { return db_executor_js_2.buildDbExecutor; } });
|
|
44
|
+
Object.defineProperty(exports, "extractSqlState", { enumerable: true, get: function () { return db_executor_js_2.extractSqlState; } });
|
|
45
|
+
Object.defineProperty(exports, "matchDbExpectedError", { enumerable: true, get: function () { return db_executor_js_2.matchDbExpectedError; } });
|
|
46
|
+
var ai_executor_js_2 = require("./ai-executor.js");
|
|
47
|
+
Object.defineProperty(exports, "buildAiExecutor", { enumerable: true, get: function () { return ai_executor_js_2.buildAiExecutor; } });
|
|
48
|
+
var aiExample_js_1 = require("./helpers/aiExample.js");
|
|
49
|
+
Object.defineProperty(exports, "buildAiMessages", { enumerable: true, get: function () { return aiExample_js_1.buildAiMessages; } });
|
|
50
|
+
Object.defineProperty(exports, "deriveSchemaFromExample", { enumerable: true, get: function () { return aiExample_js_1.deriveSchemaFromExample; } });
|
|
51
|
+
Object.defineProperty(exports, "parseJsonExample", { enumerable: true, get: function () { return aiExample_js_1.parseJsonExample; } });
|
|
32
52
|
var kafka_executor_js_2 = require("./kafka-executor.js");
|
|
33
53
|
Object.defineProperty(exports, "buildKafkaExecutor", { enumerable: true, get: function () { return kafka_executor_js_2.buildKafkaExecutor; } });
|
|
34
54
|
var notification_executor_js_2 = require("./notification-executor.js");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scenario-runner/adapters/index.ts"],"names":[],"mappings":";AAAA,6DAA6D;AAC7D,EAAE;AACF,gEAAgE;AAChE,6DAA6D;AAC7D,qEAAqE;AACrE,qEAAqE;AACrE,uEAAuE;AACvE,wDAAwD;;;AAIxD,qDAAmD;AACnD,2DAAyD;AACzD,yEAAuE;AACvE,2DAAyD;AACzD,+DAA6D;AAC7D,6DAA2D;AAC3D,mEAAiE;AAI1D,MAAM,4BAA4B,GAAG,CAC1C,OAA6C,EACpB,EAAE,CAAC,CAAC;IAC7B,eAAe,EAAE,IAAA,0CAAoB,EAAC,OAAO,CAAC;IAC9C,UAAU,EAAE,IAAA,gCAAe,EAAC,OAAO,CAAC;IACpC,aAAa,EAAE,IAAA,sCAAkB,EAAC,OAAO,CAAC;IAC1C,aAAa,EAAE,IAAA,sCAAkB,EAAC,OAAO,CAAC;IAC1C,iBAAiB,EAAE,IAAA,8CAAsB,EAAC,OAAO,CAAC;IAClD,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,IAAA,wCAAmB,EAAC,OAAO,CAAC;IACtE,oBAAoB,EAClB,OAAO,CAAC,oBAAoB,IAAI,IAAA,oDAAyB,EAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scenario-runner/adapters/index.ts"],"names":[],"mappings":";AAAA,6DAA6D;AAC7D,EAAE;AACF,gEAAgE;AAChE,6DAA6D;AAC7D,qEAAqE;AACrE,qEAAqE;AACrE,uEAAuE;AACvE,wDAAwD;;;AAIxD,qDAAmD;AACnD,qDAAmD;AACnD,2DAAyD;AACzD,yEAAuE;AACvE,2DAAyD;AACzD,+DAA6D;AAC7D,6DAA2D;AAC3D,mEAAiE;AAI1D,MAAM,4BAA4B,GAAG,CAC1C,OAA6C,EACpB,EAAE,CAAC,CAAC;IAC7B,eAAe,EAAE,IAAA,0CAAoB,EAAC,OAAO,CAAC;IAC9C,UAAU,EAAE,IAAA,gCAAe,EAAC,OAAO,CAAC;IACpC,aAAa,EAAE,IAAA,sCAAkB,EAAC,OAAO,CAAC;IAC1C,aAAa,EAAE,IAAA,sCAAkB,EAAC,OAAO,CAAC;IAC1C,iBAAiB,EAAE,IAAA,8CAAsB,EAAC,OAAO,CAAC;IAClD,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,IAAA,wCAAmB,EAAC,OAAO,CAAC;IACtE,oBAAoB,EAClB,OAAO,CAAC,oBAAoB,IAAI,IAAA,oDAAyB,EAAC,OAAO,CAAC;IACpE,UAAU,EAAE,IAAA,gCAAe,EAAC;QAC1B,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,eAAe;QAC9C,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC;IACF,sEAAsE;IACtE,qEAAqE;IACrE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,sBAAsB;QACxC,CAAC,CAAC;YACE,gBAAgB,EAAE,CAAC,KAAwD,EAAE,EAAE,CAC7E,OAAO,CAAC,OAAO,CAAC,sBAAuB,CAAC,KAAK,CAAC;SACjD;QACH,CAAC,CAAC,EAAE,CAAC;CACR,CAAC,CAAC;AAvBU,QAAA,4BAA4B,gCAuBtC;AAEH,iEAAiE;AACjE,mDAI0B;AAHxB,iHAAA,eAAe,OAAA;AACf,iHAAA,eAAe,OAAA;AACf,sHAAA,oBAAoB,OAAA;AAEtB,mDAA2E;AAAlE,iHAAA,eAAe,OAAA;AACxB,uDAKgC;AAJ9B,+GAAA,eAAe,OAAA;AACf,uHAAA,uBAAuB,OAAA;AACvB,gHAAA,gBAAgB,OAAA;AAGlB,yDAAyD;AAAhD,uHAAA,kBAAkB,OAAA;AAC3B,uEAAuE;AAA9D,qIAAA,yBAAyB,OAAA;AAClC,yDAAyD;AAAhD,uHAAA,kBAAkB,OAAA;AAC3B,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA;AAC7B,2DAA2D;AAAlD,yHAAA,mBAAmB,OAAA;AAC5B,iEAAiE;AAAxD,+HAAA,sBAAsB,OAAA;AAC/B,yEAAyE;AACzE,0EAA0E;AAC1E,sEAAsE;AACtE,mEAGsC;AAFpC,oIAAA,8BAA8B,OAAA;AAGhC,qEAIuC;AAHrC,iIAAA,0BAA0B,OAAA;AAC1B,+HAAA,wBAAwB,OAAA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.convertLegacyConditionOperator = exports.createConditionConfigFromLegacyExpression = exports.filterConditionRuntimeSuggestions = exports.buildConditionRuntimeSuggestions = exports.buildConditionRuntimeRootKeysForNodes = exports.buildConditionRuntimeRootSetFromKeys = exports.isConditionOperatorUnary = exports.getConditionOperatorLabel = exports.buildConditionOutputPayload = exports.updateConditionRuleRightOperand = exports.updateConditionRuleLeftOperand = exports.updateConditionRuleOperator = exports.patchConditionGroup = exports.patchConditionRule = exports.replaceConditionGroup = exports.removeConditionChild = exports.appendGroupToConditionGroup = exports.appendRuleToConditionGroup = exports.updateConditionGroupOperator = exports.getConditionRuntimeRootKeys = exports.evaluateConditionConfig = exports.getExecutableConditionGroup = exports.getConditionValueTypeLabel = exports.getConditionHeadline = exports.getConditionQuickSummary = exports.getConditionGroupCount = exports.getConditionRuleCount = exports.validateConditionRule = exports.validateConditionConfig = exports.normalizeConditionNodeConfig = exports.parseConditionExpressionSafely = exports.parseConditionExpressionToGroup = exports.serializeConditionConfigToPreview = exports.serializeConditionGroupToPreview = exports.serializeConditionConfigToExpression = exports.serializeConditionGroupToExpression = exports.createDefaultConditionConfig = exports.createConditionGroup = exports.createConditionRule = exports.createConditionOperand = exports.CONDITION_VALUE_TYPES = exports.evaluateConditionRule = exports.resolveConditionRuntimeValue = exports.CONDITION_OPERATOR_META = void 0;
|
|
3
|
+
exports.convertLegacyConditionOperator = exports.createConditionConfigFromLegacyExpression = exports.filterConditionRuntimeSuggestions = exports.buildConditionRuntimeSuggestions = exports.buildConditionRuntimeRootKeysForNodes = exports.buildConditionRuntimeRootSetFromKeys = exports.isConditionOperatorUnary = exports.getConditionOperatorLabel = exports.buildConditionOutputPayload = exports.updateConditionRuleRightOperand = exports.updateConditionRuleLeftOperand = exports.updateConditionRuleOperator = exports.patchConditionGroup = exports.patchConditionRule = exports.replaceConditionGroup = exports.removeConditionChild = exports.appendGroupToConditionGroup = exports.appendRuleToConditionGroup = exports.updateConditionGroupOperator = exports.getConditionRuntimeRootKeys = exports.evaluateConditionConfig = exports.getExecutableConditionGroup = exports.getConditionValueTypeLabel = exports.getConditionHeadline = exports.getConditionQuickSummary = exports.getConditionGroupCount = exports.getConditionRuleCount = exports.validateConditionRule = exports.validateConditionConfig = exports.normalizeConditionNodeConfig = exports.parseConditionExpressionSafely = exports.parseConditionExpressionToGroup = exports.serializeConditionConfigToPreview = exports.serializeConditionGroupToPreview = exports.serializeConditionConfigToExpression = exports.serializeConditionGroupToExpression = exports.createDefaultConditionConfig = exports.createConditionGroup = exports.createConditionRule = exports.createConditionOperand = exports.CONDITION_VALUE_TYPES = exports.evaluateConditionRule = exports.resolveConditionRuntimeValue = exports.isArrayRightConditionOperator = exports.CONDITION_OPERATOR_META = void 0;
|
|
4
4
|
const extraction_js_1 = require("./extraction.js");
|
|
5
|
+
const aiExample_js_1 = require("./adapters/helpers/aiExample.js");
|
|
5
6
|
const createId = (prefix) => typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
6
7
|
? crypto.randomUUID()
|
|
7
8
|
: `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -252,6 +253,8 @@ exports.CONDITION_OPERATOR_META = [
|
|
|
252
253
|
{ category: "Existence", isUnary: true, label: "Not Null", value: "not_null" },
|
|
253
254
|
{ category: "Collection", label: "In", value: "in" },
|
|
254
255
|
{ category: "Collection", label: "Not In", value: "not_in" },
|
|
256
|
+
{ category: "Collection", label: "Contains Row", value: "contains_row" },
|
|
257
|
+
{ category: "Collection", label: "Set Equals (unordered)", value: "set_equals_unordered" },
|
|
255
258
|
];
|
|
256
259
|
const OPERATOR_ALIAS_MAP = [
|
|
257
260
|
{ alias: "!==", operator: "strict_not_equals" },
|
|
@@ -264,7 +267,11 @@ const OPERATOR_ALIAS_MAP = [
|
|
|
264
267
|
{ alias: "<", operator: "less_than" },
|
|
265
268
|
{ alias: "not_contains", operator: "not_contains" },
|
|
266
269
|
{ alias: "not contains", operator: "not_contains" },
|
|
270
|
+
{ alias: "contains_row", operator: "contains_row" },
|
|
271
|
+
{ alias: "contains row", operator: "contains_row" },
|
|
267
272
|
{ alias: "contains", operator: "contains" },
|
|
273
|
+
{ alias: "set_equals_unordered", operator: "set_equals_unordered" },
|
|
274
|
+
{ alias: "set equals", operator: "set_equals_unordered" },
|
|
268
275
|
{ alias: "starts_with", operator: "starts_with" },
|
|
269
276
|
{ alias: "starts with", operator: "starts_with" },
|
|
270
277
|
{ alias: "ends_with", operator: "ends_with" },
|
|
@@ -343,6 +350,13 @@ const isNumericOperator = (operator) => operator === "greater_than" ||
|
|
|
343
350
|
operator === "less_or_equal" ||
|
|
344
351
|
operator === "between";
|
|
345
352
|
const isCollectionOperator = (operator) => operator === "in" || operator === "not_in";
|
|
353
|
+
// Operators whose right operand is an array literal (drives the default
|
|
354
|
+
// operand type and the builder UIs' type lock).
|
|
355
|
+
const isArrayRightConditionOperator = (operator) => operator === "between" ||
|
|
356
|
+
operator === "in" ||
|
|
357
|
+
operator === "not_in" ||
|
|
358
|
+
operator === "set_equals_unordered";
|
|
359
|
+
exports.isArrayRightConditionOperator = isArrayRightConditionOperator;
|
|
346
360
|
const getOperatorToken = (operator) => {
|
|
347
361
|
switch (operator) {
|
|
348
362
|
case "equals":
|
|
@@ -389,6 +403,10 @@ const getOperatorToken = (operator) => {
|
|
|
389
403
|
return "in";
|
|
390
404
|
case "not_in":
|
|
391
405
|
return "not in";
|
|
406
|
+
case "contains_row":
|
|
407
|
+
return "contains_row";
|
|
408
|
+
case "set_equals_unordered":
|
|
409
|
+
return "set_equals_unordered";
|
|
392
410
|
default:
|
|
393
411
|
return "==";
|
|
394
412
|
}
|
|
@@ -423,7 +441,17 @@ const createSuggestion = (group, path, description, valueType, exampleValue) =>
|
|
|
423
441
|
path,
|
|
424
442
|
valueType,
|
|
425
443
|
});
|
|
426
|
-
|
|
444
|
+
// Maps an AI response-example value onto the suggestion value type.
|
|
445
|
+
const aiExampleValueType = (value) => {
|
|
446
|
+
if (typeof value === "number")
|
|
447
|
+
return "number";
|
|
448
|
+
if (typeof value === "boolean")
|
|
449
|
+
return "boolean";
|
|
450
|
+
if (Array.isArray(value))
|
|
451
|
+
return "array";
|
|
452
|
+
return "string";
|
|
453
|
+
};
|
|
454
|
+
const buildNodeSuggestions = (label, type, node) => {
|
|
427
455
|
const group = label;
|
|
428
456
|
const metaSuggestions = [
|
|
429
457
|
createSuggestion(group, `${label}.__meta.status`, "Execution status.", "string", "success"),
|
|
@@ -549,6 +577,30 @@ const buildNodeSuggestions = (label, type) => {
|
|
|
549
577
|
createSuggestion(group, `${label}.failedSteps`, "How many steps failed.", "number"),
|
|
550
578
|
...metaSuggestions,
|
|
551
579
|
];
|
|
580
|
+
case "ai": {
|
|
581
|
+
// The AI node's outputData IS the parsed JSON reply, so the example's
|
|
582
|
+
// own keys are the reference surface. Parse tolerantly and fall back to
|
|
583
|
+
// a generic hint when the example is missing/invalid.
|
|
584
|
+
const exampleText = node?.type === "ai" ? node.data.config.responseExample : "";
|
|
585
|
+
const parsedExample = (0, aiExample_js_1.parseJsonExample)(exampleText ?? "");
|
|
586
|
+
const exampleSuggestions = parsedExample.value
|
|
587
|
+
? Object.entries(parsedExample.value).flatMap(([key, value]) => {
|
|
588
|
+
const base = createSuggestion(group, `${label}.${key}`, `AI reply field "${key}" (from the response example).`, aiExampleValueType(value), typeof value === "string" || typeof value === "number" || typeof value === "boolean"
|
|
589
|
+
? String(value)
|
|
590
|
+
: undefined);
|
|
591
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
592
|
+
return [
|
|
593
|
+
base,
|
|
594
|
+
...Object.entries(value).map(([nestedKey, nested]) => createSuggestion(group, `${label}.${key}.${nestedKey}`, `AI reply field "${key}.${nestedKey}" (from the response example).`, aiExampleValueType(nested))),
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
return [base];
|
|
598
|
+
})
|
|
599
|
+
: [
|
|
600
|
+
createSuggestion(group, `${label}`, "Parsed JSON reply from the AI node.", "string"),
|
|
601
|
+
];
|
|
602
|
+
return [...exampleSuggestions, ...metaSuggestions];
|
|
603
|
+
}
|
|
552
604
|
case "end":
|
|
553
605
|
return metaSuggestions;
|
|
554
606
|
default:
|
|
@@ -583,9 +635,13 @@ const getDefaultRightOperand = (operator) => {
|
|
|
583
635
|
if (isUnaryOperator(operator)) {
|
|
584
636
|
return undefined;
|
|
585
637
|
}
|
|
586
|
-
if (
|
|
638
|
+
if ((0, exports.isArrayRightConditionOperator)(operator)) {
|
|
587
639
|
return (0, exports.createConditionOperand)("literal", "array", operator === "between" ? [0, 1] : []);
|
|
588
640
|
}
|
|
641
|
+
if (operator === "contains_row") {
|
|
642
|
+
// JSON object literal — "{ column: expected }" row matcher.
|
|
643
|
+
return (0, exports.createConditionOperand)("literal", "string", "{}");
|
|
644
|
+
}
|
|
589
645
|
return (0, exports.createConditionOperand)("literal", "string", "");
|
|
590
646
|
};
|
|
591
647
|
const parseLegacyConditionOperator = (value) => LEGACY_OPERATOR_MAP[value] ?? "equals";
|
|
@@ -1345,6 +1401,105 @@ const buildConditionRegex = (value) => {
|
|
|
1345
1401
|
}
|
|
1346
1402
|
return new RegExp(trimmed);
|
|
1347
1403
|
};
|
|
1404
|
+
// --- Result-set assertion helpers (DB rows) -----------------------------
|
|
1405
|
+
// Accept an object literal typed as string ('{"tier":"GOLD"}') or an actual
|
|
1406
|
+
// record from a runtime path.
|
|
1407
|
+
const coerceRecordOperand = (value) => {
|
|
1408
|
+
if (isRecord(value))
|
|
1409
|
+
return value;
|
|
1410
|
+
if (typeof value === "string") {
|
|
1411
|
+
try {
|
|
1412
|
+
const parsed = JSON.parse(value);
|
|
1413
|
+
return isRecord(parsed) ? parsed : null;
|
|
1414
|
+
}
|
|
1415
|
+
catch {
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return null;
|
|
1420
|
+
};
|
|
1421
|
+
const coerceArrayOperand = (value) => {
|
|
1422
|
+
if (Array.isArray(value))
|
|
1423
|
+
return value;
|
|
1424
|
+
if (typeof value === "string") {
|
|
1425
|
+
try {
|
|
1426
|
+
const parsed = JSON.parse(value);
|
|
1427
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
1428
|
+
}
|
|
1429
|
+
catch {
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return null;
|
|
1434
|
+
};
|
|
1435
|
+
// "Some row matches every expected key" — pgTAP-ish `results_ne`-lite for
|
|
1436
|
+
// side-effect table checks: {{Ledger.rows}} contains_row {"tier":"GOLD",
|
|
1437
|
+
// "cashback": 2.5}. Values compare loosely so pg numeric strings match
|
|
1438
|
+
// number literals.
|
|
1439
|
+
const evaluateContainsRow = (leftValue, rightValue) => {
|
|
1440
|
+
const rows = coerceArrayOperand(leftValue);
|
|
1441
|
+
const expected = coerceRecordOperand(rightValue);
|
|
1442
|
+
if (!rows || !expected)
|
|
1443
|
+
return false;
|
|
1444
|
+
const keys = Object.keys(expected);
|
|
1445
|
+
if (keys.length === 0)
|
|
1446
|
+
return rows.length > 0;
|
|
1447
|
+
return rows.some((row) => isRecord(row) &&
|
|
1448
|
+
keys.every((key) => compareLooseEquality(row[key], expected[key])));
|
|
1449
|
+
};
|
|
1450
|
+
// Multiset equality, order-insensitive (pgTAP `bag_eq` equivalent). Values
|
|
1451
|
+
// are canonicalized first: numeric strings become numbers ("2.50" → 2.5) and
|
|
1452
|
+
// object keys sort, so row order and numeric formatting don't matter.
|
|
1453
|
+
const canonicalizeForSetEquality = (value) => {
|
|
1454
|
+
if (typeof value === "string") {
|
|
1455
|
+
const trimmed = value.trim();
|
|
1456
|
+
if (trimmed !== "" && !Number.isNaN(Number(trimmed))) {
|
|
1457
|
+
const asNumber = Number(trimmed);
|
|
1458
|
+
if (Number.isFinite(asNumber))
|
|
1459
|
+
return asNumber;
|
|
1460
|
+
}
|
|
1461
|
+
return value;
|
|
1462
|
+
}
|
|
1463
|
+
if (Array.isArray(value))
|
|
1464
|
+
return value.map(canonicalizeForSetEquality);
|
|
1465
|
+
if (isRecord(value)) {
|
|
1466
|
+
const record = value;
|
|
1467
|
+
return Object.fromEntries(Object.keys(record)
|
|
1468
|
+
.sort()
|
|
1469
|
+
.map((key) => [key, canonicalizeForSetEquality(record[key])]));
|
|
1470
|
+
}
|
|
1471
|
+
return value;
|
|
1472
|
+
};
|
|
1473
|
+
const evaluateSetEqualsUnordered = (leftValue, rightValue) => {
|
|
1474
|
+
const leftArray = coerceArrayOperand(leftValue);
|
|
1475
|
+
const rightArray = coerceArrayOperand(rightValue);
|
|
1476
|
+
if (!leftArray || !rightArray || leftArray.length !== rightArray.length)
|
|
1477
|
+
return false;
|
|
1478
|
+
const serialize = (entry) => {
|
|
1479
|
+
try {
|
|
1480
|
+
return JSON.stringify(canonicalizeForSetEquality(entry)) ?? "null";
|
|
1481
|
+
}
|
|
1482
|
+
catch {
|
|
1483
|
+
return String(entry);
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
const counts = new Map();
|
|
1487
|
+
for (const entry of leftArray) {
|
|
1488
|
+
const key = serialize(entry);
|
|
1489
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1490
|
+
}
|
|
1491
|
+
for (const entry of rightArray) {
|
|
1492
|
+
const key = serialize(entry);
|
|
1493
|
+
const current = counts.get(key);
|
|
1494
|
+
if (!current)
|
|
1495
|
+
return false;
|
|
1496
|
+
if (current === 1)
|
|
1497
|
+
counts.delete(key);
|
|
1498
|
+
else
|
|
1499
|
+
counts.set(key, current - 1);
|
|
1500
|
+
}
|
|
1501
|
+
return counts.size === 0;
|
|
1502
|
+
};
|
|
1348
1503
|
const compareLooseEquality = (left, right) => {
|
|
1349
1504
|
if (Array.isArray(left) || Array.isArray(right) || isRecord(left) || isRecord(right)) {
|
|
1350
1505
|
try {
|
|
@@ -1555,6 +1710,12 @@ const evaluateConditionRule = (rule, runtimeScope) => {
|
|
|
1555
1710
|
? !right.value.some((entry) => compareLooseEquality(left.value, entry))
|
|
1556
1711
|
: false;
|
|
1557
1712
|
break;
|
|
1713
|
+
case "contains_row":
|
|
1714
|
+
result = evaluateContainsRow(left.value, right?.value);
|
|
1715
|
+
break;
|
|
1716
|
+
case "set_equals_unordered":
|
|
1717
|
+
result = evaluateSetEqualsUnordered(left.value, right?.value);
|
|
1718
|
+
break;
|
|
1558
1719
|
default:
|
|
1559
1720
|
result = false;
|
|
1560
1721
|
break;
|
|
@@ -1855,7 +2016,7 @@ const updateConditionRuleOperator = (rootGroup, ruleId, operator) => updateCondi
|
|
|
1855
2016
|
? { right: undefined }
|
|
1856
2017
|
: {
|
|
1857
2018
|
right: rule.right &&
|
|
1858
|
-
((
|
|
2019
|
+
(!(0, exports.isArrayRightConditionOperator)(operator) ||
|
|
1859
2020
|
rule.right.valueType === "array")
|
|
1860
2021
|
? cloneConditionOperand(rule.right)
|
|
1861
2022
|
: getDefaultRightOperand(operator),
|
|
@@ -1914,7 +2075,7 @@ const buildConditionRuntimeSuggestions = ({ nodes, environmentKeys = [], }) => d
|
|
|
1914
2075
|
...environmentKeys.map((key) => createSuggestion("Environment", `env.${key}`, `Environment variable "${key}".`, "string")),
|
|
1915
2076
|
...flattenScenarioNodes(nodes).flatMap((node) => {
|
|
1916
2077
|
const label = node.data.label.trim();
|
|
1917
|
-
return label ? buildNodeSuggestions(label, node.type) : [];
|
|
2078
|
+
return label ? buildNodeSuggestions(label, node.type, node) : [];
|
|
1918
2079
|
}),
|
|
1919
2080
|
]);
|
|
1920
2081
|
exports.buildConditionRuntimeSuggestions = buildConditionRuntimeSuggestions;
|