@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/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,187 @@ 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 compactJsonAssertionSample(value, depth = 0) {
|
|
8981
|
+
if (typeof value === "string") return value.length > 240 ? `${value.slice(0, 237)}...` : value;
|
|
8982
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return toJsonValue(value);
|
|
8983
|
+
if (Array.isArray(value)) {
|
|
8984
|
+
if (depth >= 2) return `[array:${value.length}]`;
|
|
8985
|
+
return value.slice(0, 3).map((item) => compactJsonAssertionSample(item, depth + 1));
|
|
8986
|
+
}
|
|
8987
|
+
if (isRecord2(value)) {
|
|
8988
|
+
const entries = Object.entries(value).slice(0, 8);
|
|
8989
|
+
if (depth >= 2) return `[object:${Object.keys(value).length} keys]`;
|
|
8990
|
+
return Object.fromEntries(entries.map(([key, child]) => [key, compactJsonAssertionSample(child, depth + 1)]));
|
|
8991
|
+
}
|
|
8992
|
+
return String(value);
|
|
8993
|
+
}
|
|
8994
|
+
function attachJsonAssertionObservedValue(result, value) {
|
|
8995
|
+
const type = jsonValueType(value);
|
|
8996
|
+
if (type === "array" && Array.isArray(value)) {
|
|
8997
|
+
result.observed_length = value.length;
|
|
8998
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
8999
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
9000
|
+
return;
|
|
9001
|
+
}
|
|
9002
|
+
if (type === "object" && isRecord2(value)) {
|
|
9003
|
+
const keyCount = Object.keys(value).length;
|
|
9004
|
+
result.observed_key_count = keyCount;
|
|
9005
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
9006
|
+
result.observed_sample = compactJsonAssertionSample(value);
|
|
9007
|
+
return;
|
|
9008
|
+
}
|
|
9009
|
+
result.observed = toJsonValue(value);
|
|
9010
|
+
}
|
|
9011
|
+
function deepJsonEqual(left, right) {
|
|
9012
|
+
if (left === right) return true;
|
|
9013
|
+
if (typeof left !== typeof right) return false;
|
|
9014
|
+
if (left === null || right === null) return left === right;
|
|
9015
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
9016
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
9017
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
9018
|
+
return left.every((item, index) => deepJsonEqual(item, right[index]));
|
|
9019
|
+
}
|
|
9020
|
+
if (!isRecord2(left) || !isRecord2(right)) return false;
|
|
9021
|
+
const leftKeys = Object.keys(left).sort();
|
|
9022
|
+
const rightKeys = Object.keys(right).sort();
|
|
9023
|
+
if (!deepJsonEqual(leftKeys, rightKeys)) return false;
|
|
9024
|
+
return leftKeys.every((key) => deepJsonEqual(left[key], right[key]));
|
|
9025
|
+
}
|
|
9026
|
+
function jsonContains(observed, expected) {
|
|
9027
|
+
if (typeof observed === "string" && typeof expected === "string") {
|
|
9028
|
+
return observed.includes(expected);
|
|
9029
|
+
}
|
|
9030
|
+
if (Array.isArray(observed)) {
|
|
9031
|
+
return observed.some((item) => deepJsonEqual(item, expected));
|
|
9032
|
+
}
|
|
9033
|
+
if (isRecord2(observed) && isRecord2(expected)) {
|
|
9034
|
+
return Object.entries(expected).every(([key, value]) => hasOwn(observed, key) && deepJsonEqual(observed[key], value));
|
|
9035
|
+
}
|
|
9036
|
+
return false;
|
|
9037
|
+
}
|
|
9038
|
+
function parseJsonPathSegments(path6) {
|
|
9039
|
+
let input = path6.trim();
|
|
9040
|
+
if (!input) throw new Error("path is empty");
|
|
9041
|
+
if (input === "$") return [];
|
|
9042
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
9043
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
9044
|
+
const segments = [];
|
|
9045
|
+
let token = "";
|
|
9046
|
+
const pushToken = () => {
|
|
9047
|
+
if (!token) return;
|
|
9048
|
+
segments.push(token);
|
|
9049
|
+
token = "";
|
|
9050
|
+
};
|
|
9051
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
9052
|
+
const char = input[index];
|
|
9053
|
+
if (char === ".") {
|
|
9054
|
+
pushToken();
|
|
9055
|
+
continue;
|
|
9056
|
+
}
|
|
9057
|
+
if (char !== "[") {
|
|
9058
|
+
token += char;
|
|
9059
|
+
continue;
|
|
9060
|
+
}
|
|
9061
|
+
pushToken();
|
|
9062
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
9063
|
+
if (closeIndex === -1) throw new Error(`unterminated bracket at ${index}`);
|
|
9064
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
9065
|
+
if (!bracket) throw new Error(`empty bracket at ${index}`);
|
|
9066
|
+
if (/^\d+$/.test(bracket)) {
|
|
9067
|
+
segments.push(Number(bracket));
|
|
9068
|
+
} else if (bracket.startsWith('"') && bracket.endsWith('"') || bracket.startsWith("'") && bracket.endsWith("'")) {
|
|
9069
|
+
const quoted = bracket.startsWith("'") ? `"${bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : bracket;
|
|
9070
|
+
segments.push(String(JSON.parse(quoted)));
|
|
9071
|
+
} else {
|
|
9072
|
+
segments.push(bracket);
|
|
9073
|
+
}
|
|
9074
|
+
index = closeIndex;
|
|
9075
|
+
}
|
|
9076
|
+
pushToken();
|
|
9077
|
+
return segments;
|
|
9078
|
+
}
|
|
9079
|
+
function resolveJsonPath(root, path6) {
|
|
9080
|
+
let segments;
|
|
9081
|
+
try {
|
|
9082
|
+
segments = parseJsonPathSegments(path6);
|
|
9083
|
+
} catch (error) {
|
|
9084
|
+
return { exists: false, error: String(error instanceof Error ? error.message : error) };
|
|
9085
|
+
}
|
|
9086
|
+
let current = root;
|
|
9087
|
+
for (const segment of segments) {
|
|
9088
|
+
if (typeof segment === "number") {
|
|
9089
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
9090
|
+
current = current[segment];
|
|
9091
|
+
continue;
|
|
9092
|
+
}
|
|
9093
|
+
if (!isRecord2(current) || !hasOwn(current, segment)) return { exists: false };
|
|
9094
|
+
current = current[segment];
|
|
9095
|
+
}
|
|
9096
|
+
return { exists: true, value: current };
|
|
9097
|
+
}
|
|
9098
|
+
function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
|
|
9099
|
+
const resolved = resolveJsonPath(root, assertion.path);
|
|
9100
|
+
const errors = [];
|
|
9101
|
+
const result = {
|
|
9102
|
+
label: assertion.label || assertion.path,
|
|
9103
|
+
path: assertion.path,
|
|
9104
|
+
ok: true,
|
|
9105
|
+
exists: resolved.exists,
|
|
9106
|
+
observed_type: resolved.exists ? jsonValueType(resolved.value) : "missing"
|
|
9107
|
+
};
|
|
9108
|
+
if (resolved.exists) attachJsonAssertionObservedValue(result, resolved.value);
|
|
9109
|
+
if (resolved.error) errors.push(resolved.error);
|
|
9110
|
+
if (hasOwn(assertion, "exists")) {
|
|
9111
|
+
result.expected_exists = assertion.exists;
|
|
9112
|
+
if (resolved.exists !== assertion.exists) errors.push(`expected exists=${assertion.exists}`);
|
|
9113
|
+
}
|
|
9114
|
+
if (hasOwn(assertion, "type")) {
|
|
9115
|
+
result.type = assertion.type;
|
|
9116
|
+
if (!resolved.exists || jsonValueType(resolved.value) !== assertion.type) errors.push(`expected type ${assertion.type}`);
|
|
9117
|
+
}
|
|
9118
|
+
if (hasOwn(assertion, "equals")) {
|
|
9119
|
+
result.equals = assertion.equals;
|
|
9120
|
+
if (!resolved.exists || !deepJsonEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
9121
|
+
}
|
|
9122
|
+
if (hasOwn(assertion, "not_equals")) {
|
|
9123
|
+
result.not_equals = assertion.not_equals;
|
|
9124
|
+
if (resolved.exists && deepJsonEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
9125
|
+
}
|
|
9126
|
+
if (hasOwn(assertion, "contains")) {
|
|
9127
|
+
result.contains = assertion.contains;
|
|
9128
|
+
if (!resolved.exists || !jsonContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
9129
|
+
}
|
|
9130
|
+
result.ok = errors.length === 0;
|
|
9131
|
+
if (errors.length) result.errors = errors;
|
|
9132
|
+
return result;
|
|
9133
|
+
}
|
|
9134
|
+
function evaluateHttpStatusBodyJsonAssertions(bodyText, assertions) {
|
|
9135
|
+
const expected = assertions?.filter((assertion) => assertion.path) ?? [];
|
|
9136
|
+
if (!expected.length) return [];
|
|
9137
|
+
let parsed;
|
|
9138
|
+
try {
|
|
9139
|
+
parsed = JSON.parse(bodyText);
|
|
9140
|
+
} catch (error) {
|
|
9141
|
+
const message = `response body is not valid JSON: ${String(error instanceof Error ? error.message : error).slice(0, 200)}`;
|
|
9142
|
+
return expected.map((assertion) => ({
|
|
9143
|
+
label: assertion.label || assertion.path,
|
|
9144
|
+
path: assertion.path,
|
|
9145
|
+
ok: false,
|
|
9146
|
+
exists: false,
|
|
9147
|
+
observed_type: "missing",
|
|
9148
|
+
errors: [message]
|
|
9149
|
+
}));
|
|
9150
|
+
}
|
|
9151
|
+
return expected.map((assertion) => evaluateHttpStatusBodyJsonAssertion(parsed, assertion));
|
|
9152
|
+
}
|
|
8969
9153
|
function compactProfileSetupSummaryText(value, limit = 160) {
|
|
8970
9154
|
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
8971
9155
|
if (!text) return void 0;
|
|
@@ -9620,6 +9804,46 @@ function validateRegexPatterns(patterns, label) {
|
|
|
9620
9804
|
}
|
|
9621
9805
|
}
|
|
9622
9806
|
}
|
|
9807
|
+
function normalizeHttpStatusBodyJsonAssertions(value, label) {
|
|
9808
|
+
if (value === void 0) return void 0;
|
|
9809
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
9810
|
+
if (!value.length) throw new Error(`${label} must not be empty.`);
|
|
9811
|
+
return value.map((item, index) => {
|
|
9812
|
+
const itemLabel = `${label}[${index}]`;
|
|
9813
|
+
if (typeof item === "string") {
|
|
9814
|
+
const path7 = stringValue5(item);
|
|
9815
|
+
if (!path7) throw new Error(`${itemLabel} path must not be empty.`);
|
|
9816
|
+
return { path: path7, exists: true };
|
|
9817
|
+
}
|
|
9818
|
+
if (!isRecord2(item)) throw new Error(`${itemLabel} must be an object or JSON path string.`);
|
|
9819
|
+
const path6 = stringFromOwn(item, "path", "json_path", "jsonPath", "key");
|
|
9820
|
+
if (!path6) throw new Error(`${itemLabel}.path is required.`);
|
|
9821
|
+
const assertion = {
|
|
9822
|
+
label: stringValue5(item.label),
|
|
9823
|
+
path: path6
|
|
9824
|
+
};
|
|
9825
|
+
const exists = booleanValue(valueFromOwn(item, "exists", "present"));
|
|
9826
|
+
if (exists !== void 0) assertion.exists = exists;
|
|
9827
|
+
const type = stringValue5(valueFromOwn(item, "type", "value_type", "valueType"));
|
|
9828
|
+
if (type !== void 0) {
|
|
9829
|
+
const allowedTypes = ["array", "boolean", "null", "number", "object", "string"];
|
|
9830
|
+
if (!allowedTypes.includes(type)) {
|
|
9831
|
+
throw new Error(`${itemLabel}.type must be one of ${allowedTypes.join(", ")}.`);
|
|
9832
|
+
}
|
|
9833
|
+
assertion.type = type;
|
|
9834
|
+
}
|
|
9835
|
+
const equalsValue = valueFromOwn(item, "equals", "expected", "expected_value", "expectedValue", "value");
|
|
9836
|
+
if (equalsValue !== void 0) assertion.equals = toJsonValue(equalsValue);
|
|
9837
|
+
const notEqualsValue = valueFromOwn(item, "not_equals", "notEquals", "forbidden", "forbidden_value", "forbiddenValue");
|
|
9838
|
+
if (notEqualsValue !== void 0) assertion.not_equals = toJsonValue(notEqualsValue);
|
|
9839
|
+
const containsValue = valueFromOwn(item, "contains", "includes", "contains_value", "containsValue", "include");
|
|
9840
|
+
if (containsValue !== void 0) assertion.contains = toJsonValue(containsValue);
|
|
9841
|
+
if (assertion.exists === void 0 && assertion.type === void 0 && !hasOwn(assertion, "equals") && !hasOwn(assertion, "not_equals") && !hasOwn(assertion, "contains")) {
|
|
9842
|
+
assertion.exists = true;
|
|
9843
|
+
}
|
|
9844
|
+
return assertion;
|
|
9845
|
+
});
|
|
9846
|
+
}
|
|
9623
9847
|
function isDialogCountCheckType(type) {
|
|
9624
9848
|
return type === "dialog_count_equals" || type === "dialog_accept_count_equals" || type === "dialog_dismiss_count_equals";
|
|
9625
9849
|
}
|
|
@@ -9719,6 +9943,10 @@ function normalizeCheck(input, index) {
|
|
|
9719
9943
|
`checks[${index}] body_not_patterns`
|
|
9720
9944
|
) : void 0;
|
|
9721
9945
|
if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
|
|
9946
|
+
const bodyJsonAssertions = isHttpStatusCheck ? normalizeHttpStatusBodyJsonAssertions(
|
|
9947
|
+
input.body_json_assertions ?? input.bodyJsonAssertions ?? input.json_body_assertions ?? input.jsonBodyAssertions ?? input.json_assertions ?? input.jsonAssertions ?? input.response_json_assertions ?? input.responseJsonAssertions,
|
|
9948
|
+
`checks[${index}] body_json_assertions`
|
|
9949
|
+
) : void 0;
|
|
9722
9950
|
if (isLinkStatusCheck) {
|
|
9723
9951
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
9724
9952
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -9744,6 +9972,7 @@ function normalizeCheck(input, index) {
|
|
|
9744
9972
|
body_contains: bodyContains,
|
|
9745
9973
|
body_not_contains: bodyNotContains,
|
|
9746
9974
|
body_not_patterns: bodyNotPatterns,
|
|
9975
|
+
body_json_assertions: bodyJsonAssertions,
|
|
9747
9976
|
expected_texts: expectedTexts,
|
|
9748
9977
|
link_selector: stringValue5(input.link_selector) || stringValue5(input.linkSelector),
|
|
9749
9978
|
source_selector: stringValue5(input.source_selector) || stringValue5(input.sourceSelector),
|
|
@@ -9950,6 +10179,38 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
9950
10179
|
const observed = isRecord2(result.body_not_patterns) ? result.body_not_patterns : {};
|
|
9951
10180
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
9952
10181
|
}
|
|
10182
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
10183
|
+
const expected = check.body_json_assertions?.filter((assertion) => assertion.path) ?? [];
|
|
10184
|
+
if (!expected.length) return [];
|
|
10185
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
10186
|
+
return expected.map((assertion) => ({
|
|
10187
|
+
label: assertion.label || assertion.path,
|
|
10188
|
+
path: assertion.path,
|
|
10189
|
+
ok: false,
|
|
10190
|
+
exists: false,
|
|
10191
|
+
observed_type: "missing",
|
|
10192
|
+
errors: ["body_json_assertions evidence missing"]
|
|
10193
|
+
}));
|
|
10194
|
+
}
|
|
10195
|
+
return result.body_json_assertions.filter((assertion) => isRecord2(assertion) && assertion.ok !== true).map((assertion) => ({
|
|
10196
|
+
label: stringValue5(assertion.label) || stringValue5(assertion.path) || "json assertion",
|
|
10197
|
+
path: stringValue5(assertion.path) || "",
|
|
10198
|
+
ok: false,
|
|
10199
|
+
exists: assertion.exists === true,
|
|
10200
|
+
observed: hasOwn(assertion, "observed") ? toJsonValue(assertion.observed) : void 0,
|
|
10201
|
+
observed_sample: hasOwn(assertion, "observed_sample") ? toJsonValue(assertion.observed_sample) : void 0,
|
|
10202
|
+
observed_length: numberValue3(assertion.observed_length),
|
|
10203
|
+
observed_key_count: numberValue3(assertion.observed_key_count),
|
|
10204
|
+
observed_omitted_count: numberValue3(assertion.observed_omitted_count),
|
|
10205
|
+
observed_type: stringValue5(assertion.observed_type) || "missing",
|
|
10206
|
+
expected_exists: booleanValue(assertion.expected_exists),
|
|
10207
|
+
equals: hasOwn(assertion, "equals") ? toJsonValue(assertion.equals) : void 0,
|
|
10208
|
+
not_equals: hasOwn(assertion, "not_equals") ? toJsonValue(assertion.not_equals) : void 0,
|
|
10209
|
+
contains: hasOwn(assertion, "contains") ? toJsonValue(assertion.contains) : void 0,
|
|
10210
|
+
type: stringValue5(assertion.type),
|
|
10211
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : void 0
|
|
10212
|
+
}));
|
|
10213
|
+
}
|
|
9953
10214
|
function linkStatusResultOk(result, check) {
|
|
9954
10215
|
const status = numberValue3(result.status);
|
|
9955
10216
|
if (!httpStatusIsAllowed(status, check)) return false;
|
|
@@ -9967,6 +10228,7 @@ function linkStatusResultOk(result, check) {
|
|
|
9967
10228
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
9968
10229
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
9969
10230
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
10231
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
9970
10232
|
return true;
|
|
9971
10233
|
}
|
|
9972
10234
|
function responseHeader(response, name) {
|
|
@@ -10035,7 +10297,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10035
10297
|
statusText = typeof response.statusText === "string" ? response.statusText : "";
|
|
10036
10298
|
result.content_type = responseHeader(response, "content-type");
|
|
10037
10299
|
result.content_length = responseContentLength(response);
|
|
10038
|
-
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);
|
|
10300
|
+
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);
|
|
10039
10301
|
if (shouldReadBody && method !== "HEAD") {
|
|
10040
10302
|
const body = await responseBodyText(response);
|
|
10041
10303
|
result.bytes = body.bytes;
|
|
@@ -10048,6 +10310,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10048
10310
|
if (check.body_not_patterns?.length) {
|
|
10049
10311
|
result.body_not_patterns = Object.fromEntries(check.body_not_patterns.filter(Boolean).map((pattern) => [pattern, new RegExp(pattern).test(body.text)]));
|
|
10050
10312
|
}
|
|
10313
|
+
if (check.body_json_assertions?.length) {
|
|
10314
|
+
result.body_json_assertions = evaluateHttpStatusBodyJsonAssertions(body.text, check.body_json_assertions);
|
|
10315
|
+
}
|
|
10051
10316
|
}
|
|
10052
10317
|
} catch (caught) {
|
|
10053
10318
|
error = String(caught instanceof Error ? caught.message : caught).slice(0, 500);
|
|
@@ -10056,6 +10321,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10056
10321
|
const bodyContainsMissing = httpStatusBodyContainsFailures(result, check);
|
|
10057
10322
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(result, check);
|
|
10058
10323
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(result, check);
|
|
10324
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(result, check);
|
|
10059
10325
|
const ok = !error && linkStatusResultOk(result, check);
|
|
10060
10326
|
return {
|
|
10061
10327
|
index,
|
|
@@ -10074,7 +10340,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
10074
10340
|
body_not_contains: isRecord2(result.body_not_contains) ? Object.fromEntries(Object.entries(result.body_not_contains).map(([key, value]) => [key, value === true])) : null,
|
|
10075
10341
|
body_not_contains_found: bodyNotContainsFound,
|
|
10076
10342
|
body_not_patterns: isRecord2(result.body_not_patterns) ? Object.fromEntries(Object.entries(result.body_not_patterns).map(([key, value]) => [key, value === true])) : null,
|
|
10077
|
-
body_not_patterns_found: bodyNotPatternsFound
|
|
10343
|
+
body_not_patterns_found: bodyNotPatternsFound,
|
|
10344
|
+
body_json_assertions: Array.isArray(result.body_json_assertions) ? result.body_json_assertions : null,
|
|
10345
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed
|
|
10078
10346
|
};
|
|
10079
10347
|
}
|
|
10080
10348
|
async function preflightRiddleProofProfileHttpStatusChecks(profile, options = {}) {
|
|
@@ -10119,6 +10387,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10119
10387
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
10120
10388
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
10121
10389
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
10390
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
10122
10391
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
10123
10392
|
failures.push({
|
|
10124
10393
|
code: "http_status_failed",
|
|
@@ -10137,6 +10406,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10137
10406
|
body_not_contains_found: bodyNotContainsFound,
|
|
10138
10407
|
body_not_patterns: check.body_not_patterns ?? null,
|
|
10139
10408
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10409
|
+
body_json_assertions: check.body_json_assertions ?? null,
|
|
10410
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
10140
10411
|
body_sample: stringValue5(statusEvidence.body_sample) ?? null
|
|
10141
10412
|
});
|
|
10142
10413
|
}
|
|
@@ -10158,6 +10429,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
10158
10429
|
body_not_contains_found: bodyNotContainsFound,
|
|
10159
10430
|
body_not_patterns: isRecord2(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
|
|
10160
10431
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10432
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? toJsonValue(statusEvidence.body_json_assertions) : null,
|
|
10433
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
10161
10434
|
body_sample: stringValue5(statusEvidence.body_sample) ?? null,
|
|
10162
10435
|
failures
|
|
10163
10436
|
};
|
|
@@ -10794,6 +11067,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
10794
11067
|
body_contains: check.body_contains ?? [],
|
|
10795
11068
|
body_not_contains: check.body_not_contains ?? [],
|
|
10796
11069
|
body_not_patterns: check.body_not_patterns ?? [],
|
|
11070
|
+
body_json_assertions: toJsonValue(check.body_json_assertions ?? []),
|
|
10797
11071
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
10798
11072
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue5(summary.viewport) ?? null, failure })) : [])
|
|
10799
11073
|
},
|
|
@@ -11537,6 +11811,40 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
11537
11811
|
: {};
|
|
11538
11812
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
11539
11813
|
}
|
|
11814
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
11815
|
+
const expected = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
11816
|
+
if (!expected.length) return [];
|
|
11817
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
11818
|
+
return expected.map((assertion) => ({
|
|
11819
|
+
label: assertion.label || assertion.path,
|
|
11820
|
+
path: assertion.path,
|
|
11821
|
+
ok: false,
|
|
11822
|
+
exists: false,
|
|
11823
|
+
observed_type: "missing",
|
|
11824
|
+
errors: ["body_json_assertions evidence missing"],
|
|
11825
|
+
}));
|
|
11826
|
+
}
|
|
11827
|
+
return result.body_json_assertions
|
|
11828
|
+
.filter((assertion) => assertion && typeof assertion === "object" && assertion.ok !== true)
|
|
11829
|
+
.map((assertion) => ({
|
|
11830
|
+
label: typeof assertion.label === "string" && assertion.label ? assertion.label : typeof assertion.path === "string" && assertion.path ? assertion.path : "json assertion",
|
|
11831
|
+
path: typeof assertion.path === "string" ? assertion.path : "",
|
|
11832
|
+
ok: false,
|
|
11833
|
+
exists: assertion.exists === true,
|
|
11834
|
+
observed: Object.hasOwn(assertion, "observed") ? assertion.observed : undefined,
|
|
11835
|
+
observed_sample: Object.hasOwn(assertion, "observed_sample") ? assertion.observed_sample : undefined,
|
|
11836
|
+
observed_length: typeof assertion.observed_length === "number" && Number.isFinite(assertion.observed_length) ? assertion.observed_length : undefined,
|
|
11837
|
+
observed_key_count: typeof assertion.observed_key_count === "number" && Number.isFinite(assertion.observed_key_count) ? assertion.observed_key_count : undefined,
|
|
11838
|
+
observed_omitted_count: typeof assertion.observed_omitted_count === "number" && Number.isFinite(assertion.observed_omitted_count) ? assertion.observed_omitted_count : undefined,
|
|
11839
|
+
observed_type: typeof assertion.observed_type === "string" && assertion.observed_type ? assertion.observed_type : "missing",
|
|
11840
|
+
expected_exists: typeof assertion.expected_exists === "boolean" ? assertion.expected_exists : undefined,
|
|
11841
|
+
equals: Object.hasOwn(assertion, "equals") ? assertion.equals : undefined,
|
|
11842
|
+
not_equals: Object.hasOwn(assertion, "not_equals") ? assertion.not_equals : undefined,
|
|
11843
|
+
contains: Object.hasOwn(assertion, "contains") ? assertion.contains : undefined,
|
|
11844
|
+
type: typeof assertion.type === "string" ? assertion.type : undefined,
|
|
11845
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : undefined,
|
|
11846
|
+
}));
|
|
11847
|
+
}
|
|
11540
11848
|
function linkStatusResultOk(result, check) {
|
|
11541
11849
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
11542
11850
|
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
@@ -11554,6 +11862,7 @@ function linkStatusResultOk(result, check) {
|
|
|
11554
11862
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
11555
11863
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
11556
11864
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
11865
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
11557
11866
|
return true;
|
|
11558
11867
|
}
|
|
11559
11868
|
function summarizeHttpStatusEvidence(viewport, check) {
|
|
@@ -11574,6 +11883,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
11574
11883
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
11575
11884
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
11576
11885
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
11886
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
11577
11887
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
11578
11888
|
failures.push({
|
|
11579
11889
|
code: "http_status_failed",
|
|
@@ -11592,6 +11902,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
11592
11902
|
body_not_contains_found: bodyNotContainsFound,
|
|
11593
11903
|
body_not_patterns: Array.isArray(check.body_not_patterns) ? check.body_not_patterns : null,
|
|
11594
11904
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
11905
|
+
body_json_assertions: Array.isArray(check.body_json_assertions) ? check.body_json_assertions : null,
|
|
11906
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
11595
11907
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
11596
11908
|
});
|
|
11597
11909
|
}
|
|
@@ -11619,6 +11931,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
11619
11931
|
? statusEvidence.body_not_patterns
|
|
11620
11932
|
: null,
|
|
11621
11933
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
11934
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? statusEvidence.body_json_assertions : null,
|
|
11935
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
11622
11936
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
11623
11937
|
failures,
|
|
11624
11938
|
};
|
|
@@ -13721,6 +14035,187 @@ function linkProbeResponseFields(response, method) {
|
|
|
13721
14035
|
content_length: contentLength,
|
|
13722
14036
|
};
|
|
13723
14037
|
}
|
|
14038
|
+
function jsonProbeValueType(value) {
|
|
14039
|
+
if (value === null) return "null";
|
|
14040
|
+
if (Array.isArray(value)) return "array";
|
|
14041
|
+
if (typeof value === "boolean") return "boolean";
|
|
14042
|
+
if (typeof value === "number") return "number";
|
|
14043
|
+
if (typeof value === "string") return "string";
|
|
14044
|
+
return "object";
|
|
14045
|
+
}
|
|
14046
|
+
function compactJsonProbeSample(value, depth) {
|
|
14047
|
+
const level = typeof depth === "number" ? depth : 0;
|
|
14048
|
+
if (typeof value === "string") return value.length > 240 ? value.slice(0, 237) + "..." : value;
|
|
14049
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
14050
|
+
if (Array.isArray(value)) {
|
|
14051
|
+
if (level >= 2) return "[array:" + value.length + "]";
|
|
14052
|
+
return value.slice(0, 3).map((item) => compactJsonProbeSample(item, level + 1));
|
|
14053
|
+
}
|
|
14054
|
+
if (value && typeof value === "object") {
|
|
14055
|
+
const entries = Object.entries(value);
|
|
14056
|
+
if (level >= 2) return "[object:" + entries.length + " keys]";
|
|
14057
|
+
return Object.fromEntries(entries.slice(0, 8).map(([key, child]) => [key, compactJsonProbeSample(child, level + 1)]));
|
|
14058
|
+
}
|
|
14059
|
+
return String(value);
|
|
14060
|
+
}
|
|
14061
|
+
function attachJsonProbeObservedValue(result, value) {
|
|
14062
|
+
const type = jsonProbeValueType(value);
|
|
14063
|
+
if (type === "array" && Array.isArray(value)) {
|
|
14064
|
+
result.observed_length = value.length;
|
|
14065
|
+
result.observed_omitted_count = Math.max(0, value.length - 3);
|
|
14066
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
14067
|
+
return;
|
|
14068
|
+
}
|
|
14069
|
+
if (type === "object" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
14070
|
+
const keyCount = Object.keys(value).length;
|
|
14071
|
+
result.observed_key_count = keyCount;
|
|
14072
|
+
result.observed_omitted_count = Math.max(0, keyCount - 8);
|
|
14073
|
+
result.observed_sample = compactJsonProbeSample(value, 0);
|
|
14074
|
+
return;
|
|
14075
|
+
}
|
|
14076
|
+
result.observed = value;
|
|
14077
|
+
}
|
|
14078
|
+
function jsonProbeDeepEqual(left, right) {
|
|
14079
|
+
if (left === right) return true;
|
|
14080
|
+
if (typeof left !== typeof right) return false;
|
|
14081
|
+
if (left === null || right === null) return left === right;
|
|
14082
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
14083
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
14084
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
14085
|
+
return left.every((item, index) => jsonProbeDeepEqual(item, right[index]));
|
|
14086
|
+
}
|
|
14087
|
+
const leftKeys = Object.keys(left).sort();
|
|
14088
|
+
const rightKeys = Object.keys(right).sort();
|
|
14089
|
+
if (!jsonProbeDeepEqual(leftKeys, rightKeys)) return false;
|
|
14090
|
+
return leftKeys.every((key) => jsonProbeDeepEqual(left[key], right[key]));
|
|
14091
|
+
}
|
|
14092
|
+
function jsonProbeContains(observed, expected) {
|
|
14093
|
+
if (typeof observed === "string" && typeof expected === "string") return observed.includes(expected);
|
|
14094
|
+
if (Array.isArray(observed)) return observed.some((item) => jsonProbeDeepEqual(item, expected));
|
|
14095
|
+
if (observed && expected && typeof observed === "object" && typeof expected === "object" && !Array.isArray(observed) && !Array.isArray(expected)) {
|
|
14096
|
+
return Object.entries(expected).every(([key, value]) => Object.hasOwn(observed, key) && jsonProbeDeepEqual(observed[key], value));
|
|
14097
|
+
}
|
|
14098
|
+
return false;
|
|
14099
|
+
}
|
|
14100
|
+
function parseJsonProbePathSegments(path) {
|
|
14101
|
+
let input = String(path || "").trim();
|
|
14102
|
+
if (!input) throw new Error("path is empty");
|
|
14103
|
+
if (input === "$") return [];
|
|
14104
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
14105
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
14106
|
+
const segments = [];
|
|
14107
|
+
let token = "";
|
|
14108
|
+
const pushToken = () => {
|
|
14109
|
+
if (!token) return;
|
|
14110
|
+
segments.push(token);
|
|
14111
|
+
token = "";
|
|
14112
|
+
};
|
|
14113
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
14114
|
+
const char = input[index];
|
|
14115
|
+
if (char === ".") {
|
|
14116
|
+
pushToken();
|
|
14117
|
+
continue;
|
|
14118
|
+
}
|
|
14119
|
+
if (char !== "[") {
|
|
14120
|
+
token += char;
|
|
14121
|
+
continue;
|
|
14122
|
+
}
|
|
14123
|
+
pushToken();
|
|
14124
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
14125
|
+
if (closeIndex === -1) throw new Error("unterminated bracket at " + index);
|
|
14126
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
14127
|
+
if (!bracket) throw new Error("empty bracket at " + index);
|
|
14128
|
+
if (/^\d+$/.test(bracket)) {
|
|
14129
|
+
segments.push(Number(bracket));
|
|
14130
|
+
} else if ((bracket.startsWith('"') && bracket.endsWith('"')) || (bracket.startsWith("'") && bracket.endsWith("'"))) {
|
|
14131
|
+
const quoted = bracket.startsWith("'")
|
|
14132
|
+
? '"' + bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"'
|
|
14133
|
+
: bracket;
|
|
14134
|
+
segments.push(String(JSON.parse(quoted)));
|
|
14135
|
+
} else {
|
|
14136
|
+
segments.push(bracket);
|
|
14137
|
+
}
|
|
14138
|
+
index = closeIndex;
|
|
14139
|
+
}
|
|
14140
|
+
pushToken();
|
|
14141
|
+
return segments;
|
|
14142
|
+
}
|
|
14143
|
+
function resolveJsonProbePath(root, path) {
|
|
14144
|
+
let segments;
|
|
14145
|
+
try {
|
|
14146
|
+
segments = parseJsonProbePathSegments(path);
|
|
14147
|
+
} catch (error) {
|
|
14148
|
+
return { exists: false, error: String(error && error.message ? error.message : error) };
|
|
14149
|
+
}
|
|
14150
|
+
let current = root;
|
|
14151
|
+
for (const segment of segments) {
|
|
14152
|
+
if (typeof segment === "number") {
|
|
14153
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
14154
|
+
current = current[segment];
|
|
14155
|
+
continue;
|
|
14156
|
+
}
|
|
14157
|
+
if (!current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment)) {
|
|
14158
|
+
return { exists: false };
|
|
14159
|
+
}
|
|
14160
|
+
current = current[segment];
|
|
14161
|
+
}
|
|
14162
|
+
return { exists: true, value: current };
|
|
14163
|
+
}
|
|
14164
|
+
function evaluateJsonProbeAssertion(root, assertion) {
|
|
14165
|
+
const resolved = resolveJsonProbePath(root, assertion.path);
|
|
14166
|
+
const errors = [];
|
|
14167
|
+
const result = {
|
|
14168
|
+
label: assertion.label || assertion.path,
|
|
14169
|
+
path: assertion.path,
|
|
14170
|
+
ok: true,
|
|
14171
|
+
exists: resolved.exists,
|
|
14172
|
+
observed_type: resolved.exists ? jsonProbeValueType(resolved.value) : "missing",
|
|
14173
|
+
};
|
|
14174
|
+
if (resolved.exists) attachJsonProbeObservedValue(result, resolved.value);
|
|
14175
|
+
if (resolved.error) errors.push(resolved.error);
|
|
14176
|
+
if (Object.hasOwn(assertion, "exists")) {
|
|
14177
|
+
result.expected_exists = assertion.exists;
|
|
14178
|
+
if (resolved.exists !== assertion.exists) errors.push("expected exists=" + assertion.exists);
|
|
14179
|
+
}
|
|
14180
|
+
if (Object.hasOwn(assertion, "type")) {
|
|
14181
|
+
result.type = assertion.type;
|
|
14182
|
+
if (!resolved.exists || jsonProbeValueType(resolved.value) !== assertion.type) errors.push("expected type " + assertion.type);
|
|
14183
|
+
}
|
|
14184
|
+
if (Object.hasOwn(assertion, "equals")) {
|
|
14185
|
+
result.equals = assertion.equals;
|
|
14186
|
+
if (!resolved.exists || !jsonProbeDeepEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
14187
|
+
}
|
|
14188
|
+
if (Object.hasOwn(assertion, "not_equals")) {
|
|
14189
|
+
result.not_equals = assertion.not_equals;
|
|
14190
|
+
if (resolved.exists && jsonProbeDeepEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
14191
|
+
}
|
|
14192
|
+
if (Object.hasOwn(assertion, "contains")) {
|
|
14193
|
+
result.contains = assertion.contains;
|
|
14194
|
+
if (!resolved.exists || !jsonProbeContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
14195
|
+
}
|
|
14196
|
+
result.ok = errors.length === 0;
|
|
14197
|
+
if (errors.length) result.errors = errors;
|
|
14198
|
+
return result;
|
|
14199
|
+
}
|
|
14200
|
+
function evaluateJsonProbeAssertions(text, assertions) {
|
|
14201
|
+
const expected = Array.isArray(assertions) ? assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
14202
|
+
if (!expected.length) return [];
|
|
14203
|
+
let parsed;
|
|
14204
|
+
try {
|
|
14205
|
+
parsed = JSON.parse(text);
|
|
14206
|
+
} catch (error) {
|
|
14207
|
+
const message = "response body is not valid JSON: " + String(error && error.message ? error.message : error).slice(0, 200);
|
|
14208
|
+
return expected.map((assertion) => ({
|
|
14209
|
+
label: assertion.label || assertion.path,
|
|
14210
|
+
path: assertion.path,
|
|
14211
|
+
ok: false,
|
|
14212
|
+
exists: false,
|
|
14213
|
+
observed_type: "missing",
|
|
14214
|
+
errors: [message],
|
|
14215
|
+
}));
|
|
14216
|
+
}
|
|
14217
|
+
return expected.map((assertion) => evaluateJsonProbeAssertion(parsed, assertion));
|
|
14218
|
+
}
|
|
13724
14219
|
async function collectHttpStatus(check) {
|
|
13725
14220
|
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
13726
14221
|
const method = httpStatusMethod(check);
|
|
@@ -13737,6 +14232,7 @@ async function collectHttpStatus(check) {
|
|
|
13737
14232
|
const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
13738
14233
|
const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
|
|
13739
14234
|
const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
|
|
14235
|
+
const bodyJsonAssertions = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
13740
14236
|
const options = {
|
|
13741
14237
|
method,
|
|
13742
14238
|
redirect: "follow",
|
|
@@ -13762,17 +14258,18 @@ async function collectHttpStatus(check) {
|
|
|
13762
14258
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
13763
14259
|
result.url = url;
|
|
13764
14260
|
result.status_text = response.statusText || "";
|
|
13765
|
-
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;
|
|
14261
|
+
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;
|
|
13766
14262
|
if (shouldReadBody) {
|
|
13767
14263
|
try {
|
|
13768
14264
|
const buffer = await response.arrayBuffer();
|
|
13769
14265
|
result.bytes = buffer.byteLength;
|
|
13770
|
-
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
|
|
14266
|
+
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length || bodyJsonAssertions.length) {
|
|
13771
14267
|
const text = new TextDecoder().decode(buffer);
|
|
13772
14268
|
result.body_sample = text.slice(0, 1000);
|
|
13773
14269
|
if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
|
|
13774
14270
|
if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
|
|
13775
14271
|
if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
|
|
14272
|
+
if (bodyJsonAssertions.length) result.body_json_assertions = evaluateJsonProbeAssertions(text, bodyJsonAssertions);
|
|
13776
14273
|
}
|
|
13777
14274
|
} catch (error) {
|
|
13778
14275
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -13785,6 +14282,7 @@ async function collectHttpStatus(check) {
|
|
|
13785
14282
|
&& (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
|
|
13786
14283
|
&& (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
|
|
13787
14284
|
&& (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
|
|
14285
|
+
&& (!bodyJsonAssertions.length || (Array.isArray(result.body_json_assertions) && result.body_json_assertions.every((assertion) => assertion.ok === true)))
|
|
13788
14286
|
&& !result.error;
|
|
13789
14287
|
return result;
|
|
13790
14288
|
} catch (error) {
|