@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
|
@@ -139,6 +139,9 @@ function valueFromOwn(input, ...keys) {
|
|
|
139
139
|
function numberValue(value) {
|
|
140
140
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
141
141
|
}
|
|
142
|
+
function booleanValue(value) {
|
|
143
|
+
return typeof value === "boolean" ? value : void 0;
|
|
144
|
+
}
|
|
142
145
|
function horizontalBoundsOverflowPx(value) {
|
|
143
146
|
if (!isRecord(value)) return 0;
|
|
144
147
|
let max = maxPositiveNumber(
|
|
@@ -233,6 +236,187 @@ function toJsonValue(value) {
|
|
|
233
236
|
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
234
237
|
return String(value);
|
|
235
238
|
}
|
|
239
|
+
function jsonValueType(value) {
|
|
240
|
+
if (value === null) return "null";
|
|
241
|
+
if (Array.isArray(value)) return "array";
|
|
242
|
+
if (typeof value === "boolean") return "boolean";
|
|
243
|
+
if (typeof value === "number") return "number";
|
|
244
|
+
if (typeof value === "string") return "string";
|
|
245
|
+
return "object";
|
|
246
|
+
}
|
|
247
|
+
function compactJsonAssertionSample(value, depth = 0) {
|
|
248
|
+
if (typeof value === "string") return value.length > 240 ? `${value.slice(0, 237)}...` : value;
|
|
249
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return toJsonValue(value);
|
|
250
|
+
if (Array.isArray(value)) {
|
|
251
|
+
if (depth >= 2) return `[array:${value.length}]`;
|
|
252
|
+
return value.slice(0, 3).map((item) => compactJsonAssertionSample(item, depth + 1));
|
|
253
|
+
}
|
|
254
|
+
if (isRecord(value)) {
|
|
255
|
+
const entries = Object.entries(value).slice(0, 8);
|
|
256
|
+
if (depth >= 2) return `[object:${Object.keys(value).length} keys]`;
|
|
257
|
+
return Object.fromEntries(entries.map(([key, child]) => [key, compactJsonAssertionSample(child, depth + 1)]));
|
|
258
|
+
}
|
|
259
|
+
return String(value);
|
|
260
|
+
}
|
|
261
|
+
function attachJsonAssertionObservedValue(result, value) {
|
|
262
|
+
const type = jsonValueType(value);
|
|
263
|
+
if (type === "array" && Array.isArray(value)) {
|
|
264
|
+
result.observed_length = value.length;
|
|
265
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
266
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (type === "object" && isRecord(value)) {
|
|
270
|
+
const keyCount = Object.keys(value).length;
|
|
271
|
+
result.observed_key_count = keyCount;
|
|
272
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
273
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
result.observed = toJsonValue(value);
|
|
277
|
+
}
|
|
278
|
+
function deepJsonEqual(left, right) {
|
|
279
|
+
if (left === right) return true;
|
|
280
|
+
if (typeof left !== typeof right) return false;
|
|
281
|
+
if (left === null || right === null) return left === right;
|
|
282
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
283
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
284
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
285
|
+
return left.every((item, index) => deepJsonEqual(item, right[index]));
|
|
286
|
+
}
|
|
287
|
+
if (!isRecord(left) || !isRecord(right)) return false;
|
|
288
|
+
const leftKeys = Object.keys(left).sort();
|
|
289
|
+
const rightKeys = Object.keys(right).sort();
|
|
290
|
+
if (!deepJsonEqual(leftKeys, rightKeys)) return false;
|
|
291
|
+
return leftKeys.every((key) => deepJsonEqual(left[key], right[key]));
|
|
292
|
+
}
|
|
293
|
+
function jsonContains(observed, expected) {
|
|
294
|
+
if (typeof observed === "string" && typeof expected === "string") {
|
|
295
|
+
return observed.includes(expected);
|
|
296
|
+
}
|
|
297
|
+
if (Array.isArray(observed)) {
|
|
298
|
+
return observed.some((item) => deepJsonEqual(item, expected));
|
|
299
|
+
}
|
|
300
|
+
if (isRecord(observed) && isRecord(expected)) {
|
|
301
|
+
return Object.entries(expected).every(([key, value]) => hasOwn(observed, key) && deepJsonEqual(observed[key], value));
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
function parseJsonPathSegments(path) {
|
|
306
|
+
let input = path.trim();
|
|
307
|
+
if (!input) throw new Error("path is empty");
|
|
308
|
+
if (input === "$") return [];
|
|
309
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
310
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
311
|
+
const segments = [];
|
|
312
|
+
let token = "";
|
|
313
|
+
const pushToken = () => {
|
|
314
|
+
if (!token) return;
|
|
315
|
+
segments.push(token);
|
|
316
|
+
token = "";
|
|
317
|
+
};
|
|
318
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
319
|
+
const char = input[index];
|
|
320
|
+
if (char === ".") {
|
|
321
|
+
pushToken();
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (char !== "[") {
|
|
325
|
+
token += char;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
pushToken();
|
|
329
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
330
|
+
if (closeIndex === -1) throw new Error(`unterminated bracket at ${index}`);
|
|
331
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
332
|
+
if (!bracket) throw new Error(`empty bracket at ${index}`);
|
|
333
|
+
if (/^\d+$/.test(bracket)) {
|
|
334
|
+
segments.push(Number(bracket));
|
|
335
|
+
} else if (bracket.startsWith('"') && bracket.endsWith('"') || bracket.startsWith("'") && bracket.endsWith("'")) {
|
|
336
|
+
const quoted = bracket.startsWith("'") ? `"${bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : bracket;
|
|
337
|
+
segments.push(String(JSON.parse(quoted)));
|
|
338
|
+
} else {
|
|
339
|
+
segments.push(bracket);
|
|
340
|
+
}
|
|
341
|
+
index = closeIndex;
|
|
342
|
+
}
|
|
343
|
+
pushToken();
|
|
344
|
+
return segments;
|
|
345
|
+
}
|
|
346
|
+
function resolveJsonPath(root, path) {
|
|
347
|
+
let segments;
|
|
348
|
+
try {
|
|
349
|
+
segments = parseJsonPathSegments(path);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
return { exists: false, error: String(error instanceof Error ? error.message : error) };
|
|
352
|
+
}
|
|
353
|
+
let current = root;
|
|
354
|
+
for (const segment of segments) {
|
|
355
|
+
if (typeof segment === "number") {
|
|
356
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
357
|
+
current = current[segment];
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (!isRecord(current) || !hasOwn(current, segment)) return { exists: false };
|
|
361
|
+
current = current[segment];
|
|
362
|
+
}
|
|
363
|
+
return { exists: true, value: current };
|
|
364
|
+
}
|
|
365
|
+
function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
|
|
366
|
+
const resolved = resolveJsonPath(root, assertion.path);
|
|
367
|
+
const errors = [];
|
|
368
|
+
const result = {
|
|
369
|
+
label: assertion.label || assertion.path,
|
|
370
|
+
path: assertion.path,
|
|
371
|
+
ok: true,
|
|
372
|
+
exists: resolved.exists,
|
|
373
|
+
observed_type: resolved.exists ? jsonValueType(resolved.value) : "missing"
|
|
374
|
+
};
|
|
375
|
+
if (resolved.exists) attachJsonAssertionObservedValue(result, resolved.value);
|
|
376
|
+
if (resolved.error) errors.push(resolved.error);
|
|
377
|
+
if (hasOwn(assertion, "exists")) {
|
|
378
|
+
result.expected_exists = assertion.exists;
|
|
379
|
+
if (resolved.exists !== assertion.exists) errors.push(`expected exists=${assertion.exists}`);
|
|
380
|
+
}
|
|
381
|
+
if (hasOwn(assertion, "type")) {
|
|
382
|
+
result.type = assertion.type;
|
|
383
|
+
if (!resolved.exists || jsonValueType(resolved.value) !== assertion.type) errors.push(`expected type ${assertion.type}`);
|
|
384
|
+
}
|
|
385
|
+
if (hasOwn(assertion, "equals")) {
|
|
386
|
+
result.equals = assertion.equals;
|
|
387
|
+
if (!resolved.exists || !deepJsonEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
388
|
+
}
|
|
389
|
+
if (hasOwn(assertion, "not_equals")) {
|
|
390
|
+
result.not_equals = assertion.not_equals;
|
|
391
|
+
if (resolved.exists && deepJsonEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
392
|
+
}
|
|
393
|
+
if (hasOwn(assertion, "contains")) {
|
|
394
|
+
result.contains = assertion.contains;
|
|
395
|
+
if (!resolved.exists || !jsonContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
396
|
+
}
|
|
397
|
+
result.ok = errors.length === 0;
|
|
398
|
+
if (errors.length) result.errors = errors;
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
function evaluateHttpStatusBodyJsonAssertions(bodyText, assertions) {
|
|
402
|
+
const expected = assertions?.filter((assertion) => assertion.path) ?? [];
|
|
403
|
+
if (!expected.length) return [];
|
|
404
|
+
let parsed;
|
|
405
|
+
try {
|
|
406
|
+
parsed = JSON.parse(bodyText);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
const message = `response body is not valid JSON: ${String(error instanceof Error ? error.message : error).slice(0, 200)}`;
|
|
409
|
+
return expected.map((assertion) => ({
|
|
410
|
+
label: assertion.label || assertion.path,
|
|
411
|
+
path: assertion.path,
|
|
412
|
+
ok: false,
|
|
413
|
+
exists: false,
|
|
414
|
+
observed_type: "missing",
|
|
415
|
+
errors: [message]
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
418
|
+
return expected.map((assertion) => evaluateHttpStatusBodyJsonAssertion(parsed, assertion));
|
|
419
|
+
}
|
|
236
420
|
function compactProfileSetupSummaryText(value, limit = 160) {
|
|
237
421
|
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
238
422
|
if (!text) return void 0;
|
|
@@ -887,6 +1071,46 @@ function validateRegexPatterns(patterns, label) {
|
|
|
887
1071
|
}
|
|
888
1072
|
}
|
|
889
1073
|
}
|
|
1074
|
+
function normalizeHttpStatusBodyJsonAssertions(value, label) {
|
|
1075
|
+
if (value === void 0) return void 0;
|
|
1076
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
1077
|
+
if (!value.length) throw new Error(`${label} must not be empty.`);
|
|
1078
|
+
return value.map((item, index) => {
|
|
1079
|
+
const itemLabel = `${label}[${index}]`;
|
|
1080
|
+
if (typeof item === "string") {
|
|
1081
|
+
const path2 = stringValue(item);
|
|
1082
|
+
if (!path2) throw new Error(`${itemLabel} path must not be empty.`);
|
|
1083
|
+
return { path: path2, exists: true };
|
|
1084
|
+
}
|
|
1085
|
+
if (!isRecord(item)) throw new Error(`${itemLabel} must be an object or JSON path string.`);
|
|
1086
|
+
const path = stringFromOwn(item, "path", "json_path", "jsonPath", "key");
|
|
1087
|
+
if (!path) throw new Error(`${itemLabel}.path is required.`);
|
|
1088
|
+
const assertion = {
|
|
1089
|
+
label: stringValue(item.label),
|
|
1090
|
+
path
|
|
1091
|
+
};
|
|
1092
|
+
const exists = booleanValue(valueFromOwn(item, "exists", "present"));
|
|
1093
|
+
if (exists !== void 0) assertion.exists = exists;
|
|
1094
|
+
const type = stringValue(valueFromOwn(item, "type", "value_type", "valueType"));
|
|
1095
|
+
if (type !== void 0) {
|
|
1096
|
+
const allowedTypes = ["array", "boolean", "null", "number", "object", "string"];
|
|
1097
|
+
if (!allowedTypes.includes(type)) {
|
|
1098
|
+
throw new Error(`${itemLabel}.type must be one of ${allowedTypes.join(", ")}.`);
|
|
1099
|
+
}
|
|
1100
|
+
assertion.type = type;
|
|
1101
|
+
}
|
|
1102
|
+
const equalsValue = valueFromOwn(item, "equals", "expected", "expected_value", "expectedValue", "value");
|
|
1103
|
+
if (equalsValue !== void 0) assertion.equals = toJsonValue(equalsValue);
|
|
1104
|
+
const notEqualsValue = valueFromOwn(item, "not_equals", "notEquals", "forbidden", "forbidden_value", "forbiddenValue");
|
|
1105
|
+
if (notEqualsValue !== void 0) assertion.not_equals = toJsonValue(notEqualsValue);
|
|
1106
|
+
const containsValue = valueFromOwn(item, "contains", "includes", "contains_value", "containsValue", "include");
|
|
1107
|
+
if (containsValue !== void 0) assertion.contains = toJsonValue(containsValue);
|
|
1108
|
+
if (assertion.exists === void 0 && assertion.type === void 0 && !hasOwn(assertion, "equals") && !hasOwn(assertion, "not_equals") && !hasOwn(assertion, "contains")) {
|
|
1109
|
+
assertion.exists = true;
|
|
1110
|
+
}
|
|
1111
|
+
return assertion;
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
890
1114
|
function isDialogCountCheckType(type) {
|
|
891
1115
|
return type === "dialog_count_equals" || type === "dialog_accept_count_equals" || type === "dialog_dismiss_count_equals";
|
|
892
1116
|
}
|
|
@@ -986,6 +1210,10 @@ function normalizeCheck(input, index) {
|
|
|
986
1210
|
`checks[${index}] body_not_patterns`
|
|
987
1211
|
) : void 0;
|
|
988
1212
|
if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
|
|
1213
|
+
const bodyJsonAssertions = isHttpStatusCheck ? normalizeHttpStatusBodyJsonAssertions(
|
|
1214
|
+
input.body_json_assertions ?? input.bodyJsonAssertions ?? input.json_body_assertions ?? input.jsonBodyAssertions ?? input.json_assertions ?? input.jsonAssertions ?? input.response_json_assertions ?? input.responseJsonAssertions,
|
|
1215
|
+
`checks[${index}] body_json_assertions`
|
|
1216
|
+
) : void 0;
|
|
989
1217
|
if (isLinkStatusCheck) {
|
|
990
1218
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
991
1219
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -1011,6 +1239,7 @@ function normalizeCheck(input, index) {
|
|
|
1011
1239
|
body_contains: bodyContains,
|
|
1012
1240
|
body_not_contains: bodyNotContains,
|
|
1013
1241
|
body_not_patterns: bodyNotPatterns,
|
|
1242
|
+
body_json_assertions: bodyJsonAssertions,
|
|
1014
1243
|
expected_texts: expectedTexts,
|
|
1015
1244
|
link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
|
|
1016
1245
|
source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
|
|
@@ -1217,6 +1446,38 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
1217
1446
|
const observed = isRecord(result.body_not_patterns) ? result.body_not_patterns : {};
|
|
1218
1447
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
1219
1448
|
}
|
|
1449
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
1450
|
+
const expected = check.body_json_assertions?.filter((assertion) => assertion.path) ?? [];
|
|
1451
|
+
if (!expected.length) return [];
|
|
1452
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
1453
|
+
return expected.map((assertion) => ({
|
|
1454
|
+
label: assertion.label || assertion.path,
|
|
1455
|
+
path: assertion.path,
|
|
1456
|
+
ok: false,
|
|
1457
|
+
exists: false,
|
|
1458
|
+
observed_type: "missing",
|
|
1459
|
+
errors: ["body_json_assertions evidence missing"]
|
|
1460
|
+
}));
|
|
1461
|
+
}
|
|
1462
|
+
return result.body_json_assertions.filter((assertion) => isRecord(assertion) && assertion.ok !== true).map((assertion) => ({
|
|
1463
|
+
label: stringValue(assertion.label) || stringValue(assertion.path) || "json assertion",
|
|
1464
|
+
path: stringValue(assertion.path) || "",
|
|
1465
|
+
ok: false,
|
|
1466
|
+
exists: assertion.exists === true,
|
|
1467
|
+
observed: hasOwn(assertion, "observed") ? toJsonValue(assertion.observed) : void 0,
|
|
1468
|
+
observed_sample: hasOwn(assertion, "observed_sample") ? toJsonValue(assertion.observed_sample) : void 0,
|
|
1469
|
+
observed_length: numberValue(assertion.observed_length),
|
|
1470
|
+
observed_key_count: numberValue(assertion.observed_key_count),
|
|
1471
|
+
observed_omitted_count: numberValue(assertion.observed_omitted_count),
|
|
1472
|
+
observed_type: stringValue(assertion.observed_type) || "missing",
|
|
1473
|
+
expected_exists: booleanValue(assertion.expected_exists),
|
|
1474
|
+
equals: hasOwn(assertion, "equals") ? toJsonValue(assertion.equals) : void 0,
|
|
1475
|
+
not_equals: hasOwn(assertion, "not_equals") ? toJsonValue(assertion.not_equals) : void 0,
|
|
1476
|
+
contains: hasOwn(assertion, "contains") ? toJsonValue(assertion.contains) : void 0,
|
|
1477
|
+
type: stringValue(assertion.type),
|
|
1478
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : void 0
|
|
1479
|
+
}));
|
|
1480
|
+
}
|
|
1220
1481
|
function linkStatusResultOk(result, check) {
|
|
1221
1482
|
const status = numberValue(result.status);
|
|
1222
1483
|
if (!httpStatusIsAllowed(status, check)) return false;
|
|
@@ -1234,6 +1495,7 @@ function linkStatusResultOk(result, check) {
|
|
|
1234
1495
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
1235
1496
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
1236
1497
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
1498
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
1237
1499
|
return true;
|
|
1238
1500
|
}
|
|
1239
1501
|
function responseHeader(response, name) {
|
|
@@ -1302,7 +1564,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1302
1564
|
statusText = typeof response.statusText === "string" ? response.statusText : "";
|
|
1303
1565
|
result.content_type = responseHeader(response, "content-type");
|
|
1304
1566
|
result.content_length = responseContentLength(response);
|
|
1305
|
-
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);
|
|
1567
|
+
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);
|
|
1306
1568
|
if (shouldReadBody && method !== "HEAD") {
|
|
1307
1569
|
const body = await responseBodyText(response);
|
|
1308
1570
|
result.bytes = body.bytes;
|
|
@@ -1315,6 +1577,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1315
1577
|
if (check.body_not_patterns?.length) {
|
|
1316
1578
|
result.body_not_patterns = Object.fromEntries(check.body_not_patterns.filter(Boolean).map((pattern) => [pattern, new RegExp(pattern).test(body.text)]));
|
|
1317
1579
|
}
|
|
1580
|
+
if (check.body_json_assertions?.length) {
|
|
1581
|
+
result.body_json_assertions = evaluateHttpStatusBodyJsonAssertions(body.text, check.body_json_assertions);
|
|
1582
|
+
}
|
|
1318
1583
|
}
|
|
1319
1584
|
} catch (caught) {
|
|
1320
1585
|
error = String(caught instanceof Error ? caught.message : caught).slice(0, 500);
|
|
@@ -1323,6 +1588,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1323
1588
|
const bodyContainsMissing = httpStatusBodyContainsFailures(result, check);
|
|
1324
1589
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(result, check);
|
|
1325
1590
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(result, check);
|
|
1591
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(result, check);
|
|
1326
1592
|
const ok = !error && linkStatusResultOk(result, check);
|
|
1327
1593
|
return {
|
|
1328
1594
|
index,
|
|
@@ -1341,7 +1607,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
1341
1607
|
body_not_contains: isRecord(result.body_not_contains) ? Object.fromEntries(Object.entries(result.body_not_contains).map(([key, value]) => [key, value === true])) : null,
|
|
1342
1608
|
body_not_contains_found: bodyNotContainsFound,
|
|
1343
1609
|
body_not_patterns: isRecord(result.body_not_patterns) ? Object.fromEntries(Object.entries(result.body_not_patterns).map(([key, value]) => [key, value === true])) : null,
|
|
1344
|
-
body_not_patterns_found: bodyNotPatternsFound
|
|
1610
|
+
body_not_patterns_found: bodyNotPatternsFound,
|
|
1611
|
+
body_json_assertions: Array.isArray(result.body_json_assertions) ? result.body_json_assertions : null,
|
|
1612
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed
|
|
1345
1613
|
};
|
|
1346
1614
|
}
|
|
1347
1615
|
async function preflightRiddleProofProfileHttpStatusChecks(profile, options = {}) {
|
|
@@ -1386,6 +1654,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
1386
1654
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
1387
1655
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
1388
1656
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
1657
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
1389
1658
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
1390
1659
|
failures.push({
|
|
1391
1660
|
code: "http_status_failed",
|
|
@@ -1404,6 +1673,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
1404
1673
|
body_not_contains_found: bodyNotContainsFound,
|
|
1405
1674
|
body_not_patterns: check.body_not_patterns ?? null,
|
|
1406
1675
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
1676
|
+
body_json_assertions: check.body_json_assertions ?? null,
|
|
1677
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
1407
1678
|
body_sample: stringValue(statusEvidence.body_sample) ?? null
|
|
1408
1679
|
});
|
|
1409
1680
|
}
|
|
@@ -1425,6 +1696,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
1425
1696
|
body_not_contains_found: bodyNotContainsFound,
|
|
1426
1697
|
body_not_patterns: isRecord(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
|
|
1427
1698
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
1699
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? toJsonValue(statusEvidence.body_json_assertions) : null,
|
|
1700
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
1428
1701
|
body_sample: stringValue(statusEvidence.body_sample) ?? null,
|
|
1429
1702
|
failures
|
|
1430
1703
|
};
|
|
@@ -2061,6 +2334,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
2061
2334
|
body_contains: check.body_contains ?? [],
|
|
2062
2335
|
body_not_contains: check.body_not_contains ?? [],
|
|
2063
2336
|
body_not_patterns: check.body_not_patterns ?? [],
|
|
2337
|
+
body_json_assertions: toJsonValue(check.body_json_assertions ?? []),
|
|
2064
2338
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
2065
2339
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
2066
2340
|
},
|
|
@@ -2804,6 +3078,40 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
2804
3078
|
: {};
|
|
2805
3079
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
2806
3080
|
}
|
|
3081
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
3082
|
+
const expected = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
3083
|
+
if (!expected.length) return [];
|
|
3084
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
3085
|
+
return expected.map((assertion) => ({
|
|
3086
|
+
label: assertion.label || assertion.path,
|
|
3087
|
+
path: assertion.path,
|
|
3088
|
+
ok: false,
|
|
3089
|
+
exists: false,
|
|
3090
|
+
observed_type: "missing",
|
|
3091
|
+
errors: ["body_json_assertions evidence missing"],
|
|
3092
|
+
}));
|
|
3093
|
+
}
|
|
3094
|
+
return result.body_json_assertions
|
|
3095
|
+
.filter((assertion) => assertion && typeof assertion === "object" && assertion.ok !== true)
|
|
3096
|
+
.map((assertion) => ({
|
|
3097
|
+
label: typeof assertion.label === "string" && assertion.label ? assertion.label : typeof assertion.path === "string" && assertion.path ? assertion.path : "json assertion",
|
|
3098
|
+
path: typeof assertion.path === "string" ? assertion.path : "",
|
|
3099
|
+
ok: false,
|
|
3100
|
+
exists: assertion.exists === true,
|
|
3101
|
+
observed: Object.hasOwn(assertion, "observed") ? assertion.observed : undefined,
|
|
3102
|
+
observed_sample: Object.hasOwn(assertion, "observed_sample") ? assertion.observed_sample : undefined,
|
|
3103
|
+
observed_length: typeof assertion.observed_length === "number" && Number.isFinite(assertion.observed_length) ? assertion.observed_length : undefined,
|
|
3104
|
+
observed_key_count: typeof assertion.observed_key_count === "number" && Number.isFinite(assertion.observed_key_count) ? assertion.observed_key_count : undefined,
|
|
3105
|
+
observed_omitted_count: typeof assertion.observed_omitted_count === "number" && Number.isFinite(assertion.observed_omitted_count) ? assertion.observed_omitted_count : undefined,
|
|
3106
|
+
observed_type: typeof assertion.observed_type === "string" && assertion.observed_type ? assertion.observed_type : "missing",
|
|
3107
|
+
expected_exists: typeof assertion.expected_exists === "boolean" ? assertion.expected_exists : undefined,
|
|
3108
|
+
equals: Object.hasOwn(assertion, "equals") ? assertion.equals : undefined,
|
|
3109
|
+
not_equals: Object.hasOwn(assertion, "not_equals") ? assertion.not_equals : undefined,
|
|
3110
|
+
contains: Object.hasOwn(assertion, "contains") ? assertion.contains : undefined,
|
|
3111
|
+
type: typeof assertion.type === "string" ? assertion.type : undefined,
|
|
3112
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : undefined,
|
|
3113
|
+
}));
|
|
3114
|
+
}
|
|
2807
3115
|
function linkStatusResultOk(result, check) {
|
|
2808
3116
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2809
3117
|
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
@@ -2821,6 +3129,7 @@ function linkStatusResultOk(result, check) {
|
|
|
2821
3129
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
2822
3130
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
2823
3131
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
3132
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
2824
3133
|
return true;
|
|
2825
3134
|
}
|
|
2826
3135
|
function summarizeHttpStatusEvidence(viewport, check) {
|
|
@@ -2841,6 +3150,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
2841
3150
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
2842
3151
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
2843
3152
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
3153
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
2844
3154
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
2845
3155
|
failures.push({
|
|
2846
3156
|
code: "http_status_failed",
|
|
@@ -2859,6 +3169,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
2859
3169
|
body_not_contains_found: bodyNotContainsFound,
|
|
2860
3170
|
body_not_patterns: Array.isArray(check.body_not_patterns) ? check.body_not_patterns : null,
|
|
2861
3171
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
3172
|
+
body_json_assertions: Array.isArray(check.body_json_assertions) ? check.body_json_assertions : null,
|
|
3173
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
2862
3174
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
2863
3175
|
});
|
|
2864
3176
|
}
|
|
@@ -2886,6 +3198,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
2886
3198
|
? statusEvidence.body_not_patterns
|
|
2887
3199
|
: null,
|
|
2888
3200
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
3201
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? statusEvidence.body_json_assertions : null,
|
|
3202
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
2889
3203
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
2890
3204
|
failures,
|
|
2891
3205
|
};
|
|
@@ -4988,6 +5302,187 @@ function linkProbeResponseFields(response, method) {
|
|
|
4988
5302
|
content_length: contentLength,
|
|
4989
5303
|
};
|
|
4990
5304
|
}
|
|
5305
|
+
function jsonProbeValueType(value) {
|
|
5306
|
+
if (value === null) return "null";
|
|
5307
|
+
if (Array.isArray(value)) return "array";
|
|
5308
|
+
if (typeof value === "boolean") return "boolean";
|
|
5309
|
+
if (typeof value === "number") return "number";
|
|
5310
|
+
if (typeof value === "string") return "string";
|
|
5311
|
+
return "object";
|
|
5312
|
+
}
|
|
5313
|
+
function compactJsonProbeSample(value, depth) {
|
|
5314
|
+
const level = typeof depth === "number" ? depth : 0;
|
|
5315
|
+
if (typeof value === "string") return value.length > 240 ? value.slice(0, 237) + "..." : value;
|
|
5316
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
5317
|
+
if (Array.isArray(value)) {
|
|
5318
|
+
if (level >= 2) return "[array:" + value.length + "]";
|
|
5319
|
+
return value.slice(0, 3).map((item) => compactJsonProbeSample(item, level + 1));
|
|
5320
|
+
}
|
|
5321
|
+
if (value && typeof value === "object") {
|
|
5322
|
+
const entries = Object.entries(value);
|
|
5323
|
+
if (level >= 2) return "[object:" + entries.length + " keys]";
|
|
5324
|
+
return Object.fromEntries(entries.slice(0, 8).map(([key, child]) => [key, compactJsonProbeSample(child, level + 1)]));
|
|
5325
|
+
}
|
|
5326
|
+
return String(value);
|
|
5327
|
+
}
|
|
5328
|
+
function attachJsonProbeObservedValue(result, value) {
|
|
5329
|
+
const type = jsonProbeValueType(value);
|
|
5330
|
+
if (type === "array" && Array.isArray(value)) {
|
|
5331
|
+
result.observed_length = value.length;
|
|
5332
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
5333
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
5336
|
+
if (type === "object" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
5337
|
+
const keyCount = Object.keys(value).length;
|
|
5338
|
+
result.observed_key_count = keyCount;
|
|
5339
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
5340
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
5341
|
+
return;
|
|
5342
|
+
}
|
|
5343
|
+
result.observed = value;
|
|
5344
|
+
}
|
|
5345
|
+
function jsonProbeDeepEqual(left, right) {
|
|
5346
|
+
if (left === right) return true;
|
|
5347
|
+
if (typeof left !== typeof right) return false;
|
|
5348
|
+
if (left === null || right === null) return left === right;
|
|
5349
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
5350
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
5351
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
5352
|
+
return left.every((item, index) => jsonProbeDeepEqual(item, right[index]));
|
|
5353
|
+
}
|
|
5354
|
+
const leftKeys = Object.keys(left).sort();
|
|
5355
|
+
const rightKeys = Object.keys(right).sort();
|
|
5356
|
+
if (!jsonProbeDeepEqual(leftKeys, rightKeys)) return false;
|
|
5357
|
+
return leftKeys.every((key) => jsonProbeDeepEqual(left[key], right[key]));
|
|
5358
|
+
}
|
|
5359
|
+
function jsonProbeContains(observed, expected) {
|
|
5360
|
+
if (typeof observed === "string" && typeof expected === "string") return observed.includes(expected);
|
|
5361
|
+
if (Array.isArray(observed)) return observed.some((item) => jsonProbeDeepEqual(item, expected));
|
|
5362
|
+
if (observed && expected && typeof observed === "object" && typeof expected === "object" && !Array.isArray(observed) && !Array.isArray(expected)) {
|
|
5363
|
+
return Object.entries(expected).every(([key, value]) => Object.hasOwn(observed, key) && jsonProbeDeepEqual(observed[key], value));
|
|
5364
|
+
}
|
|
5365
|
+
return false;
|
|
5366
|
+
}
|
|
5367
|
+
function parseJsonProbePathSegments(path) {
|
|
5368
|
+
let input = String(path || "").trim();
|
|
5369
|
+
if (!input) throw new Error("path is empty");
|
|
5370
|
+
if (input === "$") return [];
|
|
5371
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
5372
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
5373
|
+
const segments = [];
|
|
5374
|
+
let token = "";
|
|
5375
|
+
const pushToken = () => {
|
|
5376
|
+
if (!token) return;
|
|
5377
|
+
segments.push(token);
|
|
5378
|
+
token = "";
|
|
5379
|
+
};
|
|
5380
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
5381
|
+
const char = input[index];
|
|
5382
|
+
if (char === ".") {
|
|
5383
|
+
pushToken();
|
|
5384
|
+
continue;
|
|
5385
|
+
}
|
|
5386
|
+
if (char !== "[") {
|
|
5387
|
+
token += char;
|
|
5388
|
+
continue;
|
|
5389
|
+
}
|
|
5390
|
+
pushToken();
|
|
5391
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
5392
|
+
if (closeIndex === -1) throw new Error("unterminated bracket at " + index);
|
|
5393
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
5394
|
+
if (!bracket) throw new Error("empty bracket at " + index);
|
|
5395
|
+
if (/^\d+$/.test(bracket)) {
|
|
5396
|
+
segments.push(Number(bracket));
|
|
5397
|
+
} else if ((bracket.startsWith('"') && bracket.endsWith('"')) || (bracket.startsWith("'") && bracket.endsWith("'"))) {
|
|
5398
|
+
const quoted = bracket.startsWith("'")
|
|
5399
|
+
? '"' + bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"'
|
|
5400
|
+
: bracket;
|
|
5401
|
+
segments.push(String(JSON.parse(quoted)));
|
|
5402
|
+
} else {
|
|
5403
|
+
segments.push(bracket);
|
|
5404
|
+
}
|
|
5405
|
+
index = closeIndex;
|
|
5406
|
+
}
|
|
5407
|
+
pushToken();
|
|
5408
|
+
return segments;
|
|
5409
|
+
}
|
|
5410
|
+
function resolveJsonProbePath(root, path) {
|
|
5411
|
+
let segments;
|
|
5412
|
+
try {
|
|
5413
|
+
segments = parseJsonProbePathSegments(path);
|
|
5414
|
+
} catch (error) {
|
|
5415
|
+
return { exists: false, error: String(error && error.message ? error.message : error) };
|
|
5416
|
+
}
|
|
5417
|
+
let current = root;
|
|
5418
|
+
for (const segment of segments) {
|
|
5419
|
+
if (typeof segment === "number") {
|
|
5420
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
5421
|
+
current = current[segment];
|
|
5422
|
+
continue;
|
|
5423
|
+
}
|
|
5424
|
+
if (!current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment)) {
|
|
5425
|
+
return { exists: false };
|
|
5426
|
+
}
|
|
5427
|
+
current = current[segment];
|
|
5428
|
+
}
|
|
5429
|
+
return { exists: true, value: current };
|
|
5430
|
+
}
|
|
5431
|
+
function evaluateJsonProbeAssertion(root, assertion) {
|
|
5432
|
+
const resolved = resolveJsonProbePath(root, assertion.path);
|
|
5433
|
+
const errors = [];
|
|
5434
|
+
const result = {
|
|
5435
|
+
label: assertion.label || assertion.path,
|
|
5436
|
+
path: assertion.path,
|
|
5437
|
+
ok: true,
|
|
5438
|
+
exists: resolved.exists,
|
|
5439
|
+
observed_type: resolved.exists ? jsonProbeValueType(resolved.value) : "missing",
|
|
5440
|
+
};
|
|
5441
|
+
if (resolved.exists) attachJsonProbeObservedValue(result, resolved.value);
|
|
5442
|
+
if (resolved.error) errors.push(resolved.error);
|
|
5443
|
+
if (Object.hasOwn(assertion, "exists")) {
|
|
5444
|
+
result.expected_exists = assertion.exists;
|
|
5445
|
+
if (resolved.exists !== assertion.exists) errors.push("expected exists=" + assertion.exists);
|
|
5446
|
+
}
|
|
5447
|
+
if (Object.hasOwn(assertion, "type")) {
|
|
5448
|
+
result.type = assertion.type;
|
|
5449
|
+
if (!resolved.exists || jsonProbeValueType(resolved.value) !== assertion.type) errors.push("expected type " + assertion.type);
|
|
5450
|
+
}
|
|
5451
|
+
if (Object.hasOwn(assertion, "equals")) {
|
|
5452
|
+
result.equals = assertion.equals;
|
|
5453
|
+
if (!resolved.exists || !jsonProbeDeepEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
5454
|
+
}
|
|
5455
|
+
if (Object.hasOwn(assertion, "not_equals")) {
|
|
5456
|
+
result.not_equals = assertion.not_equals;
|
|
5457
|
+
if (resolved.exists && jsonProbeDeepEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
5458
|
+
}
|
|
5459
|
+
if (Object.hasOwn(assertion, "contains")) {
|
|
5460
|
+
result.contains = assertion.contains;
|
|
5461
|
+
if (!resolved.exists || !jsonProbeContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
5462
|
+
}
|
|
5463
|
+
result.ok = errors.length === 0;
|
|
5464
|
+
if (errors.length) result.errors = errors;
|
|
5465
|
+
return result;
|
|
5466
|
+
}
|
|
5467
|
+
function evaluateJsonProbeAssertions(text, assertions) {
|
|
5468
|
+
const expected = Array.isArray(assertions) ? assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
5469
|
+
if (!expected.length) return [];
|
|
5470
|
+
let parsed;
|
|
5471
|
+
try {
|
|
5472
|
+
parsed = JSON.parse(text);
|
|
5473
|
+
} catch (error) {
|
|
5474
|
+
const message = "response body is not valid JSON: " + String(error && error.message ? error.message : error).slice(0, 200);
|
|
5475
|
+
return expected.map((assertion) => ({
|
|
5476
|
+
label: assertion.label || assertion.path,
|
|
5477
|
+
path: assertion.path,
|
|
5478
|
+
ok: false,
|
|
5479
|
+
exists: false,
|
|
5480
|
+
observed_type: "missing",
|
|
5481
|
+
errors: [message],
|
|
5482
|
+
}));
|
|
5483
|
+
}
|
|
5484
|
+
return expected.map((assertion) => evaluateJsonProbeAssertion(parsed, assertion));
|
|
5485
|
+
}
|
|
4991
5486
|
async function collectHttpStatus(check) {
|
|
4992
5487
|
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
4993
5488
|
const method = httpStatusMethod(check);
|
|
@@ -5004,6 +5499,7 @@ async function collectHttpStatus(check) {
|
|
|
5004
5499
|
const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
5005
5500
|
const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
|
|
5006
5501
|
const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
|
|
5502
|
+
const bodyJsonAssertions = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
5007
5503
|
const options = {
|
|
5008
5504
|
method,
|
|
5009
5505
|
redirect: "follow",
|
|
@@ -5029,17 +5525,18 @@ async function collectHttpStatus(check) {
|
|
|
5029
5525
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
5030
5526
|
result.url = url;
|
|
5031
5527
|
result.status_text = response.statusText || "";
|
|
5032
|
-
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;
|
|
5528
|
+
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;
|
|
5033
5529
|
if (shouldReadBody) {
|
|
5034
5530
|
try {
|
|
5035
5531
|
const buffer = await response.arrayBuffer();
|
|
5036
5532
|
result.bytes = buffer.byteLength;
|
|
5037
|
-
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
|
|
5533
|
+
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length || bodyJsonAssertions.length) {
|
|
5038
5534
|
const text = new TextDecoder().decode(buffer);
|
|
5039
5535
|
result.body_sample = text.slice(0, 1000);
|
|
5040
5536
|
if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
|
|
5041
5537
|
if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
|
|
5042
5538
|
if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
|
|
5539
|
+
if (bodyJsonAssertions.length) result.body_json_assertions = evaluateJsonProbeAssertions(text, bodyJsonAssertions);
|
|
5043
5540
|
}
|
|
5044
5541
|
} catch (error) {
|
|
5045
5542
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -5052,6 +5549,7 @@ async function collectHttpStatus(check) {
|
|
|
5052
5549
|
&& (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
|
|
5053
5550
|
&& (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
|
|
5054
5551
|
&& (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
|
|
5552
|
+
&& (!bodyJsonAssertions.length || (Array.isArray(result.body_json_assertions) && result.body_json_assertions.every((assertion) => assertion.ok === true)))
|
|
5055
5553
|
&& !result.error;
|
|
5056
5554
|
return result;
|
|
5057
5555
|
} catch (error) {
|