@riddledc/riddle-proof 0.7.126 → 0.7.128
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/README.md +26 -2
- package/dist/{chunk-55KDZEB3.js → chunk-TO3FVF5S.js} +502 -4
- package/dist/cli.cjs +520 -5
- package/dist/cli.js +19 -2
- package/dist/index.cjs +502 -4
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/profile.cjs +502 -4
- package/dist/profile.d.cts +32 -1
- package/dist/profile.d.ts +32 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/dist/profile.cjs
CHANGED
|
@@ -186,6 +186,9 @@ function valueFromOwn(input, ...keys) {
|
|
|
186
186
|
function numberValue(value) {
|
|
187
187
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
188
188
|
}
|
|
189
|
+
function booleanValue(value) {
|
|
190
|
+
return typeof value === "boolean" ? value : void 0;
|
|
191
|
+
}
|
|
189
192
|
function horizontalBoundsOverflowPx(value) {
|
|
190
193
|
if (!isRecord(value)) return 0;
|
|
191
194
|
let max = maxPositiveNumber(
|
|
@@ -280,6 +283,187 @@ function toJsonValue(value) {
|
|
|
280
283
|
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
281
284
|
return String(value);
|
|
282
285
|
}
|
|
286
|
+
function jsonValueType(value) {
|
|
287
|
+
if (value === null) return "null";
|
|
288
|
+
if (Array.isArray(value)) return "array";
|
|
289
|
+
if (typeof value === "boolean") return "boolean";
|
|
290
|
+
if (typeof value === "number") return "number";
|
|
291
|
+
if (typeof value === "string") return "string";
|
|
292
|
+
return "object";
|
|
293
|
+
}
|
|
294
|
+
function compactJsonAssertionSample(value, depth = 0) {
|
|
295
|
+
if (typeof value === "string") return value.length > 240 ? `${value.slice(0, 237)}...` : value;
|
|
296
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return toJsonValue(value);
|
|
297
|
+
if (Array.isArray(value)) {
|
|
298
|
+
if (depth >= 2) return `[array:${value.length}]`;
|
|
299
|
+
return value.slice(0, 3).map((item) => compactJsonAssertionSample(item, depth + 1));
|
|
300
|
+
}
|
|
301
|
+
if (isRecord(value)) {
|
|
302
|
+
const entries = Object.entries(value).slice(0, 8);
|
|
303
|
+
if (depth >= 2) return `[object:${Object.keys(value).length} keys]`;
|
|
304
|
+
return Object.fromEntries(entries.map(([key, child]) => [key, compactJsonAssertionSample(child, depth + 1)]));
|
|
305
|
+
}
|
|
306
|
+
return String(value);
|
|
307
|
+
}
|
|
308
|
+
function attachJsonAssertionObservedValue(result, value) {
|
|
309
|
+
const type = jsonValueType(value);
|
|
310
|
+
if (type === "array" && Array.isArray(value)) {
|
|
311
|
+
result.observed_length = value.length;
|
|
312
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
313
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (type === "object" && isRecord(value)) {
|
|
317
|
+
const keyCount = Object.keys(value).length;
|
|
318
|
+
result.observed_key_count = keyCount;
|
|
319
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
320
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
result.observed = toJsonValue(value);
|
|
324
|
+
}
|
|
325
|
+
function deepJsonEqual(left, right) {
|
|
326
|
+
if (left === right) return true;
|
|
327
|
+
if (typeof left !== typeof right) return false;
|
|
328
|
+
if (left === null || right === null) return left === right;
|
|
329
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
330
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
331
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
332
|
+
return left.every((item, index) => deepJsonEqual(item, right[index]));
|
|
333
|
+
}
|
|
334
|
+
if (!isRecord(left) || !isRecord(right)) return false;
|
|
335
|
+
const leftKeys = Object.keys(left).sort();
|
|
336
|
+
const rightKeys = Object.keys(right).sort();
|
|
337
|
+
if (!deepJsonEqual(leftKeys, rightKeys)) return false;
|
|
338
|
+
return leftKeys.every((key) => deepJsonEqual(left[key], right[key]));
|
|
339
|
+
}
|
|
340
|
+
function jsonContains(observed, expected) {
|
|
341
|
+
if (typeof observed === "string" && typeof expected === "string") {
|
|
342
|
+
return observed.includes(expected);
|
|
343
|
+
}
|
|
344
|
+
if (Array.isArray(observed)) {
|
|
345
|
+
return observed.some((item) => deepJsonEqual(item, expected));
|
|
346
|
+
}
|
|
347
|
+
if (isRecord(observed) && isRecord(expected)) {
|
|
348
|
+
return Object.entries(expected).every(([key, value]) => hasOwn(observed, key) && deepJsonEqual(observed[key], value));
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
function parseJsonPathSegments(path) {
|
|
353
|
+
let input = path.trim();
|
|
354
|
+
if (!input) throw new Error("path is empty");
|
|
355
|
+
if (input === "$") return [];
|
|
356
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
357
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
358
|
+
const segments = [];
|
|
359
|
+
let token = "";
|
|
360
|
+
const pushToken = () => {
|
|
361
|
+
if (!token) return;
|
|
362
|
+
segments.push(token);
|
|
363
|
+
token = "";
|
|
364
|
+
};
|
|
365
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
366
|
+
const char = input[index];
|
|
367
|
+
if (char === ".") {
|
|
368
|
+
pushToken();
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (char !== "[") {
|
|
372
|
+
token += char;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
pushToken();
|
|
376
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
377
|
+
if (closeIndex === -1) throw new Error(`unterminated bracket at ${index}`);
|
|
378
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
379
|
+
if (!bracket) throw new Error(`empty bracket at ${index}`);
|
|
380
|
+
if (/^\d+$/.test(bracket)) {
|
|
381
|
+
segments.push(Number(bracket));
|
|
382
|
+
} else if (bracket.startsWith('"') && bracket.endsWith('"') || bracket.startsWith("'") && bracket.endsWith("'")) {
|
|
383
|
+
const quoted = bracket.startsWith("'") ? `"${bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : bracket;
|
|
384
|
+
segments.push(String(JSON.parse(quoted)));
|
|
385
|
+
} else {
|
|
386
|
+
segments.push(bracket);
|
|
387
|
+
}
|
|
388
|
+
index = closeIndex;
|
|
389
|
+
}
|
|
390
|
+
pushToken();
|
|
391
|
+
return segments;
|
|
392
|
+
}
|
|
393
|
+
function resolveJsonPath(root, path) {
|
|
394
|
+
let segments;
|
|
395
|
+
try {
|
|
396
|
+
segments = parseJsonPathSegments(path);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return { exists: false, error: String(error instanceof Error ? error.message : error) };
|
|
399
|
+
}
|
|
400
|
+
let current = root;
|
|
401
|
+
for (const segment of segments) {
|
|
402
|
+
if (typeof segment === "number") {
|
|
403
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
404
|
+
current = current[segment];
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (!isRecord(current) || !hasOwn(current, segment)) return { exists: false };
|
|
408
|
+
current = current[segment];
|
|
409
|
+
}
|
|
410
|
+
return { exists: true, value: current };
|
|
411
|
+
}
|
|
412
|
+
function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
|
|
413
|
+
const resolved = resolveJsonPath(root, assertion.path);
|
|
414
|
+
const errors = [];
|
|
415
|
+
const result = {
|
|
416
|
+
label: assertion.label || assertion.path,
|
|
417
|
+
path: assertion.path,
|
|
418
|
+
ok: true,
|
|
419
|
+
exists: resolved.exists,
|
|
420
|
+
observed_type: resolved.exists ? jsonValueType(resolved.value) : "missing"
|
|
421
|
+
};
|
|
422
|
+
if (resolved.exists) attachJsonAssertionObservedValue(result, resolved.value);
|
|
423
|
+
if (resolved.error) errors.push(resolved.error);
|
|
424
|
+
if (hasOwn(assertion, "exists")) {
|
|
425
|
+
result.expected_exists = assertion.exists;
|
|
426
|
+
if (resolved.exists !== assertion.exists) errors.push(`expected exists=${assertion.exists}`);
|
|
427
|
+
}
|
|
428
|
+
if (hasOwn(assertion, "type")) {
|
|
429
|
+
result.type = assertion.type;
|
|
430
|
+
if (!resolved.exists || jsonValueType(resolved.value) !== assertion.type) errors.push(`expected type ${assertion.type}`);
|
|
431
|
+
}
|
|
432
|
+
if (hasOwn(assertion, "equals")) {
|
|
433
|
+
result.equals = assertion.equals;
|
|
434
|
+
if (!resolved.exists || !deepJsonEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
435
|
+
}
|
|
436
|
+
if (hasOwn(assertion, "not_equals")) {
|
|
437
|
+
result.not_equals = assertion.not_equals;
|
|
438
|
+
if (resolved.exists && deepJsonEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
439
|
+
}
|
|
440
|
+
if (hasOwn(assertion, "contains")) {
|
|
441
|
+
result.contains = assertion.contains;
|
|
442
|
+
if (!resolved.exists || !jsonContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
443
|
+
}
|
|
444
|
+
result.ok = errors.length === 0;
|
|
445
|
+
if (errors.length) result.errors = errors;
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
function evaluateHttpStatusBodyJsonAssertions(bodyText, assertions) {
|
|
449
|
+
const expected = assertions?.filter((assertion) => assertion.path) ?? [];
|
|
450
|
+
if (!expected.length) return [];
|
|
451
|
+
let parsed;
|
|
452
|
+
try {
|
|
453
|
+
parsed = JSON.parse(bodyText);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
const message = `response body is not valid JSON: ${String(error instanceof Error ? error.message : error).slice(0, 200)}`;
|
|
456
|
+
return expected.map((assertion) => ({
|
|
457
|
+
label: assertion.label || assertion.path,
|
|
458
|
+
path: assertion.path,
|
|
459
|
+
ok: false,
|
|
460
|
+
exists: false,
|
|
461
|
+
observed_type: "missing",
|
|
462
|
+
errors: [message]
|
|
463
|
+
}));
|
|
464
|
+
}
|
|
465
|
+
return expected.map((assertion) => evaluateHttpStatusBodyJsonAssertion(parsed, assertion));
|
|
466
|
+
}
|
|
283
467
|
function compactProfileSetupSummaryText(value, limit = 160) {
|
|
284
468
|
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
285
469
|
if (!text) return void 0;
|
|
@@ -934,6 +1118,46 @@ function validateRegexPatterns(patterns, label) {
|
|
|
934
1118
|
}
|
|
935
1119
|
}
|
|
936
1120
|
}
|
|
1121
|
+
function normalizeHttpStatusBodyJsonAssertions(value, label) {
|
|
1122
|
+
if (value === void 0) return void 0;
|
|
1123
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
1124
|
+
if (!value.length) throw new Error(`${label} must not be empty.`);
|
|
1125
|
+
return value.map((item, index) => {
|
|
1126
|
+
const itemLabel = `${label}[${index}]`;
|
|
1127
|
+
if (typeof item === "string") {
|
|
1128
|
+
const path2 = stringValue(item);
|
|
1129
|
+
if (!path2) throw new Error(`${itemLabel} path must not be empty.`);
|
|
1130
|
+
return { path: path2, exists: true };
|
|
1131
|
+
}
|
|
1132
|
+
if (!isRecord(item)) throw new Error(`${itemLabel} must be an object or JSON path string.`);
|
|
1133
|
+
const path = stringFromOwn(item, "path", "json_path", "jsonPath", "key");
|
|
1134
|
+
if (!path) throw new Error(`${itemLabel}.path is required.`);
|
|
1135
|
+
const assertion = {
|
|
1136
|
+
label: stringValue(item.label),
|
|
1137
|
+
path
|
|
1138
|
+
};
|
|
1139
|
+
const exists = booleanValue(valueFromOwn(item, "exists", "present"));
|
|
1140
|
+
if (exists !== void 0) assertion.exists = exists;
|
|
1141
|
+
const type = stringValue(valueFromOwn(item, "type", "value_type", "valueType"));
|
|
1142
|
+
if (type !== void 0) {
|
|
1143
|
+
const allowedTypes = ["array", "boolean", "null", "number", "object", "string"];
|
|
1144
|
+
if (!allowedTypes.includes(type)) {
|
|
1145
|
+
throw new Error(`${itemLabel}.type must be one of ${allowedTypes.join(", ")}.`);
|
|
1146
|
+
}
|
|
1147
|
+
assertion.type = type;
|
|
1148
|
+
}
|
|
1149
|
+
const equalsValue = valueFromOwn(item, "equals", "expected", "expected_value", "expectedValue", "value");
|
|
1150
|
+
if (equalsValue !== void 0) assertion.equals = toJsonValue(equalsValue);
|
|
1151
|
+
const notEqualsValue = valueFromOwn(item, "not_equals", "notEquals", "forbidden", "forbidden_value", "forbiddenValue");
|
|
1152
|
+
if (notEqualsValue !== void 0) assertion.not_equals = toJsonValue(notEqualsValue);
|
|
1153
|
+
const containsValue = valueFromOwn(item, "contains", "includes", "contains_value", "containsValue", "include");
|
|
1154
|
+
if (containsValue !== void 0) assertion.contains = toJsonValue(containsValue);
|
|
1155
|
+
if (assertion.exists === void 0 && assertion.type === void 0 && !hasOwn(assertion, "equals") && !hasOwn(assertion, "not_equals") && !hasOwn(assertion, "contains")) {
|
|
1156
|
+
assertion.exists = true;
|
|
1157
|
+
}
|
|
1158
|
+
return assertion;
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
937
1161
|
function isDialogCountCheckType(type) {
|
|
938
1162
|
return type === "dialog_count_equals" || type === "dialog_accept_count_equals" || type === "dialog_dismiss_count_equals";
|
|
939
1163
|
}
|
|
@@ -1033,6 +1257,10 @@ function normalizeCheck(input, index) {
|
|
|
1033
1257
|
`checks[${index}] body_not_patterns`
|
|
1034
1258
|
) : void 0;
|
|
1035
1259
|
if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
|
|
1260
|
+
const bodyJsonAssertions = isHttpStatusCheck ? normalizeHttpStatusBodyJsonAssertions(
|
|
1261
|
+
input.body_json_assertions ?? input.bodyJsonAssertions ?? input.json_body_assertions ?? input.jsonBodyAssertions ?? input.json_assertions ?? input.jsonAssertions ?? input.response_json_assertions ?? input.responseJsonAssertions,
|
|
1262
|
+
`checks[${index}] body_json_assertions`
|
|
1263
|
+
) : void 0;
|
|
1036
1264
|
if (isLinkStatusCheck) {
|
|
1037
1265
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
1038
1266
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -1058,6 +1286,7 @@ function normalizeCheck(input, index) {
|
|
|
1058
1286
|
body_contains: bodyContains,
|
|
1059
1287
|
body_not_contains: bodyNotContains,
|
|
1060
1288
|
body_not_patterns: bodyNotPatterns,
|
|
1289
|
+
body_json_assertions: bodyJsonAssertions,
|
|
1061
1290
|
expected_texts: expectedTexts,
|
|
1062
1291
|
link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
|
|
1063
1292
|
source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
|
|
@@ -1264,6 +1493,38 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
1264
1493
|
const observed = isRecord(result.body_not_patterns) ? result.body_not_patterns : {};
|
|
1265
1494
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
1266
1495
|
}
|
|
1496
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
1497
|
+
const expected = check.body_json_assertions?.filter((assertion) => assertion.path) ?? [];
|
|
1498
|
+
if (!expected.length) return [];
|
|
1499
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
1500
|
+
return expected.map((assertion) => ({
|
|
1501
|
+
label: assertion.label || assertion.path,
|
|
1502
|
+
path: assertion.path,
|
|
1503
|
+
ok: false,
|
|
1504
|
+
exists: false,
|
|
1505
|
+
observed_type: "missing",
|
|
1506
|
+
errors: ["body_json_assertions evidence missing"]
|
|
1507
|
+
}));
|
|
1508
|
+
}
|
|
1509
|
+
return result.body_json_assertions.filter((assertion) => isRecord(assertion) && assertion.ok !== true).map((assertion) => ({
|
|
1510
|
+
label: stringValue(assertion.label) || stringValue(assertion.path) || "json assertion",
|
|
1511
|
+
path: stringValue(assertion.path) || "",
|
|
1512
|
+
ok: false,
|
|
1513
|
+
exists: assertion.exists === true,
|
|
1514
|
+
observed: hasOwn(assertion, "observed") ? toJsonValue(assertion.observed) : void 0,
|
|
1515
|
+
observed_sample: hasOwn(assertion, "observed_sample") ? toJsonValue(assertion.observed_sample) : void 0,
|
|
1516
|
+
observed_length: numberValue(assertion.observed_length),
|
|
1517
|
+
observed_key_count: numberValue(assertion.observed_key_count),
|
|
1518
|
+
observed_omitted_count: numberValue(assertion.observed_omitted_count),
|
|
1519
|
+
observed_type: stringValue(assertion.observed_type) || "missing",
|
|
1520
|
+
expected_exists: booleanValue(assertion.expected_exists),
|
|
1521
|
+
equals: hasOwn(assertion, "equals") ? toJsonValue(assertion.equals) : void 0,
|
|
1522
|
+
not_equals: hasOwn(assertion, "not_equals") ? toJsonValue(assertion.not_equals) : void 0,
|
|
1523
|
+
contains: hasOwn(assertion, "contains") ? toJsonValue(assertion.contains) : void 0,
|
|
1524
|
+
type: stringValue(assertion.type),
|
|
1525
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : void 0
|
|
1526
|
+
}));
|
|
1527
|
+
}
|
|
1267
1528
|
function linkStatusResultOk(result, check) {
|
|
1268
1529
|
const status = numberValue(result.status);
|
|
1269
1530
|
if (!httpStatusIsAllowed(status, check)) return false;
|
|
@@ -1281,6 +1542,7 @@ function linkStatusResultOk(result, check) {
|
|
|
1281
1542
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
1282
1543
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
1283
1544
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
1545
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
1284
1546
|
return true;
|
|
1285
1547
|
}
|
|
1286
1548
|
function responseHeader(response, name) {
|
|
@@ -1349,7 +1611,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1349
1611
|
statusText = typeof response.statusText === "string" ? response.statusText : "";
|
|
1350
1612
|
result.content_type = responseHeader(response, "content-type");
|
|
1351
1613
|
result.content_length = responseContentLength(response);
|
|
1352
|
-
const shouldReadBody = check.require_nonzero_bytes === true || typeof check.min_bytes === "number" || Boolean(check.body_contains?.length) || Boolean(check.body_not_contains?.length) || Boolean(check.body_not_patterns?.length);
|
|
1614
|
+
const shouldReadBody = check.require_nonzero_bytes === true || typeof check.min_bytes === "number" || Boolean(check.body_contains?.length) || Boolean(check.body_not_contains?.length) || Boolean(check.body_not_patterns?.length) || Boolean(check.body_json_assertions?.length);
|
|
1353
1615
|
if (shouldReadBody && method !== "HEAD") {
|
|
1354
1616
|
const body = await responseBodyText(response);
|
|
1355
1617
|
result.bytes = body.bytes;
|
|
@@ -1362,6 +1624,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1362
1624
|
if (check.body_not_patterns?.length) {
|
|
1363
1625
|
result.body_not_patterns = Object.fromEntries(check.body_not_patterns.filter(Boolean).map((pattern) => [pattern, new RegExp(pattern).test(body.text)]));
|
|
1364
1626
|
}
|
|
1627
|
+
if (check.body_json_assertions?.length) {
|
|
1628
|
+
result.body_json_assertions = evaluateHttpStatusBodyJsonAssertions(body.text, check.body_json_assertions);
|
|
1629
|
+
}
|
|
1365
1630
|
}
|
|
1366
1631
|
} catch (caught) {
|
|
1367
1632
|
error = String(caught instanceof Error ? caught.message : caught).slice(0, 500);
|
|
@@ -1370,6 +1635,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1370
1635
|
const bodyContainsMissing = httpStatusBodyContainsFailures(result, check);
|
|
1371
1636
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(result, check);
|
|
1372
1637
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(result, check);
|
|
1638
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(result, check);
|
|
1373
1639
|
const ok = !error && linkStatusResultOk(result, check);
|
|
1374
1640
|
return {
|
|
1375
1641
|
index,
|
|
@@ -1388,7 +1654,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1388
1654
|
body_not_contains: isRecord(result.body_not_contains) ? Object.fromEntries(Object.entries(result.body_not_contains).map(([key, value]) => [key, value === true])) : null,
|
|
1389
1655
|
body_not_contains_found: bodyNotContainsFound,
|
|
1390
1656
|
body_not_patterns: isRecord(result.body_not_patterns) ? Object.fromEntries(Object.entries(result.body_not_patterns).map(([key, value]) => [key, value === true])) : null,
|
|
1391
|
-
body_not_patterns_found: bodyNotPatternsFound
|
|
1657
|
+
body_not_patterns_found: bodyNotPatternsFound,
|
|
1658
|
+
body_json_assertions: Array.isArray(result.body_json_assertions) ? result.body_json_assertions : null,
|
|
1659
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed
|
|
1392
1660
|
};
|
|
1393
1661
|
}
|
|
1394
1662
|
async function preflightRiddleProofProfileHttpStatusChecks(profile, options = {}) {
|
|
@@ -1433,6 +1701,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
1433
1701
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
1434
1702
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
1435
1703
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
1704
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
1436
1705
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
1437
1706
|
failures.push({
|
|
1438
1707
|
code: "http_status_failed",
|
|
@@ -1451,6 +1720,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
1451
1720
|
body_not_contains_found: bodyNotContainsFound,
|
|
1452
1721
|
body_not_patterns: check.body_not_patterns ?? null,
|
|
1453
1722
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
1723
|
+
body_json_assertions: check.body_json_assertions ?? null,
|
|
1724
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
1454
1725
|
body_sample: stringValue(statusEvidence.body_sample) ?? null
|
|
1455
1726
|
});
|
|
1456
1727
|
}
|
|
@@ -1472,6 +1743,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
1472
1743
|
body_not_contains_found: bodyNotContainsFound,
|
|
1473
1744
|
body_not_patterns: isRecord(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
|
|
1474
1745
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
1746
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? toJsonValue(statusEvidence.body_json_assertions) : null,
|
|
1747
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
1475
1748
|
body_sample: stringValue(statusEvidence.body_sample) ?? null,
|
|
1476
1749
|
failures
|
|
1477
1750
|
};
|
|
@@ -2108,6 +2381,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
2108
2381
|
body_contains: check.body_contains ?? [],
|
|
2109
2382
|
body_not_contains: check.body_not_contains ?? [],
|
|
2110
2383
|
body_not_patterns: check.body_not_patterns ?? [],
|
|
2384
|
+
body_json_assertions: toJsonValue(check.body_json_assertions ?? []),
|
|
2111
2385
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
2112
2386
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
2113
2387
|
},
|
|
@@ -2851,6 +3125,40 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
2851
3125
|
: {};
|
|
2852
3126
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
2853
3127
|
}
|
|
3128
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
3129
|
+
const expected = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
3130
|
+
if (!expected.length) return [];
|
|
3131
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
3132
|
+
return expected.map((assertion) => ({
|
|
3133
|
+
label: assertion.label || assertion.path,
|
|
3134
|
+
path: assertion.path,
|
|
3135
|
+
ok: false,
|
|
3136
|
+
exists: false,
|
|
3137
|
+
observed_type: "missing",
|
|
3138
|
+
errors: ["body_json_assertions evidence missing"],
|
|
3139
|
+
}));
|
|
3140
|
+
}
|
|
3141
|
+
return result.body_json_assertions
|
|
3142
|
+
.filter((assertion) => assertion && typeof assertion === "object" && assertion.ok !== true)
|
|
3143
|
+
.map((assertion) => ({
|
|
3144
|
+
label: typeof assertion.label === "string" && assertion.label ? assertion.label : typeof assertion.path === "string" && assertion.path ? assertion.path : "json assertion",
|
|
3145
|
+
path: typeof assertion.path === "string" ? assertion.path : "",
|
|
3146
|
+
ok: false,
|
|
3147
|
+
exists: assertion.exists === true,
|
|
3148
|
+
observed: Object.hasOwn(assertion, "observed") ? assertion.observed : undefined,
|
|
3149
|
+
observed_sample: Object.hasOwn(assertion, "observed_sample") ? assertion.observed_sample : undefined,
|
|
3150
|
+
observed_length: typeof assertion.observed_length === "number" && Number.isFinite(assertion.observed_length) ? assertion.observed_length : undefined,
|
|
3151
|
+
observed_key_count: typeof assertion.observed_key_count === "number" && Number.isFinite(assertion.observed_key_count) ? assertion.observed_key_count : undefined,
|
|
3152
|
+
observed_omitted_count: typeof assertion.observed_omitted_count === "number" && Number.isFinite(assertion.observed_omitted_count) ? assertion.observed_omitted_count : undefined,
|
|
3153
|
+
observed_type: typeof assertion.observed_type === "string" && assertion.observed_type ? assertion.observed_type : "missing",
|
|
3154
|
+
expected_exists: typeof assertion.expected_exists === "boolean" ? assertion.expected_exists : undefined,
|
|
3155
|
+
equals: Object.hasOwn(assertion, "equals") ? assertion.equals : undefined,
|
|
3156
|
+
not_equals: Object.hasOwn(assertion, "not_equals") ? assertion.not_equals : undefined,
|
|
3157
|
+
contains: Object.hasOwn(assertion, "contains") ? assertion.contains : undefined,
|
|
3158
|
+
type: typeof assertion.type === "string" ? assertion.type : undefined,
|
|
3159
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : undefined,
|
|
3160
|
+
}));
|
|
3161
|
+
}
|
|
2854
3162
|
function linkStatusResultOk(result, check) {
|
|
2855
3163
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2856
3164
|
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
@@ -2868,6 +3176,7 @@ function linkStatusResultOk(result, check) {
|
|
|
2868
3176
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
2869
3177
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
2870
3178
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
3179
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
2871
3180
|
return true;
|
|
2872
3181
|
}
|
|
2873
3182
|
function summarizeHttpStatusEvidence(viewport, check) {
|
|
@@ -2888,6 +3197,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
2888
3197
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
2889
3198
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
2890
3199
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
3200
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
2891
3201
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
2892
3202
|
failures.push({
|
|
2893
3203
|
code: "http_status_failed",
|
|
@@ -2906,6 +3216,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
2906
3216
|
body_not_contains_found: bodyNotContainsFound,
|
|
2907
3217
|
body_not_patterns: Array.isArray(check.body_not_patterns) ? check.body_not_patterns : null,
|
|
2908
3218
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
3219
|
+
body_json_assertions: Array.isArray(check.body_json_assertions) ? check.body_json_assertions : null,
|
|
3220
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
2909
3221
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
2910
3222
|
});
|
|
2911
3223
|
}
|
|
@@ -2933,6 +3245,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
2933
3245
|
? statusEvidence.body_not_patterns
|
|
2934
3246
|
: null,
|
|
2935
3247
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
3248
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? statusEvidence.body_json_assertions : null,
|
|
3249
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
2936
3250
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
2937
3251
|
failures,
|
|
2938
3252
|
};
|
|
@@ -5035,6 +5349,187 @@ function linkProbeResponseFields(response, method) {
|
|
|
5035
5349
|
content_length: contentLength,
|
|
5036
5350
|
};
|
|
5037
5351
|
}
|
|
5352
|
+
function jsonProbeValueType(value) {
|
|
5353
|
+
if (value === null) return "null";
|
|
5354
|
+
if (Array.isArray(value)) return "array";
|
|
5355
|
+
if (typeof value === "boolean") return "boolean";
|
|
5356
|
+
if (typeof value === "number") return "number";
|
|
5357
|
+
if (typeof value === "string") return "string";
|
|
5358
|
+
return "object";
|
|
5359
|
+
}
|
|
5360
|
+
function compactJsonProbeSample(value, depth) {
|
|
5361
|
+
const level = typeof depth === "number" ? depth : 0;
|
|
5362
|
+
if (typeof value === "string") return value.length > 240 ? value.slice(0, 237) + "..." : value;
|
|
5363
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
5364
|
+
if (Array.isArray(value)) {
|
|
5365
|
+
if (level >= 2) return "[array:" + value.length + "]";
|
|
5366
|
+
return value.slice(0, 3).map((item) => compactJsonProbeSample(item, level + 1));
|
|
5367
|
+
}
|
|
5368
|
+
if (value && typeof value === "object") {
|
|
5369
|
+
const entries = Object.entries(value);
|
|
5370
|
+
if (level >= 2) return "[object:" + entries.length + " keys]";
|
|
5371
|
+
return Object.fromEntries(entries.slice(0, 8).map(([key, child]) => [key, compactJsonProbeSample(child, level + 1)]));
|
|
5372
|
+
}
|
|
5373
|
+
return String(value);
|
|
5374
|
+
}
|
|
5375
|
+
function attachJsonProbeObservedValue(result, value) {
|
|
5376
|
+
const type = jsonProbeValueType(value);
|
|
5377
|
+
if (type === "array" && Array.isArray(value)) {
|
|
5378
|
+
result.observed_length = value.length;
|
|
5379
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
5380
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5383
|
+
if (type === "object" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
5384
|
+
const keyCount = Object.keys(value).length;
|
|
5385
|
+
result.observed_key_count = keyCount;
|
|
5386
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
5387
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
5388
|
+
return;
|
|
5389
|
+
}
|
|
5390
|
+
result.observed = value;
|
|
5391
|
+
}
|
|
5392
|
+
function jsonProbeDeepEqual(left, right) {
|
|
5393
|
+
if (left === right) return true;
|
|
5394
|
+
if (typeof left !== typeof right) return false;
|
|
5395
|
+
if (left === null || right === null) return left === right;
|
|
5396
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
5397
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
5398
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
5399
|
+
return left.every((item, index) => jsonProbeDeepEqual(item, right[index]));
|
|
5400
|
+
}
|
|
5401
|
+
const leftKeys = Object.keys(left).sort();
|
|
5402
|
+
const rightKeys = Object.keys(right).sort();
|
|
5403
|
+
if (!jsonProbeDeepEqual(leftKeys, rightKeys)) return false;
|
|
5404
|
+
return leftKeys.every((key) => jsonProbeDeepEqual(left[key], right[key]));
|
|
5405
|
+
}
|
|
5406
|
+
function jsonProbeContains(observed, expected) {
|
|
5407
|
+
if (typeof observed === "string" && typeof expected === "string") return observed.includes(expected);
|
|
5408
|
+
if (Array.isArray(observed)) return observed.some((item) => jsonProbeDeepEqual(item, expected));
|
|
5409
|
+
if (observed && expected && typeof observed === "object" && typeof expected === "object" && !Array.isArray(observed) && !Array.isArray(expected)) {
|
|
5410
|
+
return Object.entries(expected).every(([key, value]) => Object.hasOwn(observed, key) && jsonProbeDeepEqual(observed[key], value));
|
|
5411
|
+
}
|
|
5412
|
+
return false;
|
|
5413
|
+
}
|
|
5414
|
+
function parseJsonProbePathSegments(path) {
|
|
5415
|
+
let input = String(path || "").trim();
|
|
5416
|
+
if (!input) throw new Error("path is empty");
|
|
5417
|
+
if (input === "$") return [];
|
|
5418
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
5419
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
5420
|
+
const segments = [];
|
|
5421
|
+
let token = "";
|
|
5422
|
+
const pushToken = () => {
|
|
5423
|
+
if (!token) return;
|
|
5424
|
+
segments.push(token);
|
|
5425
|
+
token = "";
|
|
5426
|
+
};
|
|
5427
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
5428
|
+
const char = input[index];
|
|
5429
|
+
if (char === ".") {
|
|
5430
|
+
pushToken();
|
|
5431
|
+
continue;
|
|
5432
|
+
}
|
|
5433
|
+
if (char !== "[") {
|
|
5434
|
+
token += char;
|
|
5435
|
+
continue;
|
|
5436
|
+
}
|
|
5437
|
+
pushToken();
|
|
5438
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
5439
|
+
if (closeIndex === -1) throw new Error("unterminated bracket at " + index);
|
|
5440
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
5441
|
+
if (!bracket) throw new Error("empty bracket at " + index);
|
|
5442
|
+
if (/^\d+$/.test(bracket)) {
|
|
5443
|
+
segments.push(Number(bracket));
|
|
5444
|
+
} else if ((bracket.startsWith('"') && bracket.endsWith('"')) || (bracket.startsWith("'") && bracket.endsWith("'"))) {
|
|
5445
|
+
const quoted = bracket.startsWith("'")
|
|
5446
|
+
? '"' + bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"'
|
|
5447
|
+
: bracket;
|
|
5448
|
+
segments.push(String(JSON.parse(quoted)));
|
|
5449
|
+
} else {
|
|
5450
|
+
segments.push(bracket);
|
|
5451
|
+
}
|
|
5452
|
+
index = closeIndex;
|
|
5453
|
+
}
|
|
5454
|
+
pushToken();
|
|
5455
|
+
return segments;
|
|
5456
|
+
}
|
|
5457
|
+
function resolveJsonProbePath(root, path) {
|
|
5458
|
+
let segments;
|
|
5459
|
+
try {
|
|
5460
|
+
segments = parseJsonProbePathSegments(path);
|
|
5461
|
+
} catch (error) {
|
|
5462
|
+
return { exists: false, error: String(error && error.message ? error.message : error) };
|
|
5463
|
+
}
|
|
5464
|
+
let current = root;
|
|
5465
|
+
for (const segment of segments) {
|
|
5466
|
+
if (typeof segment === "number") {
|
|
5467
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
5468
|
+
current = current[segment];
|
|
5469
|
+
continue;
|
|
5470
|
+
}
|
|
5471
|
+
if (!current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment)) {
|
|
5472
|
+
return { exists: false };
|
|
5473
|
+
}
|
|
5474
|
+
current = current[segment];
|
|
5475
|
+
}
|
|
5476
|
+
return { exists: true, value: current };
|
|
5477
|
+
}
|
|
5478
|
+
function evaluateJsonProbeAssertion(root, assertion) {
|
|
5479
|
+
const resolved = resolveJsonProbePath(root, assertion.path);
|
|
5480
|
+
const errors = [];
|
|
5481
|
+
const result = {
|
|
5482
|
+
label: assertion.label || assertion.path,
|
|
5483
|
+
path: assertion.path,
|
|
5484
|
+
ok: true,
|
|
5485
|
+
exists: resolved.exists,
|
|
5486
|
+
observed_type: resolved.exists ? jsonProbeValueType(resolved.value) : "missing",
|
|
5487
|
+
};
|
|
5488
|
+
if (resolved.exists) attachJsonProbeObservedValue(result, resolved.value);
|
|
5489
|
+
if (resolved.error) errors.push(resolved.error);
|
|
5490
|
+
if (Object.hasOwn(assertion, "exists")) {
|
|
5491
|
+
result.expected_exists = assertion.exists;
|
|
5492
|
+
if (resolved.exists !== assertion.exists) errors.push("expected exists=" + assertion.exists);
|
|
5493
|
+
}
|
|
5494
|
+
if (Object.hasOwn(assertion, "type")) {
|
|
5495
|
+
result.type = assertion.type;
|
|
5496
|
+
if (!resolved.exists || jsonProbeValueType(resolved.value) !== assertion.type) errors.push("expected type " + assertion.type);
|
|
5497
|
+
}
|
|
5498
|
+
if (Object.hasOwn(assertion, "equals")) {
|
|
5499
|
+
result.equals = assertion.equals;
|
|
5500
|
+
if (!resolved.exists || !jsonProbeDeepEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
5501
|
+
}
|
|
5502
|
+
if (Object.hasOwn(assertion, "not_equals")) {
|
|
5503
|
+
result.not_equals = assertion.not_equals;
|
|
5504
|
+
if (resolved.exists && jsonProbeDeepEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
5505
|
+
}
|
|
5506
|
+
if (Object.hasOwn(assertion, "contains")) {
|
|
5507
|
+
result.contains = assertion.contains;
|
|
5508
|
+
if (!resolved.exists || !jsonProbeContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
5509
|
+
}
|
|
5510
|
+
result.ok = errors.length === 0;
|
|
5511
|
+
if (errors.length) result.errors = errors;
|
|
5512
|
+
return result;
|
|
5513
|
+
}
|
|
5514
|
+
function evaluateJsonProbeAssertions(text, assertions) {
|
|
5515
|
+
const expected = Array.isArray(assertions) ? assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
5516
|
+
if (!expected.length) return [];
|
|
5517
|
+
let parsed;
|
|
5518
|
+
try {
|
|
5519
|
+
parsed = JSON.parse(text);
|
|
5520
|
+
} catch (error) {
|
|
5521
|
+
const message = "response body is not valid JSON: " + String(error && error.message ? error.message : error).slice(0, 200);
|
|
5522
|
+
return expected.map((assertion) => ({
|
|
5523
|
+
label: assertion.label || assertion.path,
|
|
5524
|
+
path: assertion.path,
|
|
5525
|
+
ok: false,
|
|
5526
|
+
exists: false,
|
|
5527
|
+
observed_type: "missing",
|
|
5528
|
+
errors: [message],
|
|
5529
|
+
}));
|
|
5530
|
+
}
|
|
5531
|
+
return expected.map((assertion) => evaluateJsonProbeAssertion(parsed, assertion));
|
|
5532
|
+
}
|
|
5038
5533
|
async function collectHttpStatus(check) {
|
|
5039
5534
|
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
5040
5535
|
const method = httpStatusMethod(check);
|
|
@@ -5051,6 +5546,7 @@ async function collectHttpStatus(check) {
|
|
|
5051
5546
|
const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
5052
5547
|
const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
|
|
5053
5548
|
const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
|
|
5549
|
+
const bodyJsonAssertions = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
5054
5550
|
const options = {
|
|
5055
5551
|
method,
|
|
5056
5552
|
redirect: "follow",
|
|
@@ -5076,17 +5572,18 @@ async function collectHttpStatus(check) {
|
|
|
5076
5572
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
5077
5573
|
result.url = url;
|
|
5078
5574
|
result.status_text = response.statusText || "";
|
|
5079
|
-
const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0 || bodyNotContains.length > 0 || bodyNotPatterns.length > 0;
|
|
5575
|
+
const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0 || bodyNotContains.length > 0 || bodyNotPatterns.length > 0 || bodyJsonAssertions.length > 0;
|
|
5080
5576
|
if (shouldReadBody) {
|
|
5081
5577
|
try {
|
|
5082
5578
|
const buffer = await response.arrayBuffer();
|
|
5083
5579
|
result.bytes = buffer.byteLength;
|
|
5084
|
-
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
|
|
5580
|
+
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length || bodyJsonAssertions.length) {
|
|
5085
5581
|
const text = new TextDecoder().decode(buffer);
|
|
5086
5582
|
result.body_sample = text.slice(0, 1000);
|
|
5087
5583
|
if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
|
|
5088
5584
|
if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
|
|
5089
5585
|
if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
|
|
5586
|
+
if (bodyJsonAssertions.length) result.body_json_assertions = evaluateJsonProbeAssertions(text, bodyJsonAssertions);
|
|
5090
5587
|
}
|
|
5091
5588
|
} catch (error) {
|
|
5092
5589
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -5099,6 +5596,7 @@ async function collectHttpStatus(check) {
|
|
|
5099
5596
|
&& (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
|
|
5100
5597
|
&& (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
|
|
5101
5598
|
&& (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
|
|
5599
|
+
&& (!bodyJsonAssertions.length || (Array.isArray(result.body_json_assertions) && result.body_json_assertions.every((assertion) => assertion.ok === true)))
|
|
5102
5600
|
&& !result.error;
|
|
5103
5601
|
return result;
|
|
5104
5602
|
} catch (error) {
|