norn-cli 1.6.1 → 1.6.2
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/AGENTS.md +9 -1
- package/CHANGELOG.md +12 -0
- package/dist/cli.js +161 -59
- package/package.json +1 -1
- package/out/assertionRunner.js +0 -537
- package/out/chatParticipant.js +0 -722
- package/out/cli/colors.js +0 -129
- package/out/cli/formatters/assertion.js +0 -75
- package/out/cli/formatters/index.js +0 -23
- package/out/cli/formatters/response.js +0 -106
- package/out/cli/formatters/summary.js +0 -187
- package/out/cli/redaction.js +0 -237
- package/out/cli/reporters/html.js +0 -634
- package/out/cli/reporters/index.js +0 -22
- package/out/cli/reporters/junit.js +0 -211
- package/out/cli.js +0 -989
- package/out/codeLensProvider.js +0 -248
- package/out/compareContentProvider.js +0 -85
- package/out/completionProvider.js +0 -2404
- package/out/contractDecorationProvider.js +0 -243
- package/out/coverageCalculator.js +0 -837
- package/out/coveragePanel.js +0 -545
- package/out/diagnosticProvider.js +0 -1113
- package/out/environmentProvider.js +0 -442
- package/out/extension.js +0 -1114
- package/out/httpClient.js +0 -269
- package/out/jsonFileReader.js +0 -320
- package/out/nornPrompt.js +0 -580
- package/out/nornapiParser.js +0 -326
- package/out/parser.js +0 -725
- package/out/responsePanel.js +0 -4674
- package/out/schemaGenerator.js +0 -393
- package/out/scriptRunner.js +0 -419
- package/out/sequenceRunner.js +0 -3046
- package/out/swaggerBodyIntellisenseCache.js +0 -147
- package/out/swaggerParser.js +0 -419
- package/out/test/coverageCalculator.test.js +0 -100
- package/out/test/extension.test.js +0 -48
- package/out/testProvider.js +0 -658
- package/out/validationCache.js +0 -245
package/out/assertionRunner.js
DELETED
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isAssertCommand = isAssertCommand;
|
|
4
|
-
exports.parseAssertCommand = parseAssertCommand;
|
|
5
|
-
exports.resolveValue = resolveValue;
|
|
6
|
-
exports.evaluateAssertion = evaluateAssertion;
|
|
7
|
-
const schemaGenerator_1 = require("./schemaGenerator");
|
|
8
|
-
/**
|
|
9
|
-
* Checks if a line is an assert command
|
|
10
|
-
*/
|
|
11
|
-
function isAssertCommand(line) {
|
|
12
|
-
return /^assert\s+/i.test(line.trim());
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Parses an assert command line.
|
|
16
|
-
*
|
|
17
|
-
* Supported formats:
|
|
18
|
-
* - assert $1.status == 200
|
|
19
|
-
* - assert $1.body.name == "John"
|
|
20
|
-
* - assert $1.body.email contains "@"
|
|
21
|
-
* - assert $1.body.token exists
|
|
22
|
-
* - assert $1.status >= 200 | "Should return success status"
|
|
23
|
-
*
|
|
24
|
-
* @param line The assert command line
|
|
25
|
-
* @returns Parsed assertion or null if invalid
|
|
26
|
-
*/
|
|
27
|
-
function parseAssertCommand(line) {
|
|
28
|
-
const trimmed = line.trim();
|
|
29
|
-
const match = trimmed.match(/^assert\s+(.+)$/i);
|
|
30
|
-
if (!match) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
let content = match[1].trim();
|
|
34
|
-
let message;
|
|
35
|
-
// Check for optional message after |
|
|
36
|
-
const pipeIndex = findUnquotedPipe(content);
|
|
37
|
-
if (pipeIndex !== -1) {
|
|
38
|
-
message = content.substring(pipeIndex + 1).trim();
|
|
39
|
-
// Remove surrounding quotes from message if present
|
|
40
|
-
if ((message.startsWith('"') && message.endsWith('"')) ||
|
|
41
|
-
(message.startsWith("'") && message.endsWith("'"))) {
|
|
42
|
-
message = message.slice(1, -1);
|
|
43
|
-
}
|
|
44
|
-
content = content.substring(0, pipeIndex).trim();
|
|
45
|
-
}
|
|
46
|
-
// Try to parse unary operators first (exists, !exists)
|
|
47
|
-
const existsMatch = content.match(/^(.+?)\s+(exists|!exists)$/i);
|
|
48
|
-
if (existsMatch) {
|
|
49
|
-
return {
|
|
50
|
-
leftExpr: existsMatch[1].trim(),
|
|
51
|
-
operator: existsMatch[2].toLowerCase(),
|
|
52
|
-
message
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
// Parse binary operators
|
|
56
|
-
// Order matters: check multi-char operators first
|
|
57
|
-
const binaryOperators = [
|
|
58
|
-
{ pattern: /^(.+?)\s*>=\s*(.+)$/, op: '>=' },
|
|
59
|
-
{ pattern: /^(.+?)\s*<=\s*(.+)$/, op: '<=' },
|
|
60
|
-
{ pattern: /^(.+?)\s*==\s*(.+)$/, op: '==' },
|
|
61
|
-
{ pattern: /^(.+?)\s*!=\s*(.+)$/, op: '!=' },
|
|
62
|
-
{ pattern: /^(.+?)\s*>\s*(.+)$/, op: '>' },
|
|
63
|
-
{ pattern: /^(.+?)\s*<\s*(.+)$/, op: '<' },
|
|
64
|
-
{ pattern: /^(.+?)\s+contains\s+(.+)$/i, op: 'contains' },
|
|
65
|
-
{ pattern: /^(.+?)\s+startsWith\s+(.+)$/i, op: 'startsWith' },
|
|
66
|
-
{ pattern: /^(.+?)\s+endsWith\s+(.+)$/i, op: 'endsWith' },
|
|
67
|
-
{ pattern: /^(.+?)\s+matches\s+(.+)$/i, op: 'matches' },
|
|
68
|
-
{ pattern: /^(.+?)\s+matchesSchema\s+(.+)$/i, op: 'matchesSchema' },
|
|
69
|
-
{ pattern: /^(.+?)\s+isType\s+(.+)$/i, op: 'isType' },
|
|
70
|
-
];
|
|
71
|
-
for (const { pattern, op } of binaryOperators) {
|
|
72
|
-
const binaryMatch = content.match(pattern);
|
|
73
|
-
if (binaryMatch) {
|
|
74
|
-
return {
|
|
75
|
-
leftExpr: binaryMatch[1].trim(),
|
|
76
|
-
operator: op,
|
|
77
|
-
rightExpr: binaryMatch[2].trim(),
|
|
78
|
-
message
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Find the index of an unquoted pipe character
|
|
86
|
-
*/
|
|
87
|
-
function findUnquotedPipe(str) {
|
|
88
|
-
let inQuote = false;
|
|
89
|
-
let quoteChar = '';
|
|
90
|
-
for (let i = 0; i < str.length; i++) {
|
|
91
|
-
const char = str[i];
|
|
92
|
-
if ((char === '"' || char === "'") && !inQuote) {
|
|
93
|
-
inQuote = true;
|
|
94
|
-
quoteChar = char;
|
|
95
|
-
}
|
|
96
|
-
else if (char === quoteChar && inQuote) {
|
|
97
|
-
inQuote = false;
|
|
98
|
-
quoteChar = '';
|
|
99
|
-
}
|
|
100
|
-
else if (char === '|' && !inQuote) {
|
|
101
|
-
return i;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return -1;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Resolves a value expression to its actual value.
|
|
108
|
-
*
|
|
109
|
-
* Supports:
|
|
110
|
-
* - $N.path references (e.g., $1.status, $1.body.user.id)
|
|
111
|
-
* - String literals ("hello" or 'hello')
|
|
112
|
-
* - Number literals (200, 3.14)
|
|
113
|
-
* - Boolean literals (true, false)
|
|
114
|
-
* - null
|
|
115
|
-
* - Variable references {{varName}}
|
|
116
|
-
*
|
|
117
|
-
* @param expr The expression to resolve
|
|
118
|
-
* @param responses Array of responses from the sequence (indexed by $N where N is 1-based)
|
|
119
|
-
* @param variables Current runtime variables
|
|
120
|
-
* @param getValueByPath Function to extract value from response
|
|
121
|
-
* @param responseIndexToVariable Optional mapping from response index to variable name
|
|
122
|
-
*/
|
|
123
|
-
function resolveValue(expr, responses, variables, getValueByPath, responseIndexToVariable) {
|
|
124
|
-
const trimmed = expr.trim();
|
|
125
|
-
// Check for $N.path reference
|
|
126
|
-
const refMatch = trimmed.match(/^\$(\d+)\.(.+)$/);
|
|
127
|
-
if (refMatch) {
|
|
128
|
-
const responseIdx = parseInt(refMatch[1], 10); // Keep 1-based for tracking
|
|
129
|
-
const responseIndex = responseIdx - 1; // Convert to 0-based for array access
|
|
130
|
-
const path = refMatch[2];
|
|
131
|
-
if (responseIndex < 0 || responseIndex >= responses.length) {
|
|
132
|
-
return {
|
|
133
|
-
value: undefined,
|
|
134
|
-
error: `Response $${refMatch[1]} does not exist (only ${responses.length} responses so far)`
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
const response = responses[responseIndex];
|
|
138
|
-
const value = getValueByPath(response, path);
|
|
139
|
-
return {
|
|
140
|
-
value,
|
|
141
|
-
responseIndex: responseIdx,
|
|
142
|
-
response,
|
|
143
|
-
jsonPath: path,
|
|
144
|
-
variableName: responseIndexToVariable?.get(responseIdx)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
// Check for $N without path (invalid in new syntax)
|
|
148
|
-
if (/^\$\d+$/.test(trimmed)) {
|
|
149
|
-
return {
|
|
150
|
-
value: undefined,
|
|
151
|
-
error: `Invalid reference: ${trimmed}. Use $N.body for full body or $N.status, $N.headers, etc.`
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
// Check for variable.path reference (e.g., user.body.username where user is a captured response)
|
|
155
|
-
const varPathMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)((?:\.[a-zA-Z_][a-zA-Z0-9_]*|\[\d+\])+)$/);
|
|
156
|
-
if (varPathMatch) {
|
|
157
|
-
const varName = varPathMatch[1];
|
|
158
|
-
const pathPart = varPathMatch[2];
|
|
159
|
-
if (varName in variables) {
|
|
160
|
-
const varValue = variables[varName];
|
|
161
|
-
// If the variable is an object (like HttpResponse), navigate the path
|
|
162
|
-
if (typeof varValue === 'object' && varValue !== null) {
|
|
163
|
-
const path = pathPart.replace(/^\./, ''); // Remove leading dot
|
|
164
|
-
const value = getNestedValue(varValue, path);
|
|
165
|
-
// Check if this looks like an HttpResponse (has status and body)
|
|
166
|
-
const isHttpResponse = 'status' in varValue && 'body' in varValue;
|
|
167
|
-
return {
|
|
168
|
-
value,
|
|
169
|
-
variableName: varName,
|
|
170
|
-
jsonPath: path,
|
|
171
|
-
response: isHttpResponse ? varValue : undefined
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
// If it's a string, try to parse as JSON
|
|
175
|
-
if (typeof varValue === 'string') {
|
|
176
|
-
try {
|
|
177
|
-
const parsed = JSON.parse(varValue);
|
|
178
|
-
const path = pathPart.replace(/^\./, '');
|
|
179
|
-
const value = getNestedValue(parsed, path);
|
|
180
|
-
return { value, variableName: varName, jsonPath: path };
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
return { value: undefined, error: `Cannot access path on non-object variable: ${varName}` };
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return { value: undefined, error: `Variable '${varName}' is not defined` };
|
|
188
|
-
}
|
|
189
|
-
// Check for simple variable reference (no path) - e.g., just "user" or "token"
|
|
190
|
-
const simpleVarMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_]*)$/);
|
|
191
|
-
if (simpleVarMatch) {
|
|
192
|
-
const varName = simpleVarMatch[1];
|
|
193
|
-
// Don't treat type keywords as variables
|
|
194
|
-
if (!['true', 'false', 'null', 'string', 'number', 'boolean', 'object', 'array', 'undefined'].includes(varName.toLowerCase())) {
|
|
195
|
-
if (varName in variables) {
|
|
196
|
-
const varValue = variables[varName];
|
|
197
|
-
// Try to parse numbers
|
|
198
|
-
if (typeof varValue === 'string' && /^-?\d+(\.\d+)?$/.test(varValue)) {
|
|
199
|
-
return { value: parseFloat(varValue) };
|
|
200
|
-
}
|
|
201
|
-
return { value: varValue };
|
|
202
|
-
}
|
|
203
|
-
// Variable not found - but could also be a type name for isType, so don't error here
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
// Check for variable reference {{varName}}
|
|
207
|
-
const varMatch = trimmed.match(/^\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}$/);
|
|
208
|
-
if (varMatch) {
|
|
209
|
-
const varName = varMatch[1];
|
|
210
|
-
if (varName in variables) {
|
|
211
|
-
// Try to parse as number if it looks like one
|
|
212
|
-
const varValue = variables[varName];
|
|
213
|
-
if (typeof varValue === 'string' && /^-?\d+(\.\d+)?$/.test(varValue)) {
|
|
214
|
-
return { value: parseFloat(varValue) };
|
|
215
|
-
}
|
|
216
|
-
return { value: varValue };
|
|
217
|
-
}
|
|
218
|
-
return { value: undefined, error: `Variable {{${varName}}} is not defined` };
|
|
219
|
-
}
|
|
220
|
-
// Check for string literal
|
|
221
|
-
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
222
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
223
|
-
return { value: trimmed.slice(1, -1) };
|
|
224
|
-
}
|
|
225
|
-
// Check for number literal
|
|
226
|
-
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
227
|
-
return { value: parseFloat(trimmed) };
|
|
228
|
-
}
|
|
229
|
-
// Check for boolean literals
|
|
230
|
-
if (trimmed.toLowerCase() === 'true') {
|
|
231
|
-
return { value: true };
|
|
232
|
-
}
|
|
233
|
-
if (trimmed.toLowerCase() === 'false') {
|
|
234
|
-
return { value: false };
|
|
235
|
-
}
|
|
236
|
-
// Check for null
|
|
237
|
-
if (trimmed.toLowerCase() === 'null') {
|
|
238
|
-
return { value: null };
|
|
239
|
-
}
|
|
240
|
-
// Check for empty array literal
|
|
241
|
-
if (trimmed === '[]') {
|
|
242
|
-
return { value: [] };
|
|
243
|
-
}
|
|
244
|
-
// Check for empty object literal
|
|
245
|
-
if (trimmed === '{}') {
|
|
246
|
-
return { value: {} };
|
|
247
|
-
}
|
|
248
|
-
// Unknown expression type
|
|
249
|
-
return { value: undefined, error: `Cannot resolve expression: ${trimmed}` };
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Helper function to get nested value from an object using dot notation path
|
|
253
|
-
*/
|
|
254
|
-
function getNestedValue(obj, path) {
|
|
255
|
-
if (!path || obj === null || obj === undefined) {
|
|
256
|
-
return obj;
|
|
257
|
-
}
|
|
258
|
-
// Convert [0] to .0 and split by dots
|
|
259
|
-
const parts = path.replace(/\[(\d+)\]/g, '.$1').split('.').filter(p => p !== '');
|
|
260
|
-
let current = obj;
|
|
261
|
-
for (const part of parts) {
|
|
262
|
-
if (current === null || current === undefined) {
|
|
263
|
-
return undefined;
|
|
264
|
-
}
|
|
265
|
-
current = current[part];
|
|
266
|
-
}
|
|
267
|
-
return current;
|
|
268
|
-
}
|
|
269
|
-
function areValuesEqual(leftValue, rightValue) {
|
|
270
|
-
if (leftValue === rightValue) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
if (leftValue === null || rightValue === null || leftValue === undefined || rightValue === undefined) {
|
|
274
|
-
return leftValue === rightValue;
|
|
275
|
-
}
|
|
276
|
-
const leftIsArray = Array.isArray(leftValue);
|
|
277
|
-
const rightIsArray = Array.isArray(rightValue);
|
|
278
|
-
if (leftIsArray || rightIsArray) {
|
|
279
|
-
if (!leftIsArray || !rightIsArray) {
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
282
|
-
if (leftValue.length !== rightValue.length) {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
for (let i = 0; i < leftValue.length; i++) {
|
|
286
|
-
if (!areValuesEqual(leftValue[i], rightValue[i])) {
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return true;
|
|
291
|
-
}
|
|
292
|
-
const leftIsObject = typeof leftValue === 'object';
|
|
293
|
-
const rightIsObject = typeof rightValue === 'object';
|
|
294
|
-
if (leftIsObject || rightIsObject) {
|
|
295
|
-
if (!leftIsObject || !rightIsObject) {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
const leftKeys = Object.keys(leftValue);
|
|
299
|
-
const rightKeys = Object.keys(rightValue);
|
|
300
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
for (const key of leftKeys) {
|
|
304
|
-
if (!Object.prototype.hasOwnProperty.call(rightValue, key)) {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
if (!areValuesEqual(leftValue[key], rightValue[key])) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
return leftValue == rightValue;
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Evaluates an assertion.
|
|
317
|
-
*
|
|
318
|
-
* @param assertion Parsed assertion
|
|
319
|
-
* @param responses Array of responses from the sequence
|
|
320
|
-
* @param variables Current runtime variables (can contain objects like HttpResponse)
|
|
321
|
-
* @param getValueByPath Function to extract value from response
|
|
322
|
-
* @param responseIndexToVariable Optional mapping from response index (1-based) to variable name
|
|
323
|
-
* @param basePath Optional base path for resolving relative schema file paths
|
|
324
|
-
*/
|
|
325
|
-
function evaluateAssertion(assertion, responses, variables, getValueByPath, responseIndexToVariable, basePath) {
|
|
326
|
-
const leftResult = resolveValue(assertion.leftExpr, responses, variables, getValueByPath, responseIndexToVariable);
|
|
327
|
-
// Helper to build failure context from left result
|
|
328
|
-
const buildFailureContext = () => ({
|
|
329
|
-
responseIndex: leftResult.responseIndex,
|
|
330
|
-
friendlyName: leftResult.variableName,
|
|
331
|
-
relatedResponse: leftResult.response,
|
|
332
|
-
jsonPath: leftResult.jsonPath
|
|
333
|
-
});
|
|
334
|
-
// Handle resolution errors
|
|
335
|
-
if (leftResult.error) {
|
|
336
|
-
return {
|
|
337
|
-
passed: false,
|
|
338
|
-
expression: formatExpression(assertion),
|
|
339
|
-
message: assertion.message,
|
|
340
|
-
operator: assertion.operator,
|
|
341
|
-
leftValue: undefined,
|
|
342
|
-
leftExpression: assertion.leftExpr,
|
|
343
|
-
rightExpression: assertion.rightExpr,
|
|
344
|
-
error: leftResult.error,
|
|
345
|
-
...buildFailureContext()
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
const leftValue = leftResult.value;
|
|
349
|
-
// Handle unary operators
|
|
350
|
-
if (assertion.operator === 'exists') {
|
|
351
|
-
const passed = leftValue !== undefined && leftValue !== null;
|
|
352
|
-
return {
|
|
353
|
-
passed,
|
|
354
|
-
expression: formatExpression(assertion),
|
|
355
|
-
message: assertion.message,
|
|
356
|
-
operator: assertion.operator,
|
|
357
|
-
leftValue,
|
|
358
|
-
leftExpression: assertion.leftExpr,
|
|
359
|
-
...(!passed ? buildFailureContext() : {})
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
if (assertion.operator === '!exists') {
|
|
363
|
-
const passed = leftValue === undefined || leftValue === null;
|
|
364
|
-
return {
|
|
365
|
-
passed,
|
|
366
|
-
expression: formatExpression(assertion),
|
|
367
|
-
message: assertion.message,
|
|
368
|
-
operator: assertion.operator,
|
|
369
|
-
leftValue,
|
|
370
|
-
leftExpression: assertion.leftExpr,
|
|
371
|
-
...(!passed ? buildFailureContext() : {})
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
// Binary operators require right expression
|
|
375
|
-
if (!assertion.rightExpr) {
|
|
376
|
-
return {
|
|
377
|
-
passed: false,
|
|
378
|
-
expression: formatExpression(assertion),
|
|
379
|
-
message: assertion.message,
|
|
380
|
-
operator: assertion.operator,
|
|
381
|
-
leftValue,
|
|
382
|
-
leftExpression: assertion.leftExpr,
|
|
383
|
-
error: `Operator ${assertion.operator} requires a right-hand value`,
|
|
384
|
-
...buildFailureContext()
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
// For isType operator, the right side is a literal type name, not a value to resolve
|
|
388
|
-
if (assertion.operator === 'isType') {
|
|
389
|
-
const expectedType = assertion.rightExpr.toLowerCase();
|
|
390
|
-
let actualType;
|
|
391
|
-
if (leftValue === null) {
|
|
392
|
-
actualType = 'null';
|
|
393
|
-
}
|
|
394
|
-
else if (Array.isArray(leftValue)) {
|
|
395
|
-
actualType = 'array';
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
actualType = typeof leftValue;
|
|
399
|
-
}
|
|
400
|
-
const passed = actualType === expectedType;
|
|
401
|
-
return {
|
|
402
|
-
passed,
|
|
403
|
-
expression: formatExpression(assertion),
|
|
404
|
-
message: assertion.message,
|
|
405
|
-
operator: assertion.operator,
|
|
406
|
-
leftValue,
|
|
407
|
-
rightValue: expectedType,
|
|
408
|
-
leftExpression: assertion.leftExpr,
|
|
409
|
-
rightExpression: assertion.rightExpr,
|
|
410
|
-
...(!passed ? buildFailureContext() : {})
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
// For matchesSchema operator, validate against a JSON Schema file
|
|
414
|
-
if (assertion.operator === 'matchesSchema') {
|
|
415
|
-
// Right side is a file path (with or without quotes)
|
|
416
|
-
let schemaPath = assertion.rightExpr;
|
|
417
|
-
// Remove surrounding quotes if present
|
|
418
|
-
if ((schemaPath.startsWith('"') && schemaPath.endsWith('"')) ||
|
|
419
|
-
(schemaPath.startsWith("'") && schemaPath.endsWith("'"))) {
|
|
420
|
-
schemaPath = schemaPath.slice(1, -1);
|
|
421
|
-
}
|
|
422
|
-
const validationResult = (0, schemaGenerator_1.validateAgainstSchemaDetailed)(leftValue, schemaPath, basePath);
|
|
423
|
-
const passed = validationResult.valid;
|
|
424
|
-
return {
|
|
425
|
-
passed,
|
|
426
|
-
expression: formatExpression(assertion),
|
|
427
|
-
message: assertion.message,
|
|
428
|
-
operator: assertion.operator,
|
|
429
|
-
leftValue,
|
|
430
|
-
rightValue: schemaPath,
|
|
431
|
-
leftExpression: assertion.leftExpr,
|
|
432
|
-
rightExpression: assertion.rightExpr,
|
|
433
|
-
error: !passed && validationResult.errorStrings ? validationResult.errorStrings.join('; ') : undefined,
|
|
434
|
-
schemaErrors: validationResult.errors,
|
|
435
|
-
schemaPath: validationResult.resolvedSchemaPath,
|
|
436
|
-
schema: validationResult.schema,
|
|
437
|
-
...(!passed ? buildFailureContext() : {})
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
const rightResult = resolveValue(assertion.rightExpr, responses, variables, getValueByPath, responseIndexToVariable);
|
|
441
|
-
if (rightResult.error) {
|
|
442
|
-
return {
|
|
443
|
-
passed: false,
|
|
444
|
-
expression: formatExpression(assertion),
|
|
445
|
-
message: assertion.message,
|
|
446
|
-
operator: assertion.operator,
|
|
447
|
-
leftValue,
|
|
448
|
-
rightValue: undefined,
|
|
449
|
-
leftExpression: assertion.leftExpr,
|
|
450
|
-
rightExpression: assertion.rightExpr,
|
|
451
|
-
error: rightResult.error,
|
|
452
|
-
...buildFailureContext()
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
const rightValue = rightResult.value;
|
|
456
|
-
let passed = false;
|
|
457
|
-
switch (assertion.operator) {
|
|
458
|
-
case '==':
|
|
459
|
-
passed = areValuesEqual(leftValue, rightValue);
|
|
460
|
-
break;
|
|
461
|
-
case '!=':
|
|
462
|
-
passed = !areValuesEqual(leftValue, rightValue);
|
|
463
|
-
break;
|
|
464
|
-
case '>':
|
|
465
|
-
passed = Number(leftValue) > Number(rightValue);
|
|
466
|
-
break;
|
|
467
|
-
case '>=':
|
|
468
|
-
passed = Number(leftValue) >= Number(rightValue);
|
|
469
|
-
break;
|
|
470
|
-
case '<':
|
|
471
|
-
passed = Number(leftValue) < Number(rightValue);
|
|
472
|
-
break;
|
|
473
|
-
case '<=':
|
|
474
|
-
passed = Number(leftValue) <= Number(rightValue);
|
|
475
|
-
break;
|
|
476
|
-
case 'contains':
|
|
477
|
-
passed = String(leftValue).includes(String(rightValue));
|
|
478
|
-
break;
|
|
479
|
-
case 'startsWith':
|
|
480
|
-
passed = String(leftValue).startsWith(String(rightValue));
|
|
481
|
-
break;
|
|
482
|
-
case 'endsWith':
|
|
483
|
-
passed = String(leftValue).endsWith(String(rightValue));
|
|
484
|
-
break;
|
|
485
|
-
case 'matches':
|
|
486
|
-
try {
|
|
487
|
-
// Remove regex delimiters if present
|
|
488
|
-
let pattern = String(rightValue);
|
|
489
|
-
if (pattern.startsWith('/') && pattern.lastIndexOf('/') > 0) {
|
|
490
|
-
const lastSlash = pattern.lastIndexOf('/');
|
|
491
|
-
const flags = pattern.substring(lastSlash + 1);
|
|
492
|
-
pattern = pattern.substring(1, lastSlash);
|
|
493
|
-
passed = new RegExp(pattern, flags).test(String(leftValue));
|
|
494
|
-
}
|
|
495
|
-
else {
|
|
496
|
-
passed = new RegExp(pattern).test(String(leftValue));
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
catch (e) {
|
|
500
|
-
return {
|
|
501
|
-
passed: false,
|
|
502
|
-
expression: formatExpression(assertion),
|
|
503
|
-
message: assertion.message,
|
|
504
|
-
operator: assertion.operator,
|
|
505
|
-
leftValue,
|
|
506
|
-
rightValue,
|
|
507
|
-
leftExpression: assertion.leftExpr,
|
|
508
|
-
rightExpression: assertion.rightExpr,
|
|
509
|
-
error: `Invalid regex pattern: ${rightValue}`,
|
|
510
|
-
...buildFailureContext()
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
break;
|
|
514
|
-
// Note: isType is handled earlier before resolveValue is called
|
|
515
|
-
}
|
|
516
|
-
return {
|
|
517
|
-
passed,
|
|
518
|
-
expression: formatExpression(assertion),
|
|
519
|
-
message: assertion.message,
|
|
520
|
-
operator: assertion.operator,
|
|
521
|
-
leftValue,
|
|
522
|
-
rightValue,
|
|
523
|
-
leftExpression: assertion.leftExpr,
|
|
524
|
-
rightExpression: assertion.rightExpr,
|
|
525
|
-
...(!passed ? buildFailureContext() : {})
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* Format the assertion expression for display
|
|
530
|
-
*/
|
|
531
|
-
function formatExpression(assertion) {
|
|
532
|
-
if (assertion.rightExpr) {
|
|
533
|
-
return `${assertion.leftExpr} ${assertion.operator} ${assertion.rightExpr}`;
|
|
534
|
-
}
|
|
535
|
-
return `${assertion.leftExpr} ${assertion.operator}`;
|
|
536
|
-
}
|
|
537
|
-
//# sourceMappingURL=assertionRunner.js.map
|