@riddledc/riddle-proof 0.7.125 → 0.7.127
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 +34 -2
- package/dist/{chunk-JVZWSI55.js → chunk-JLINSUKO.js} +460 -7
- package/dist/cli.cjs +478 -8
- package/dist/cli.js +19 -2
- package/dist/index.cjs +460 -7
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/profile.cjs +460 -7
- package/dist/profile.d.cts +29 -1
- package/dist/profile.d.ts +29 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8872,6 +8872,9 @@ function valueFromOwn(input, ...keys) {
|
|
|
8872
8872
|
function numberValue3(value) {
|
|
8873
8873
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8874
8874
|
}
|
|
8875
|
+
function booleanValue(value) {
|
|
8876
|
+
return typeof value === "boolean" ? value : void 0;
|
|
8877
|
+
}
|
|
8875
8878
|
function horizontalBoundsOverflowPx2(value) {
|
|
8876
8879
|
if (!isRecord2(value)) return 0;
|
|
8877
8880
|
let max = maxPositiveNumber2(
|
|
@@ -8966,6 +8969,156 @@ function toJsonValue(value) {
|
|
|
8966
8969
|
if (isRecord2(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
8967
8970
|
return String(value);
|
|
8968
8971
|
}
|
|
8972
|
+
function jsonValueType(value) {
|
|
8973
|
+
if (value === null) return "null";
|
|
8974
|
+
if (Array.isArray(value)) return "array";
|
|
8975
|
+
if (typeof value === "boolean") return "boolean";
|
|
8976
|
+
if (typeof value === "number") return "number";
|
|
8977
|
+
if (typeof value === "string") return "string";
|
|
8978
|
+
return "object";
|
|
8979
|
+
}
|
|
8980
|
+
function deepJsonEqual(left, right) {
|
|
8981
|
+
if (left === right) return true;
|
|
8982
|
+
if (typeof left !== typeof right) return false;
|
|
8983
|
+
if (left === null || right === null) return left === right;
|
|
8984
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
8985
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
8986
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
8987
|
+
return left.every((item, index) => deepJsonEqual(item, right[index]));
|
|
8988
|
+
}
|
|
8989
|
+
if (!isRecord2(left) || !isRecord2(right)) return false;
|
|
8990
|
+
const leftKeys = Object.keys(left).sort();
|
|
8991
|
+
const rightKeys = Object.keys(right).sort();
|
|
8992
|
+
if (!deepJsonEqual(leftKeys, rightKeys)) return false;
|
|
8993
|
+
return leftKeys.every((key) => deepJsonEqual(left[key], right[key]));
|
|
8994
|
+
}
|
|
8995
|
+
function jsonContains(observed, expected) {
|
|
8996
|
+
if (typeof observed === "string" && typeof expected === "string") {
|
|
8997
|
+
return observed.includes(expected);
|
|
8998
|
+
}
|
|
8999
|
+
if (Array.isArray(observed)) {
|
|
9000
|
+
return observed.some((item) => deepJsonEqual(item, expected));
|
|
9001
|
+
}
|
|
9002
|
+
if (isRecord2(observed) && isRecord2(expected)) {
|
|
9003
|
+
return Object.entries(expected).every(([key, value]) => hasOwn(observed, key) && deepJsonEqual(observed[key], value));
|
|
9004
|
+
}
|
|
9005
|
+
return false;
|
|
9006
|
+
}
|
|
9007
|
+
function parseJsonPathSegments(path6) {
|
|
9008
|
+
let input = path6.trim();
|
|
9009
|
+
if (!input) throw new Error("path is empty");
|
|
9010
|
+
if (input === "$") return [];
|
|
9011
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
9012
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
9013
|
+
const segments = [];
|
|
9014
|
+
let token = "";
|
|
9015
|
+
const pushToken = () => {
|
|
9016
|
+
if (!token) return;
|
|
9017
|
+
segments.push(token);
|
|
9018
|
+
token = "";
|
|
9019
|
+
};
|
|
9020
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
9021
|
+
const char = input[index];
|
|
9022
|
+
if (char === ".") {
|
|
9023
|
+
pushToken();
|
|
9024
|
+
continue;
|
|
9025
|
+
}
|
|
9026
|
+
if (char !== "[") {
|
|
9027
|
+
token += char;
|
|
9028
|
+
continue;
|
|
9029
|
+
}
|
|
9030
|
+
pushToken();
|
|
9031
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
9032
|
+
if (closeIndex === -1) throw new Error(`unterminated bracket at ${index}`);
|
|
9033
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
9034
|
+
if (!bracket) throw new Error(`empty bracket at ${index}`);
|
|
9035
|
+
if (/^\d+$/.test(bracket)) {
|
|
9036
|
+
segments.push(Number(bracket));
|
|
9037
|
+
} else if (bracket.startsWith('"') && bracket.endsWith('"') || bracket.startsWith("'") && bracket.endsWith("'")) {
|
|
9038
|
+
const quoted = bracket.startsWith("'") ? `"${bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : bracket;
|
|
9039
|
+
segments.push(String(JSON.parse(quoted)));
|
|
9040
|
+
} else {
|
|
9041
|
+
segments.push(bracket);
|
|
9042
|
+
}
|
|
9043
|
+
index = closeIndex;
|
|
9044
|
+
}
|
|
9045
|
+
pushToken();
|
|
9046
|
+
return segments;
|
|
9047
|
+
}
|
|
9048
|
+
function resolveJsonPath(root, path6) {
|
|
9049
|
+
let segments;
|
|
9050
|
+
try {
|
|
9051
|
+
segments = parseJsonPathSegments(path6);
|
|
9052
|
+
} catch (error) {
|
|
9053
|
+
return { exists: false, error: String(error instanceof Error ? error.message : error) };
|
|
9054
|
+
}
|
|
9055
|
+
let current = root;
|
|
9056
|
+
for (const segment of segments) {
|
|
9057
|
+
if (typeof segment === "number") {
|
|
9058
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
9059
|
+
current = current[segment];
|
|
9060
|
+
continue;
|
|
9061
|
+
}
|
|
9062
|
+
if (!isRecord2(current) || !hasOwn(current, segment)) return { exists: false };
|
|
9063
|
+
current = current[segment];
|
|
9064
|
+
}
|
|
9065
|
+
return { exists: true, value: current };
|
|
9066
|
+
}
|
|
9067
|
+
function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
|
|
9068
|
+
const resolved = resolveJsonPath(root, assertion.path);
|
|
9069
|
+
const errors = [];
|
|
9070
|
+
const result = {
|
|
9071
|
+
label: assertion.label || assertion.path,
|
|
9072
|
+
path: assertion.path,
|
|
9073
|
+
ok: true,
|
|
9074
|
+
exists: resolved.exists,
|
|
9075
|
+
observed_type: resolved.exists ? jsonValueType(resolved.value) : "missing"
|
|
9076
|
+
};
|
|
9077
|
+
if (resolved.exists) result.observed = toJsonValue(resolved.value);
|
|
9078
|
+
if (resolved.error) errors.push(resolved.error);
|
|
9079
|
+
if (hasOwn(assertion, "exists")) {
|
|
9080
|
+
result.expected_exists = assertion.exists;
|
|
9081
|
+
if (resolved.exists !== assertion.exists) errors.push(`expected exists=${assertion.exists}`);
|
|
9082
|
+
}
|
|
9083
|
+
if (hasOwn(assertion, "type")) {
|
|
9084
|
+
result.type = assertion.type;
|
|
9085
|
+
if (!resolved.exists || jsonValueType(resolved.value) !== assertion.type) errors.push(`expected type ${assertion.type}`);
|
|
9086
|
+
}
|
|
9087
|
+
if (hasOwn(assertion, "equals")) {
|
|
9088
|
+
result.equals = assertion.equals;
|
|
9089
|
+
if (!resolved.exists || !deepJsonEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
9090
|
+
}
|
|
9091
|
+
if (hasOwn(assertion, "not_equals")) {
|
|
9092
|
+
result.not_equals = assertion.not_equals;
|
|
9093
|
+
if (resolved.exists && deepJsonEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
9094
|
+
}
|
|
9095
|
+
if (hasOwn(assertion, "contains")) {
|
|
9096
|
+
result.contains = assertion.contains;
|
|
9097
|
+
if (!resolved.exists || !jsonContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
9098
|
+
}
|
|
9099
|
+
result.ok = errors.length === 0;
|
|
9100
|
+
if (errors.length) result.errors = errors;
|
|
9101
|
+
return result;
|
|
9102
|
+
}
|
|
9103
|
+
function evaluateHttpStatusBodyJsonAssertions(bodyText, assertions) {
|
|
9104
|
+
const expected = assertions?.filter((assertion) => assertion.path) ?? [];
|
|
9105
|
+
if (!expected.length) return [];
|
|
9106
|
+
let parsed;
|
|
9107
|
+
try {
|
|
9108
|
+
parsed = JSON.parse(bodyText);
|
|
9109
|
+
} catch (error) {
|
|
9110
|
+
const message = `response body is not valid JSON: ${String(error instanceof Error ? error.message : error).slice(0, 200)}`;
|
|
9111
|
+
return expected.map((assertion) => ({
|
|
9112
|
+
label: assertion.label || assertion.path,
|
|
9113
|
+
path: assertion.path,
|
|
9114
|
+
ok: false,
|
|
9115
|
+
exists: false,
|
|
9116
|
+
observed_type: "missing",
|
|
9117
|
+
errors: [message]
|
|
9118
|
+
}));
|
|
9119
|
+
}
|
|
9120
|
+
return expected.map((assertion) => evaluateHttpStatusBodyJsonAssertion(parsed, assertion));
|
|
9121
|
+
}
|
|
8969
9122
|
function compactProfileSetupSummaryText(value, limit = 160) {
|
|
8970
9123
|
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
8971
9124
|
if (!text) return void 0;
|
|
@@ -9329,6 +9482,20 @@ function normalizeNetworkMock(input, index) {
|
|
|
9329
9482
|
if (maxHitCount !== void 0 && effectiveRequiredHitCount > maxHitCount) {
|
|
9330
9483
|
throw new Error(`target.network_mocks[${index}].max_hit_count cannot be less than its required hit count.`);
|
|
9331
9484
|
}
|
|
9485
|
+
const sequenceScopeInput = stringValue5(
|
|
9486
|
+
input.sequence_scope ?? input.sequenceScope ?? input.response_sequence_scope ?? input.responseSequenceScope
|
|
9487
|
+
);
|
|
9488
|
+
let sequenceScope;
|
|
9489
|
+
if (sequenceScopeInput) {
|
|
9490
|
+
const normalizedScope = sequenceScopeInput.toLowerCase().replace(/[-\s]+/g, "_");
|
|
9491
|
+
if (normalizedScope === "global" || normalizedScope === "profile" || normalizedScope === "run") {
|
|
9492
|
+
sequenceScope = "global";
|
|
9493
|
+
} else if (normalizedScope === "viewport" || normalizedScope === "per_viewport" || normalizedScope === "viewport_scoped") {
|
|
9494
|
+
sequenceScope = "viewport";
|
|
9495
|
+
} else {
|
|
9496
|
+
throw new Error(`target.network_mocks[${index}].sequence_scope must be "global" or "viewport".`);
|
|
9497
|
+
}
|
|
9498
|
+
}
|
|
9332
9499
|
return {
|
|
9333
9500
|
...payload,
|
|
9334
9501
|
label: normalizeName(input.label || input.name, `network-mock-${index + 1}`),
|
|
@@ -9336,6 +9503,7 @@ function normalizeNetworkMock(input, index) {
|
|
|
9336
9503
|
method: stringValue5(input.method)?.toUpperCase(),
|
|
9337
9504
|
responses,
|
|
9338
9505
|
repeat_responses: input.repeat_responses === true || input.repeatResponses === true || input.cycle_responses === true || input.cycleResponses === true,
|
|
9506
|
+
sequence_scope: sequenceScope,
|
|
9339
9507
|
required_hit_count: requiredHitCount,
|
|
9340
9508
|
max_hit_count: maxHitCount,
|
|
9341
9509
|
forbidden,
|
|
@@ -9605,6 +9773,46 @@ function validateRegexPatterns(patterns, label) {
|
|
|
9605
9773
|
}
|
|
9606
9774
|
}
|
|
9607
9775
|
}
|
|
9776
|
+
function normalizeHttpStatusBodyJsonAssertions(value, label) {
|
|
9777
|
+
if (value === void 0) return void 0;
|
|
9778
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
9779
|
+
if (!value.length) throw new Error(`${label} must not be empty.`);
|
|
9780
|
+
return value.map((item, index) => {
|
|
9781
|
+
const itemLabel = `${label}[${index}]`;
|
|
9782
|
+
if (typeof item === "string") {
|
|
9783
|
+
const path7 = stringValue5(item);
|
|
9784
|
+
if (!path7) throw new Error(`${itemLabel} path must not be empty.`);
|
|
9785
|
+
return { path: path7, exists: true };
|
|
9786
|
+
}
|
|
9787
|
+
if (!isRecord2(item)) throw new Error(`${itemLabel} must be an object or JSON path string.`);
|
|
9788
|
+
const path6 = stringFromOwn(item, "path", "json_path", "jsonPath", "key");
|
|
9789
|
+
if (!path6) throw new Error(`${itemLabel}.path is required.`);
|
|
9790
|
+
const assertion = {
|
|
9791
|
+
label: stringValue5(item.label),
|
|
9792
|
+
path: path6
|
|
9793
|
+
};
|
|
9794
|
+
const exists = booleanValue(valueFromOwn(item, "exists", "present"));
|
|
9795
|
+
if (exists !== void 0) assertion.exists = exists;
|
|
9796
|
+
const type = stringValue5(valueFromOwn(item, "type", "value_type", "valueType"));
|
|
9797
|
+
if (type !== void 0) {
|
|
9798
|
+
const allowedTypes = ["array", "boolean", "null", "number", "object", "string"];
|
|
9799
|
+
if (!allowedTypes.includes(type)) {
|
|
9800
|
+
throw new Error(`${itemLabel}.type must be one of ${allowedTypes.join(", ")}.`);
|
|
9801
|
+
}
|
|
9802
|
+
assertion.type = type;
|
|
9803
|
+
}
|
|
9804
|
+
const equalsValue = valueFromOwn(item, "equals", "expected", "expected_value", "expectedValue", "value");
|
|
9805
|
+
if (equalsValue !== void 0) assertion.equals = toJsonValue(equalsValue);
|
|
9806
|
+
const notEqualsValue = valueFromOwn(item, "not_equals", "notEquals", "forbidden", "forbidden_value", "forbiddenValue");
|
|
9807
|
+
if (notEqualsValue !== void 0) assertion.not_equals = toJsonValue(notEqualsValue);
|
|
9808
|
+
const containsValue = valueFromOwn(item, "contains", "includes", "contains_value", "containsValue", "include");
|
|
9809
|
+
if (containsValue !== void 0) assertion.contains = toJsonValue(containsValue);
|
|
9810
|
+
if (assertion.exists === void 0 && assertion.type === void 0 && !hasOwn(assertion, "equals") && !hasOwn(assertion, "not_equals") && !hasOwn(assertion, "contains")) {
|
|
9811
|
+
assertion.exists = true;
|
|
9812
|
+
}
|
|
9813
|
+
return assertion;
|
|
9814
|
+
});
|
|
9815
|
+
}
|
|
9608
9816
|
function isDialogCountCheckType(type) {
|
|
9609
9817
|
return type === "dialog_count_equals" || type === "dialog_accept_count_equals" || type === "dialog_dismiss_count_equals";
|
|
9610
9818
|
}
|
|
@@ -9704,6 +9912,10 @@ function normalizeCheck(input, index) {
|
|
|
9704
9912
|
`checks[${index}] body_not_patterns`
|
|
9705
9913
|
) : void 0;
|
|
9706
9914
|
if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
|
|
9915
|
+
const bodyJsonAssertions = isHttpStatusCheck ? normalizeHttpStatusBodyJsonAssertions(
|
|
9916
|
+
input.body_json_assertions ?? input.bodyJsonAssertions ?? input.json_body_assertions ?? input.jsonBodyAssertions ?? input.json_assertions ?? input.jsonAssertions ?? input.response_json_assertions ?? input.responseJsonAssertions,
|
|
9917
|
+
`checks[${index}] body_json_assertions`
|
|
9918
|
+
) : void 0;
|
|
9707
9919
|
if (isLinkStatusCheck) {
|
|
9708
9920
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
9709
9921
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -9729,6 +9941,7 @@ function normalizeCheck(input, index) {
|
|
|
9729
9941
|
body_contains: bodyContains,
|
|
9730
9942
|
body_not_contains: bodyNotContains,
|
|
9731
9943
|
body_not_patterns: bodyNotPatterns,
|
|
9944
|
+
body_json_assertions: bodyJsonAssertions,
|
|
9732
9945
|
expected_texts: expectedTexts,
|
|
9733
9946
|
link_selector: stringValue5(input.link_selector) || stringValue5(input.linkSelector),
|
|
9734
9947
|
source_selector: stringValue5(input.source_selector) || stringValue5(input.sourceSelector),
|
|
@@ -9935,6 +10148,34 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
9935
10148
|
const observed = isRecord2(result.body_not_patterns) ? result.body_not_patterns : {};
|
|
9936
10149
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
9937
10150
|
}
|
|
10151
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
10152
|
+
const expected = check.body_json_assertions?.filter((assertion) => assertion.path) ?? [];
|
|
10153
|
+
if (!expected.length) return [];
|
|
10154
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
10155
|
+
return expected.map((assertion) => ({
|
|
10156
|
+
label: assertion.label || assertion.path,
|
|
10157
|
+
path: assertion.path,
|
|
10158
|
+
ok: false,
|
|
10159
|
+
exists: false,
|
|
10160
|
+
observed_type: "missing",
|
|
10161
|
+
errors: ["body_json_assertions evidence missing"]
|
|
10162
|
+
}));
|
|
10163
|
+
}
|
|
10164
|
+
return result.body_json_assertions.filter((assertion) => isRecord2(assertion) && assertion.ok !== true).map((assertion) => ({
|
|
10165
|
+
label: stringValue5(assertion.label) || stringValue5(assertion.path) || "json assertion",
|
|
10166
|
+
path: stringValue5(assertion.path) || "",
|
|
10167
|
+
ok: false,
|
|
10168
|
+
exists: assertion.exists === true,
|
|
10169
|
+
observed: hasOwn(assertion, "observed") ? toJsonValue(assertion.observed) : void 0,
|
|
10170
|
+
observed_type: stringValue5(assertion.observed_type) || "missing",
|
|
10171
|
+
expected_exists: booleanValue(assertion.expected_exists),
|
|
10172
|
+
equals: hasOwn(assertion, "equals") ? toJsonValue(assertion.equals) : void 0,
|
|
10173
|
+
not_equals: hasOwn(assertion, "not_equals") ? toJsonValue(assertion.not_equals) : void 0,
|
|
10174
|
+
contains: hasOwn(assertion, "contains") ? toJsonValue(assertion.contains) : void 0,
|
|
10175
|
+
type: stringValue5(assertion.type),
|
|
10176
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : void 0
|
|
10177
|
+
}));
|
|
10178
|
+
}
|
|
9938
10179
|
function linkStatusResultOk(result, check) {
|
|
9939
10180
|
const status = numberValue3(result.status);
|
|
9940
10181
|
if (!httpStatusIsAllowed(status, check)) return false;
|
|
@@ -9952,6 +10193,7 @@ function linkStatusResultOk(result, check) {
|
|
|
9952
10193
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
9953
10194
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
9954
10195
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
10196
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
9955
10197
|
return true;
|
|
9956
10198
|
}
|
|
9957
10199
|
function responseHeader(response, name) {
|
|
@@ -10020,7 +10262,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10020
10262
|
statusText = typeof response.statusText === "string" ? response.statusText : "";
|
|
10021
10263
|
result.content_type = responseHeader(response, "content-type");
|
|
10022
10264
|
result.content_length = responseContentLength(response);
|
|
10023
|
-
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);
|
|
10265
|
+
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);
|
|
10024
10266
|
if (shouldReadBody && method !== "HEAD") {
|
|
10025
10267
|
const body = await responseBodyText(response);
|
|
10026
10268
|
result.bytes = body.bytes;
|
|
@@ -10033,6 +10275,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10033
10275
|
if (check.body_not_patterns?.length) {
|
|
10034
10276
|
result.body_not_patterns = Object.fromEntries(check.body_not_patterns.filter(Boolean).map((pattern) => [pattern, new RegExp(pattern).test(body.text)]));
|
|
10035
10277
|
}
|
|
10278
|
+
if (check.body_json_assertions?.length) {
|
|
10279
|
+
result.body_json_assertions = evaluateHttpStatusBodyJsonAssertions(body.text, check.body_json_assertions);
|
|
10280
|
+
}
|
|
10036
10281
|
}
|
|
10037
10282
|
} catch (caught) {
|
|
10038
10283
|
error = String(caught instanceof Error ? caught.message : caught).slice(0, 500);
|
|
@@ -10041,6 +10286,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10041
10286
|
const bodyContainsMissing = httpStatusBodyContainsFailures(result, check);
|
|
10042
10287
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(result, check);
|
|
10043
10288
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(result, check);
|
|
10289
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(result, check);
|
|
10044
10290
|
const ok = !error && linkStatusResultOk(result, check);
|
|
10045
10291
|
return {
|
|
10046
10292
|
index,
|
|
@@ -10059,7 +10305,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10059
10305
|
body_not_contains: isRecord2(result.body_not_contains) ? Object.fromEntries(Object.entries(result.body_not_contains).map(([key, value]) => [key, value === true])) : null,
|
|
10060
10306
|
body_not_contains_found: bodyNotContainsFound,
|
|
10061
10307
|
body_not_patterns: isRecord2(result.body_not_patterns) ? Object.fromEntries(Object.entries(result.body_not_patterns).map(([key, value]) => [key, value === true])) : null,
|
|
10062
|
-
body_not_patterns_found: bodyNotPatternsFound
|
|
10308
|
+
body_not_patterns_found: bodyNotPatternsFound,
|
|
10309
|
+
body_json_assertions: Array.isArray(result.body_json_assertions) ? result.body_json_assertions : null,
|
|
10310
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed
|
|
10063
10311
|
};
|
|
10064
10312
|
}
|
|
10065
10313
|
async function preflightRiddleProofProfileHttpStatusChecks(profile, options = {}) {
|
|
@@ -10104,6 +10352,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10104
10352
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
10105
10353
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
10106
10354
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
10355
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
10107
10356
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
10108
10357
|
failures.push({
|
|
10109
10358
|
code: "http_status_failed",
|
|
@@ -10122,6 +10371,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10122
10371
|
body_not_contains_found: bodyNotContainsFound,
|
|
10123
10372
|
body_not_patterns: check.body_not_patterns ?? null,
|
|
10124
10373
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10374
|
+
body_json_assertions: check.body_json_assertions ?? null,
|
|
10375
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
10125
10376
|
body_sample: stringValue5(statusEvidence.body_sample) ?? null
|
|
10126
10377
|
});
|
|
10127
10378
|
}
|
|
@@ -10143,6 +10394,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10143
10394
|
body_not_contains_found: bodyNotContainsFound,
|
|
10144
10395
|
body_not_patterns: isRecord2(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
|
|
10145
10396
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10397
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? toJsonValue(statusEvidence.body_json_assertions) : null,
|
|
10398
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
10146
10399
|
body_sample: stringValue5(statusEvidence.body_sample) ?? null,
|
|
10147
10400
|
failures
|
|
10148
10401
|
};
|
|
@@ -10779,6 +11032,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
10779
11032
|
body_contains: check.body_contains ?? [],
|
|
10780
11033
|
body_not_contains: check.body_not_contains ?? [],
|
|
10781
11034
|
body_not_patterns: check.body_not_patterns ?? [],
|
|
11035
|
+
body_json_assertions: toJsonValue(check.body_json_assertions ?? []),
|
|
10782
11036
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
10783
11037
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue5(summary.viewport) ?? null, failure })) : [])
|
|
10784
11038
|
},
|
|
@@ -11522,6 +11776,36 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
11522
11776
|
: {};
|
|
11523
11777
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
11524
11778
|
}
|
|
11779
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
11780
|
+
const expected = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
11781
|
+
if (!expected.length) return [];
|
|
11782
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
11783
|
+
return expected.map((assertion) => ({
|
|
11784
|
+
label: assertion.label || assertion.path,
|
|
11785
|
+
path: assertion.path,
|
|
11786
|
+
ok: false,
|
|
11787
|
+
exists: false,
|
|
11788
|
+
observed_type: "missing",
|
|
11789
|
+
errors: ["body_json_assertions evidence missing"],
|
|
11790
|
+
}));
|
|
11791
|
+
}
|
|
11792
|
+
return result.body_json_assertions
|
|
11793
|
+
.filter((assertion) => assertion && typeof assertion === "object" && assertion.ok !== true)
|
|
11794
|
+
.map((assertion) => ({
|
|
11795
|
+
label: typeof assertion.label === "string" && assertion.label ? assertion.label : typeof assertion.path === "string" && assertion.path ? assertion.path : "json assertion",
|
|
11796
|
+
path: typeof assertion.path === "string" ? assertion.path : "",
|
|
11797
|
+
ok: false,
|
|
11798
|
+
exists: assertion.exists === true,
|
|
11799
|
+
observed: Object.hasOwn(assertion, "observed") ? assertion.observed : undefined,
|
|
11800
|
+
observed_type: typeof assertion.observed_type === "string" && assertion.observed_type ? assertion.observed_type : "missing",
|
|
11801
|
+
expected_exists: typeof assertion.expected_exists === "boolean" ? assertion.expected_exists : undefined,
|
|
11802
|
+
equals: Object.hasOwn(assertion, "equals") ? assertion.equals : undefined,
|
|
11803
|
+
not_equals: Object.hasOwn(assertion, "not_equals") ? assertion.not_equals : undefined,
|
|
11804
|
+
contains: Object.hasOwn(assertion, "contains") ? assertion.contains : undefined,
|
|
11805
|
+
type: typeof assertion.type === "string" ? assertion.type : undefined,
|
|
11806
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : undefined,
|
|
11807
|
+
}));
|
|
11808
|
+
}
|
|
11525
11809
|
function linkStatusResultOk(result, check) {
|
|
11526
11810
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
11527
11811
|
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
@@ -11539,6 +11823,7 @@ function linkStatusResultOk(result, check) {
|
|
|
11539
11823
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
11540
11824
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
11541
11825
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
11826
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
11542
11827
|
return true;
|
|
11543
11828
|
}
|
|
11544
11829
|
function summarizeHttpStatusEvidence(viewport, check) {
|
|
@@ -11559,6 +11844,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
11559
11844
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
11560
11845
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
11561
11846
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
11847
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
11562
11848
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
11563
11849
|
failures.push({
|
|
11564
11850
|
code: "http_status_failed",
|
|
@@ -11577,6 +11863,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
11577
11863
|
body_not_contains_found: bodyNotContainsFound,
|
|
11578
11864
|
body_not_patterns: Array.isArray(check.body_not_patterns) ? check.body_not_patterns : null,
|
|
11579
11865
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
11866
|
+
body_json_assertions: Array.isArray(check.body_json_assertions) ? check.body_json_assertions : null,
|
|
11867
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
11580
11868
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
11581
11869
|
});
|
|
11582
11870
|
}
|
|
@@ -11604,6 +11892,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
11604
11892
|
? statusEvidence.body_not_patterns
|
|
11605
11893
|
: null,
|
|
11606
11894
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
11895
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? statusEvidence.body_json_assertions : null,
|
|
11896
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
11607
11897
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
11608
11898
|
failures,
|
|
11609
11899
|
};
|
|
@@ -12987,6 +13277,7 @@ async function setupLocatorVisible(locator, index) {
|
|
|
12987
13277
|
async function registerNetworkMocks(mocks) {
|
|
12988
13278
|
for (const mock of mocks || []) {
|
|
12989
13279
|
let hitCount = 0;
|
|
13280
|
+
const scopedHitCounts = {};
|
|
12990
13281
|
await page.route(mock.url, async (route) => {
|
|
12991
13282
|
const request = route.request();
|
|
12992
13283
|
const method = request.method ? request.method() : "";
|
|
@@ -13002,8 +13293,13 @@ async function registerNetworkMocks(mocks) {
|
|
|
13002
13293
|
const responses = Array.isArray(mock.responses) ? mock.responses : [];
|
|
13003
13294
|
const hitIndex = hitCount;
|
|
13004
13295
|
hitCount += 1;
|
|
13296
|
+
const sequenceScope = mock.sequence_scope === "viewport" ? "viewport" : "global";
|
|
13297
|
+
const viewportName = activeViewportName || null;
|
|
13298
|
+
const sequenceScopeKey = sequenceScope === "viewport" ? (viewportName || "__unknown_viewport__") : "__global__";
|
|
13299
|
+
const sequenceHitIndex = sequenceScope === "viewport" ? (scopedHitCounts[sequenceScopeKey] || 0) : hitIndex;
|
|
13300
|
+
if (sequenceScope === "viewport") scopedHitCounts[sequenceScopeKey] = sequenceHitIndex + 1;
|
|
13005
13301
|
const sequenceResponseIndex = responses.length
|
|
13006
|
-
? (mock.repeat_responses ?
|
|
13302
|
+
? (mock.repeat_responses ? sequenceHitIndex % responses.length : Math.min(sequenceHitIndex, responses.length - 1))
|
|
13007
13303
|
: null;
|
|
13008
13304
|
let responseIndex = sequenceResponseIndex;
|
|
13009
13305
|
let responseSelection = responseIndex === null ? "mock" : "sequence";
|
|
@@ -13038,11 +13334,14 @@ async function registerNetworkMocks(mocks) {
|
|
|
13038
13334
|
label: mock.label,
|
|
13039
13335
|
response_label: response.label || null,
|
|
13040
13336
|
hit_index: hitIndex,
|
|
13337
|
+
sequence_hit_index: responseIndex === null ? undefined : sequenceHitIndex,
|
|
13338
|
+
sequence_scope: responseIndex === null ? undefined : sequenceScope,
|
|
13339
|
+
viewport: viewportName,
|
|
13041
13340
|
response_index: responseIndex,
|
|
13042
13341
|
sequence_response_index: responseSelection === "request_body" ? sequenceResponseIndex : undefined,
|
|
13043
13342
|
response_selection: responseIndex === null ? null : responseSelection,
|
|
13044
|
-
sequence_reused: responseSelection === "sequence" && responseIndex !== null && !mock.repeat_responses &&
|
|
13045
|
-
sequence_cycle: responseSelection === "sequence" && responseIndex !== null && mock.repeat_responses === true &&
|
|
13343
|
+
sequence_reused: responseSelection === "sequence" && responseIndex !== null && !mock.repeat_responses && sequenceHitIndex >= responses.length,
|
|
13344
|
+
sequence_cycle: responseSelection === "sequence" && responseIndex !== null && mock.repeat_responses === true && sequenceHitIndex >= responses.length,
|
|
13046
13345
|
url: request.url(),
|
|
13047
13346
|
method,
|
|
13048
13347
|
};
|
|
@@ -13084,6 +13383,7 @@ async function registerNetworkMocks(mocks) {
|
|
|
13084
13383
|
});
|
|
13085
13384
|
}
|
|
13086
13385
|
}
|
|
13386
|
+
let activeViewportName = null;
|
|
13087
13387
|
async function executeSetupAction(action, ordinal, viewport) {
|
|
13088
13388
|
const type = setupActionType(action);
|
|
13089
13389
|
const frameSelector = setupFrameSelector(action);
|
|
@@ -13696,6 +13996,155 @@ function linkProbeResponseFields(response, method) {
|
|
|
13696
13996
|
content_length: contentLength,
|
|
13697
13997
|
};
|
|
13698
13998
|
}
|
|
13999
|
+
function jsonProbeValueType(value) {
|
|
14000
|
+
if (value === null) return "null";
|
|
14001
|
+
if (Array.isArray(value)) return "array";
|
|
14002
|
+
if (typeof value === "boolean") return "boolean";
|
|
14003
|
+
if (typeof value === "number") return "number";
|
|
14004
|
+
if (typeof value === "string") return "string";
|
|
14005
|
+
return "object";
|
|
14006
|
+
}
|
|
14007
|
+
function jsonProbeDeepEqual(left, right) {
|
|
14008
|
+
if (left === right) return true;
|
|
14009
|
+
if (typeof left !== typeof right) return false;
|
|
14010
|
+
if (left === null || right === null) return left === right;
|
|
14011
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
14012
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
14013
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
14014
|
+
return left.every((item, index) => jsonProbeDeepEqual(item, right[index]));
|
|
14015
|
+
}
|
|
14016
|
+
const leftKeys = Object.keys(left).sort();
|
|
14017
|
+
const rightKeys = Object.keys(right).sort();
|
|
14018
|
+
if (!jsonProbeDeepEqual(leftKeys, rightKeys)) return false;
|
|
14019
|
+
return leftKeys.every((key) => jsonProbeDeepEqual(left[key], right[key]));
|
|
14020
|
+
}
|
|
14021
|
+
function jsonProbeContains(observed, expected) {
|
|
14022
|
+
if (typeof observed === "string" && typeof expected === "string") return observed.includes(expected);
|
|
14023
|
+
if (Array.isArray(observed)) return observed.some((item) => jsonProbeDeepEqual(item, expected));
|
|
14024
|
+
if (observed && expected && typeof observed === "object" && typeof expected === "object" && !Array.isArray(observed) && !Array.isArray(expected)) {
|
|
14025
|
+
return Object.entries(expected).every(([key, value]) => Object.hasOwn(observed, key) && jsonProbeDeepEqual(observed[key], value));
|
|
14026
|
+
}
|
|
14027
|
+
return false;
|
|
14028
|
+
}
|
|
14029
|
+
function parseJsonProbePathSegments(path) {
|
|
14030
|
+
let input = String(path || "").trim();
|
|
14031
|
+
if (!input) throw new Error("path is empty");
|
|
14032
|
+
if (input === "$") return [];
|
|
14033
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
14034
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
14035
|
+
const segments = [];
|
|
14036
|
+
let token = "";
|
|
14037
|
+
const pushToken = () => {
|
|
14038
|
+
if (!token) return;
|
|
14039
|
+
segments.push(token);
|
|
14040
|
+
token = "";
|
|
14041
|
+
};
|
|
14042
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
14043
|
+
const char = input[index];
|
|
14044
|
+
if (char === ".") {
|
|
14045
|
+
pushToken();
|
|
14046
|
+
continue;
|
|
14047
|
+
}
|
|
14048
|
+
if (char !== "[") {
|
|
14049
|
+
token += char;
|
|
14050
|
+
continue;
|
|
14051
|
+
}
|
|
14052
|
+
pushToken();
|
|
14053
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
14054
|
+
if (closeIndex === -1) throw new Error("unterminated bracket at " + index);
|
|
14055
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
14056
|
+
if (!bracket) throw new Error("empty bracket at " + index);
|
|
14057
|
+
if (/^\d+$/.test(bracket)) {
|
|
14058
|
+
segments.push(Number(bracket));
|
|
14059
|
+
} else if ((bracket.startsWith('"') && bracket.endsWith('"')) || (bracket.startsWith("'") && bracket.endsWith("'"))) {
|
|
14060
|
+
const quoted = bracket.startsWith("'")
|
|
14061
|
+
? '"' + bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"'
|
|
14062
|
+
: bracket;
|
|
14063
|
+
segments.push(String(JSON.parse(quoted)));
|
|
14064
|
+
} else {
|
|
14065
|
+
segments.push(bracket);
|
|
14066
|
+
}
|
|
14067
|
+
index = closeIndex;
|
|
14068
|
+
}
|
|
14069
|
+
pushToken();
|
|
14070
|
+
return segments;
|
|
14071
|
+
}
|
|
14072
|
+
function resolveJsonProbePath(root, path) {
|
|
14073
|
+
let segments;
|
|
14074
|
+
try {
|
|
14075
|
+
segments = parseJsonProbePathSegments(path);
|
|
14076
|
+
} catch (error) {
|
|
14077
|
+
return { exists: false, error: String(error && error.message ? error.message : error) };
|
|
14078
|
+
}
|
|
14079
|
+
let current = root;
|
|
14080
|
+
for (const segment of segments) {
|
|
14081
|
+
if (typeof segment === "number") {
|
|
14082
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
14083
|
+
current = current[segment];
|
|
14084
|
+
continue;
|
|
14085
|
+
}
|
|
14086
|
+
if (!current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment)) {
|
|
14087
|
+
return { exists: false };
|
|
14088
|
+
}
|
|
14089
|
+
current = current[segment];
|
|
14090
|
+
}
|
|
14091
|
+
return { exists: true, value: current };
|
|
14092
|
+
}
|
|
14093
|
+
function evaluateJsonProbeAssertion(root, assertion) {
|
|
14094
|
+
const resolved = resolveJsonProbePath(root, assertion.path);
|
|
14095
|
+
const errors = [];
|
|
14096
|
+
const result = {
|
|
14097
|
+
label: assertion.label || assertion.path,
|
|
14098
|
+
path: assertion.path,
|
|
14099
|
+
ok: true,
|
|
14100
|
+
exists: resolved.exists,
|
|
14101
|
+
observed_type: resolved.exists ? jsonProbeValueType(resolved.value) : "missing",
|
|
14102
|
+
};
|
|
14103
|
+
if (resolved.exists) result.observed = resolved.value;
|
|
14104
|
+
if (resolved.error) errors.push(resolved.error);
|
|
14105
|
+
if (Object.hasOwn(assertion, "exists")) {
|
|
14106
|
+
result.expected_exists = assertion.exists;
|
|
14107
|
+
if (resolved.exists !== assertion.exists) errors.push("expected exists=" + assertion.exists);
|
|
14108
|
+
}
|
|
14109
|
+
if (Object.hasOwn(assertion, "type")) {
|
|
14110
|
+
result.type = assertion.type;
|
|
14111
|
+
if (!resolved.exists || jsonProbeValueType(resolved.value) !== assertion.type) errors.push("expected type " + assertion.type);
|
|
14112
|
+
}
|
|
14113
|
+
if (Object.hasOwn(assertion, "equals")) {
|
|
14114
|
+
result.equals = assertion.equals;
|
|
14115
|
+
if (!resolved.exists || !jsonProbeDeepEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
14116
|
+
}
|
|
14117
|
+
if (Object.hasOwn(assertion, "not_equals")) {
|
|
14118
|
+
result.not_equals = assertion.not_equals;
|
|
14119
|
+
if (resolved.exists && jsonProbeDeepEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
14120
|
+
}
|
|
14121
|
+
if (Object.hasOwn(assertion, "contains")) {
|
|
14122
|
+
result.contains = assertion.contains;
|
|
14123
|
+
if (!resolved.exists || !jsonProbeContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
14124
|
+
}
|
|
14125
|
+
result.ok = errors.length === 0;
|
|
14126
|
+
if (errors.length) result.errors = errors;
|
|
14127
|
+
return result;
|
|
14128
|
+
}
|
|
14129
|
+
function evaluateJsonProbeAssertions(text, assertions) {
|
|
14130
|
+
const expected = Array.isArray(assertions) ? assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
14131
|
+
if (!expected.length) return [];
|
|
14132
|
+
let parsed;
|
|
14133
|
+
try {
|
|
14134
|
+
parsed = JSON.parse(text);
|
|
14135
|
+
} catch (error) {
|
|
14136
|
+
const message = "response body is not valid JSON: " + String(error && error.message ? error.message : error).slice(0, 200);
|
|
14137
|
+
return expected.map((assertion) => ({
|
|
14138
|
+
label: assertion.label || assertion.path,
|
|
14139
|
+
path: assertion.path,
|
|
14140
|
+
ok: false,
|
|
14141
|
+
exists: false,
|
|
14142
|
+
observed_type: "missing",
|
|
14143
|
+
errors: [message],
|
|
14144
|
+
}));
|
|
14145
|
+
}
|
|
14146
|
+
return expected.map((assertion) => evaluateJsonProbeAssertion(parsed, assertion));
|
|
14147
|
+
}
|
|
13699
14148
|
async function collectHttpStatus(check) {
|
|
13700
14149
|
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
13701
14150
|
const method = httpStatusMethod(check);
|
|
@@ -13712,6 +14161,7 @@ async function collectHttpStatus(check) {
|
|
|
13712
14161
|
const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
13713
14162
|
const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
|
|
13714
14163
|
const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
|
|
14164
|
+
const bodyJsonAssertions = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
13715
14165
|
const options = {
|
|
13716
14166
|
method,
|
|
13717
14167
|
redirect: "follow",
|
|
@@ -13737,17 +14187,18 @@ async function collectHttpStatus(check) {
|
|
|
13737
14187
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
13738
14188
|
result.url = url;
|
|
13739
14189
|
result.status_text = response.statusText || "";
|
|
13740
|
-
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;
|
|
14190
|
+
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;
|
|
13741
14191
|
if (shouldReadBody) {
|
|
13742
14192
|
try {
|
|
13743
14193
|
const buffer = await response.arrayBuffer();
|
|
13744
14194
|
result.bytes = buffer.byteLength;
|
|
13745
|
-
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
|
|
14195
|
+
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length || bodyJsonAssertions.length) {
|
|
13746
14196
|
const text = new TextDecoder().decode(buffer);
|
|
13747
14197
|
result.body_sample = text.slice(0, 1000);
|
|
13748
14198
|
if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
|
|
13749
14199
|
if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
|
|
13750
14200
|
if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
|
|
14201
|
+
if (bodyJsonAssertions.length) result.body_json_assertions = evaluateJsonProbeAssertions(text, bodyJsonAssertions);
|
|
13751
14202
|
}
|
|
13752
14203
|
} catch (error) {
|
|
13753
14204
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -13760,6 +14211,7 @@ async function collectHttpStatus(check) {
|
|
|
13760
14211
|
&& (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
|
|
13761
14212
|
&& (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
|
|
13762
14213
|
&& (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
|
|
14214
|
+
&& (!bodyJsonAssertions.length || (Array.isArray(result.body_json_assertions) && result.body_json_assertions.every((assertion) => assertion.ok === true)))
|
|
13763
14215
|
&& !result.error;
|
|
13764
14216
|
return result;
|
|
13765
14217
|
} catch (error) {
|
|
@@ -14346,6 +14798,7 @@ async function collectRouteInventory(check, viewport) {
|
|
|
14346
14798
|
};
|
|
14347
14799
|
}
|
|
14348
14800
|
async function captureViewport(viewport) {
|
|
14801
|
+
activeViewportName = viewport && viewport.name ? viewport.name : null;
|
|
14349
14802
|
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
14350
14803
|
let httpStatus = null;
|
|
14351
14804
|
let navigationError;
|