@riddledc/riddle-proof 0.7.126 → 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 +24 -2
- package/dist/{chunk-55KDZEB3.js → chunk-JLINSUKO.js} +431 -4
- package/dist/cli.cjs +449 -5
- package/dist/cli.js +19 -2
- package/dist/index.cjs +431 -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 +431 -4
- package/dist/profile.d.cts +28 -1
- package/dist/profile.d.ts +28 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -7076,6 +7076,9 @@ function valueFromOwn(input, ...keys) {
|
|
|
7076
7076
|
function numberValue(value) {
|
|
7077
7077
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7078
7078
|
}
|
|
7079
|
+
function booleanValue(value) {
|
|
7080
|
+
return typeof value === "boolean" ? value : void 0;
|
|
7081
|
+
}
|
|
7079
7082
|
function horizontalBoundsOverflowPx(value) {
|
|
7080
7083
|
if (!isRecord(value)) return 0;
|
|
7081
7084
|
let max = maxPositiveNumber(
|
|
@@ -7170,6 +7173,156 @@ function toJsonValue(value) {
|
|
|
7170
7173
|
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, toJsonValue(child)]));
|
|
7171
7174
|
return String(value);
|
|
7172
7175
|
}
|
|
7176
|
+
function jsonValueType(value) {
|
|
7177
|
+
if (value === null) return "null";
|
|
7178
|
+
if (Array.isArray(value)) return "array";
|
|
7179
|
+
if (typeof value === "boolean") return "boolean";
|
|
7180
|
+
if (typeof value === "number") return "number";
|
|
7181
|
+
if (typeof value === "string") return "string";
|
|
7182
|
+
return "object";
|
|
7183
|
+
}
|
|
7184
|
+
function deepJsonEqual(left, right) {
|
|
7185
|
+
if (left === right) return true;
|
|
7186
|
+
if (typeof left !== typeof right) return false;
|
|
7187
|
+
if (left === null || right === null) return left === right;
|
|
7188
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
7189
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
7190
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
7191
|
+
return left.every((item, index) => deepJsonEqual(item, right[index]));
|
|
7192
|
+
}
|
|
7193
|
+
if (!isRecord(left) || !isRecord(right)) return false;
|
|
7194
|
+
const leftKeys = Object.keys(left).sort();
|
|
7195
|
+
const rightKeys = Object.keys(right).sort();
|
|
7196
|
+
if (!deepJsonEqual(leftKeys, rightKeys)) return false;
|
|
7197
|
+
return leftKeys.every((key) => deepJsonEqual(left[key], right[key]));
|
|
7198
|
+
}
|
|
7199
|
+
function jsonContains(observed, expected) {
|
|
7200
|
+
if (typeof observed === "string" && typeof expected === "string") {
|
|
7201
|
+
return observed.includes(expected);
|
|
7202
|
+
}
|
|
7203
|
+
if (Array.isArray(observed)) {
|
|
7204
|
+
return observed.some((item) => deepJsonEqual(item, expected));
|
|
7205
|
+
}
|
|
7206
|
+
if (isRecord(observed) && isRecord(expected)) {
|
|
7207
|
+
return Object.entries(expected).every(([key, value]) => hasOwn(observed, key) && deepJsonEqual(observed[key], value));
|
|
7208
|
+
}
|
|
7209
|
+
return false;
|
|
7210
|
+
}
|
|
7211
|
+
function parseJsonPathSegments(path7) {
|
|
7212
|
+
let input = path7.trim();
|
|
7213
|
+
if (!input) throw new Error("path is empty");
|
|
7214
|
+
if (input === "$") return [];
|
|
7215
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
7216
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
7217
|
+
const segments = [];
|
|
7218
|
+
let token = "";
|
|
7219
|
+
const pushToken = () => {
|
|
7220
|
+
if (!token) return;
|
|
7221
|
+
segments.push(token);
|
|
7222
|
+
token = "";
|
|
7223
|
+
};
|
|
7224
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
7225
|
+
const char = input[index];
|
|
7226
|
+
if (char === ".") {
|
|
7227
|
+
pushToken();
|
|
7228
|
+
continue;
|
|
7229
|
+
}
|
|
7230
|
+
if (char !== "[") {
|
|
7231
|
+
token += char;
|
|
7232
|
+
continue;
|
|
7233
|
+
}
|
|
7234
|
+
pushToken();
|
|
7235
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
7236
|
+
if (closeIndex === -1) throw new Error(`unterminated bracket at ${index}`);
|
|
7237
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
7238
|
+
if (!bracket) throw new Error(`empty bracket at ${index}`);
|
|
7239
|
+
if (/^\d+$/.test(bracket)) {
|
|
7240
|
+
segments.push(Number(bracket));
|
|
7241
|
+
} else if (bracket.startsWith('"') && bracket.endsWith('"') || bracket.startsWith("'") && bracket.endsWith("'")) {
|
|
7242
|
+
const quoted = bracket.startsWith("'") ? `"${bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : bracket;
|
|
7243
|
+
segments.push(String(JSON.parse(quoted)));
|
|
7244
|
+
} else {
|
|
7245
|
+
segments.push(bracket);
|
|
7246
|
+
}
|
|
7247
|
+
index = closeIndex;
|
|
7248
|
+
}
|
|
7249
|
+
pushToken();
|
|
7250
|
+
return segments;
|
|
7251
|
+
}
|
|
7252
|
+
function resolveJsonPath(root, path7) {
|
|
7253
|
+
let segments;
|
|
7254
|
+
try {
|
|
7255
|
+
segments = parseJsonPathSegments(path7);
|
|
7256
|
+
} catch (error) {
|
|
7257
|
+
return { exists: false, error: String(error instanceof Error ? error.message : error) };
|
|
7258
|
+
}
|
|
7259
|
+
let current = root;
|
|
7260
|
+
for (const segment of segments) {
|
|
7261
|
+
if (typeof segment === "number") {
|
|
7262
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
7263
|
+
current = current[segment];
|
|
7264
|
+
continue;
|
|
7265
|
+
}
|
|
7266
|
+
if (!isRecord(current) || !hasOwn(current, segment)) return { exists: false };
|
|
7267
|
+
current = current[segment];
|
|
7268
|
+
}
|
|
7269
|
+
return { exists: true, value: current };
|
|
7270
|
+
}
|
|
7271
|
+
function evaluateHttpStatusBodyJsonAssertion(root, assertion) {
|
|
7272
|
+
const resolved = resolveJsonPath(root, assertion.path);
|
|
7273
|
+
const errors = [];
|
|
7274
|
+
const result = {
|
|
7275
|
+
label: assertion.label || assertion.path,
|
|
7276
|
+
path: assertion.path,
|
|
7277
|
+
ok: true,
|
|
7278
|
+
exists: resolved.exists,
|
|
7279
|
+
observed_type: resolved.exists ? jsonValueType(resolved.value) : "missing"
|
|
7280
|
+
};
|
|
7281
|
+
if (resolved.exists) result.observed = toJsonValue(resolved.value);
|
|
7282
|
+
if (resolved.error) errors.push(resolved.error);
|
|
7283
|
+
if (hasOwn(assertion, "exists")) {
|
|
7284
|
+
result.expected_exists = assertion.exists;
|
|
7285
|
+
if (resolved.exists !== assertion.exists) errors.push(`expected exists=${assertion.exists}`);
|
|
7286
|
+
}
|
|
7287
|
+
if (hasOwn(assertion, "type")) {
|
|
7288
|
+
result.type = assertion.type;
|
|
7289
|
+
if (!resolved.exists || jsonValueType(resolved.value) !== assertion.type) errors.push(`expected type ${assertion.type}`);
|
|
7290
|
+
}
|
|
7291
|
+
if (hasOwn(assertion, "equals")) {
|
|
7292
|
+
result.equals = assertion.equals;
|
|
7293
|
+
if (!resolved.exists || !deepJsonEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
7294
|
+
}
|
|
7295
|
+
if (hasOwn(assertion, "not_equals")) {
|
|
7296
|
+
result.not_equals = assertion.not_equals;
|
|
7297
|
+
if (resolved.exists && deepJsonEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
7298
|
+
}
|
|
7299
|
+
if (hasOwn(assertion, "contains")) {
|
|
7300
|
+
result.contains = assertion.contains;
|
|
7301
|
+
if (!resolved.exists || !jsonContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
7302
|
+
}
|
|
7303
|
+
result.ok = errors.length === 0;
|
|
7304
|
+
if (errors.length) result.errors = errors;
|
|
7305
|
+
return result;
|
|
7306
|
+
}
|
|
7307
|
+
function evaluateHttpStatusBodyJsonAssertions(bodyText, assertions) {
|
|
7308
|
+
const expected = assertions?.filter((assertion) => assertion.path) ?? [];
|
|
7309
|
+
if (!expected.length) return [];
|
|
7310
|
+
let parsed;
|
|
7311
|
+
try {
|
|
7312
|
+
parsed = JSON.parse(bodyText);
|
|
7313
|
+
} catch (error) {
|
|
7314
|
+
const message = `response body is not valid JSON: ${String(error instanceof Error ? error.message : error).slice(0, 200)}`;
|
|
7315
|
+
return expected.map((assertion) => ({
|
|
7316
|
+
label: assertion.label || assertion.path,
|
|
7317
|
+
path: assertion.path,
|
|
7318
|
+
ok: false,
|
|
7319
|
+
exists: false,
|
|
7320
|
+
observed_type: "missing",
|
|
7321
|
+
errors: [message]
|
|
7322
|
+
}));
|
|
7323
|
+
}
|
|
7324
|
+
return expected.map((assertion) => evaluateHttpStatusBodyJsonAssertion(parsed, assertion));
|
|
7325
|
+
}
|
|
7173
7326
|
function compactProfileSetupSummaryText(value, limit = 160) {
|
|
7174
7327
|
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
7175
7328
|
if (!text) return void 0;
|
|
@@ -7824,6 +7977,46 @@ function validateRegexPatterns(patterns, label) {
|
|
|
7824
7977
|
}
|
|
7825
7978
|
}
|
|
7826
7979
|
}
|
|
7980
|
+
function normalizeHttpStatusBodyJsonAssertions(value, label) {
|
|
7981
|
+
if (value === void 0) return void 0;
|
|
7982
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
7983
|
+
if (!value.length) throw new Error(`${label} must not be empty.`);
|
|
7984
|
+
return value.map((item, index) => {
|
|
7985
|
+
const itemLabel = `${label}[${index}]`;
|
|
7986
|
+
if (typeof item === "string") {
|
|
7987
|
+
const path8 = stringValue2(item);
|
|
7988
|
+
if (!path8) throw new Error(`${itemLabel} path must not be empty.`);
|
|
7989
|
+
return { path: path8, exists: true };
|
|
7990
|
+
}
|
|
7991
|
+
if (!isRecord(item)) throw new Error(`${itemLabel} must be an object or JSON path string.`);
|
|
7992
|
+
const path7 = stringFromOwn(item, "path", "json_path", "jsonPath", "key");
|
|
7993
|
+
if (!path7) throw new Error(`${itemLabel}.path is required.`);
|
|
7994
|
+
const assertion = {
|
|
7995
|
+
label: stringValue2(item.label),
|
|
7996
|
+
path: path7
|
|
7997
|
+
};
|
|
7998
|
+
const exists = booleanValue(valueFromOwn(item, "exists", "present"));
|
|
7999
|
+
if (exists !== void 0) assertion.exists = exists;
|
|
8000
|
+
const type = stringValue2(valueFromOwn(item, "type", "value_type", "valueType"));
|
|
8001
|
+
if (type !== void 0) {
|
|
8002
|
+
const allowedTypes = ["array", "boolean", "null", "number", "object", "string"];
|
|
8003
|
+
if (!allowedTypes.includes(type)) {
|
|
8004
|
+
throw new Error(`${itemLabel}.type must be one of ${allowedTypes.join(", ")}.`);
|
|
8005
|
+
}
|
|
8006
|
+
assertion.type = type;
|
|
8007
|
+
}
|
|
8008
|
+
const equalsValue = valueFromOwn(item, "equals", "expected", "expected_value", "expectedValue", "value");
|
|
8009
|
+
if (equalsValue !== void 0) assertion.equals = toJsonValue(equalsValue);
|
|
8010
|
+
const notEqualsValue = valueFromOwn(item, "not_equals", "notEquals", "forbidden", "forbidden_value", "forbiddenValue");
|
|
8011
|
+
if (notEqualsValue !== void 0) assertion.not_equals = toJsonValue(notEqualsValue);
|
|
8012
|
+
const containsValue = valueFromOwn(item, "contains", "includes", "contains_value", "containsValue", "include");
|
|
8013
|
+
if (containsValue !== void 0) assertion.contains = toJsonValue(containsValue);
|
|
8014
|
+
if (assertion.exists === void 0 && assertion.type === void 0 && !hasOwn(assertion, "equals") && !hasOwn(assertion, "not_equals") && !hasOwn(assertion, "contains")) {
|
|
8015
|
+
assertion.exists = true;
|
|
8016
|
+
}
|
|
8017
|
+
return assertion;
|
|
8018
|
+
});
|
|
8019
|
+
}
|
|
7827
8020
|
function isDialogCountCheckType(type) {
|
|
7828
8021
|
return type === "dialog_count_equals" || type === "dialog_accept_count_equals" || type === "dialog_dismiss_count_equals";
|
|
7829
8022
|
}
|
|
@@ -7923,6 +8116,10 @@ function normalizeCheck(input, index) {
|
|
|
7923
8116
|
`checks[${index}] body_not_patterns`
|
|
7924
8117
|
) : void 0;
|
|
7925
8118
|
if (bodyNotPatterns?.length) validateRegexPatterns(bodyNotPatterns, `checks[${index}] body_not_patterns`);
|
|
8119
|
+
const bodyJsonAssertions = isHttpStatusCheck ? normalizeHttpStatusBodyJsonAssertions(
|
|
8120
|
+
input.body_json_assertions ?? input.bodyJsonAssertions ?? input.json_body_assertions ?? input.jsonBodyAssertions ?? input.json_assertions ?? input.jsonAssertions ?? input.response_json_assertions ?? input.responseJsonAssertions,
|
|
8121
|
+
`checks[${index}] body_json_assertions`
|
|
8122
|
+
) : void 0;
|
|
7926
8123
|
if (isLinkStatusCheck) {
|
|
7927
8124
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
7928
8125
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -7948,6 +8145,7 @@ function normalizeCheck(input, index) {
|
|
|
7948
8145
|
body_contains: bodyContains,
|
|
7949
8146
|
body_not_contains: bodyNotContains,
|
|
7950
8147
|
body_not_patterns: bodyNotPatterns,
|
|
8148
|
+
body_json_assertions: bodyJsonAssertions,
|
|
7951
8149
|
expected_texts: expectedTexts,
|
|
7952
8150
|
link_selector: stringValue2(input.link_selector) || stringValue2(input.linkSelector),
|
|
7953
8151
|
source_selector: stringValue2(input.source_selector) || stringValue2(input.sourceSelector),
|
|
@@ -8154,6 +8352,34 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
8154
8352
|
const observed = isRecord(result.body_not_patterns) ? result.body_not_patterns : {};
|
|
8155
8353
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
8156
8354
|
}
|
|
8355
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
8356
|
+
const expected = check.body_json_assertions?.filter((assertion) => assertion.path) ?? [];
|
|
8357
|
+
if (!expected.length) return [];
|
|
8358
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
8359
|
+
return expected.map((assertion) => ({
|
|
8360
|
+
label: assertion.label || assertion.path,
|
|
8361
|
+
path: assertion.path,
|
|
8362
|
+
ok: false,
|
|
8363
|
+
exists: false,
|
|
8364
|
+
observed_type: "missing",
|
|
8365
|
+
errors: ["body_json_assertions evidence missing"]
|
|
8366
|
+
}));
|
|
8367
|
+
}
|
|
8368
|
+
return result.body_json_assertions.filter((assertion) => isRecord(assertion) && assertion.ok !== true).map((assertion) => ({
|
|
8369
|
+
label: stringValue2(assertion.label) || stringValue2(assertion.path) || "json assertion",
|
|
8370
|
+
path: stringValue2(assertion.path) || "",
|
|
8371
|
+
ok: false,
|
|
8372
|
+
exists: assertion.exists === true,
|
|
8373
|
+
observed: hasOwn(assertion, "observed") ? toJsonValue(assertion.observed) : void 0,
|
|
8374
|
+
observed_type: stringValue2(assertion.observed_type) || "missing",
|
|
8375
|
+
expected_exists: booleanValue(assertion.expected_exists),
|
|
8376
|
+
equals: hasOwn(assertion, "equals") ? toJsonValue(assertion.equals) : void 0,
|
|
8377
|
+
not_equals: hasOwn(assertion, "not_equals") ? toJsonValue(assertion.not_equals) : void 0,
|
|
8378
|
+
contains: hasOwn(assertion, "contains") ? toJsonValue(assertion.contains) : void 0,
|
|
8379
|
+
type: stringValue2(assertion.type),
|
|
8380
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : void 0
|
|
8381
|
+
}));
|
|
8382
|
+
}
|
|
8157
8383
|
function linkStatusResultOk(result, check) {
|
|
8158
8384
|
const status = numberValue(result.status);
|
|
8159
8385
|
if (!httpStatusIsAllowed(status, check)) return false;
|
|
@@ -8171,6 +8397,7 @@ function linkStatusResultOk(result, check) {
|
|
|
8171
8397
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
8172
8398
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
8173
8399
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
8400
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
8174
8401
|
return true;
|
|
8175
8402
|
}
|
|
8176
8403
|
function responseHeader(response, name) {
|
|
@@ -8239,7 +8466,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
8239
8466
|
statusText = typeof response.statusText === "string" ? response.statusText : "";
|
|
8240
8467
|
result.content_type = responseHeader(response, "content-type");
|
|
8241
8468
|
result.content_length = responseContentLength(response);
|
|
8242
|
-
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);
|
|
8469
|
+
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);
|
|
8243
8470
|
if (shouldReadBody && method !== "HEAD") {
|
|
8244
8471
|
const body = await responseBodyText(response);
|
|
8245
8472
|
result.bytes = body.bytes;
|
|
@@ -8252,6 +8479,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
8252
8479
|
if (check.body_not_patterns?.length) {
|
|
8253
8480
|
result.body_not_patterns = Object.fromEntries(check.body_not_patterns.filter(Boolean).map((pattern) => [pattern, new RegExp(pattern).test(body.text)]));
|
|
8254
8481
|
}
|
|
8482
|
+
if (check.body_json_assertions?.length) {
|
|
8483
|
+
result.body_json_assertions = evaluateHttpStatusBodyJsonAssertions(body.text, check.body_json_assertions);
|
|
8484
|
+
}
|
|
8255
8485
|
}
|
|
8256
8486
|
} catch (caught) {
|
|
8257
8487
|
error = String(caught instanceof Error ? caught.message : caught).slice(0, 500);
|
|
@@ -8260,6 +8490,7 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
8260
8490
|
const bodyContainsMissing = httpStatusBodyContainsFailures(result, check);
|
|
8261
8491
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(result, check);
|
|
8262
8492
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(result, check);
|
|
8493
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(result, check);
|
|
8263
8494
|
const ok = !error && linkStatusResultOk(result, check);
|
|
8264
8495
|
return {
|
|
8265
8496
|
index,
|
|
@@ -8278,7 +8509,9 @@ async function preflightHttpStatusCheck(check, index, targetUrl, fetchImpl) {
|
|
|
8278
8509
|
body_not_contains: isRecord(result.body_not_contains) ? Object.fromEntries(Object.entries(result.body_not_contains).map(([key, value]) => [key, value === true])) : null,
|
|
8279
8510
|
body_not_contains_found: bodyNotContainsFound,
|
|
8280
8511
|
body_not_patterns: isRecord(result.body_not_patterns) ? Object.fromEntries(Object.entries(result.body_not_patterns).map(([key, value]) => [key, value === true])) : null,
|
|
8281
|
-
body_not_patterns_found: bodyNotPatternsFound
|
|
8512
|
+
body_not_patterns_found: bodyNotPatternsFound,
|
|
8513
|
+
body_json_assertions: Array.isArray(result.body_json_assertions) ? result.body_json_assertions : null,
|
|
8514
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed
|
|
8282
8515
|
};
|
|
8283
8516
|
}
|
|
8284
8517
|
async function preflightRiddleProofProfileHttpStatusChecks(profile, options = {}) {
|
|
@@ -8323,6 +8556,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
8323
8556
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
8324
8557
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
8325
8558
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
8559
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
8326
8560
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
8327
8561
|
failures.push({
|
|
8328
8562
|
code: "http_status_failed",
|
|
@@ -8341,6 +8575,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
8341
8575
|
body_not_contains_found: bodyNotContainsFound,
|
|
8342
8576
|
body_not_patterns: check.body_not_patterns ?? null,
|
|
8343
8577
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
8578
|
+
body_json_assertions: check.body_json_assertions ?? null,
|
|
8579
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
8344
8580
|
body_sample: stringValue2(statusEvidence.body_sample) ?? null
|
|
8345
8581
|
});
|
|
8346
8582
|
}
|
|
@@ -8362,6 +8598,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
8362
8598
|
body_not_contains_found: bodyNotContainsFound,
|
|
8363
8599
|
body_not_patterns: isRecord(statusEvidence.body_not_patterns) ? toJsonValue(statusEvidence.body_not_patterns) : null,
|
|
8364
8600
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
8601
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? toJsonValue(statusEvidence.body_json_assertions) : null,
|
|
8602
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed.map((assertion) => toJsonValue(assertion)),
|
|
8365
8603
|
body_sample: stringValue2(statusEvidence.body_sample) ?? null,
|
|
8366
8604
|
failures
|
|
8367
8605
|
};
|
|
@@ -8998,6 +9236,7 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
8998
9236
|
body_contains: check.body_contains ?? [],
|
|
8999
9237
|
body_not_contains: check.body_not_contains ?? [],
|
|
9000
9238
|
body_not_patterns: check.body_not_patterns ?? [],
|
|
9239
|
+
body_json_assertions: toJsonValue(check.body_json_assertions ?? []),
|
|
9001
9240
|
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
9002
9241
|
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue2(summary.viewport) ?? null, failure })) : [])
|
|
9003
9242
|
},
|
|
@@ -9725,6 +9964,36 @@ function httpStatusBodyNotPatternFailures(result, check) {
|
|
|
9725
9964
|
: {};
|
|
9726
9965
|
return forbidden.filter((pattern) => observed[pattern] !== false);
|
|
9727
9966
|
}
|
|
9967
|
+
function httpStatusBodyJsonAssertionFailures(result, check) {
|
|
9968
|
+
const expected = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
9969
|
+
if (!expected.length) return [];
|
|
9970
|
+
if (!Array.isArray(result.body_json_assertions)) {
|
|
9971
|
+
return expected.map((assertion) => ({
|
|
9972
|
+
label: assertion.label || assertion.path,
|
|
9973
|
+
path: assertion.path,
|
|
9974
|
+
ok: false,
|
|
9975
|
+
exists: false,
|
|
9976
|
+
observed_type: "missing",
|
|
9977
|
+
errors: ["body_json_assertions evidence missing"],
|
|
9978
|
+
}));
|
|
9979
|
+
}
|
|
9980
|
+
return result.body_json_assertions
|
|
9981
|
+
.filter((assertion) => assertion && typeof assertion === "object" && assertion.ok !== true)
|
|
9982
|
+
.map((assertion) => ({
|
|
9983
|
+
label: typeof assertion.label === "string" && assertion.label ? assertion.label : typeof assertion.path === "string" && assertion.path ? assertion.path : "json assertion",
|
|
9984
|
+
path: typeof assertion.path === "string" ? assertion.path : "",
|
|
9985
|
+
ok: false,
|
|
9986
|
+
exists: assertion.exists === true,
|
|
9987
|
+
observed: Object.hasOwn(assertion, "observed") ? assertion.observed : undefined,
|
|
9988
|
+
observed_type: typeof assertion.observed_type === "string" && assertion.observed_type ? assertion.observed_type : "missing",
|
|
9989
|
+
expected_exists: typeof assertion.expected_exists === "boolean" ? assertion.expected_exists : undefined,
|
|
9990
|
+
equals: Object.hasOwn(assertion, "equals") ? assertion.equals : undefined,
|
|
9991
|
+
not_equals: Object.hasOwn(assertion, "not_equals") ? assertion.not_equals : undefined,
|
|
9992
|
+
contains: Object.hasOwn(assertion, "contains") ? assertion.contains : undefined,
|
|
9993
|
+
type: typeof assertion.type === "string" ? assertion.type : undefined,
|
|
9994
|
+
errors: Array.isArray(assertion.errors) ? assertion.errors.map(String) : undefined,
|
|
9995
|
+
}));
|
|
9996
|
+
}
|
|
9728
9997
|
function linkStatusResultOk(result, check) {
|
|
9729
9998
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
9730
9999
|
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
@@ -9742,6 +10011,7 @@ function linkStatusResultOk(result, check) {
|
|
|
9742
10011
|
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
9743
10012
|
if (httpStatusBodyNotContainsFailures(result, check).length) return false;
|
|
9744
10013
|
if (httpStatusBodyNotPatternFailures(result, check).length) return false;
|
|
10014
|
+
if (httpStatusBodyJsonAssertionFailures(result, check).length) return false;
|
|
9745
10015
|
return true;
|
|
9746
10016
|
}
|
|
9747
10017
|
function summarizeHttpStatusEvidence(viewport, check) {
|
|
@@ -9762,6 +10032,7 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
9762
10032
|
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
9763
10033
|
const bodyNotContainsFound = httpStatusBodyNotContainsFailures(statusEvidence, check);
|
|
9764
10034
|
const bodyNotPatternsFound = httpStatusBodyNotPatternFailures(statusEvidence, check);
|
|
10035
|
+
const bodyJsonAssertionsFailed = httpStatusBodyJsonAssertionFailures(statusEvidence, check);
|
|
9765
10036
|
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
9766
10037
|
failures.push({
|
|
9767
10038
|
code: "http_status_failed",
|
|
@@ -9780,6 +10051,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
9780
10051
|
body_not_contains_found: bodyNotContainsFound,
|
|
9781
10052
|
body_not_patterns: Array.isArray(check.body_not_patterns) ? check.body_not_patterns : null,
|
|
9782
10053
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10054
|
+
body_json_assertions: Array.isArray(check.body_json_assertions) ? check.body_json_assertions : null,
|
|
10055
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
9783
10056
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
9784
10057
|
});
|
|
9785
10058
|
}
|
|
@@ -9807,6 +10080,8 @@ function summarizeHttpStatusEvidence(viewport, check) {
|
|
|
9807
10080
|
? statusEvidence.body_not_patterns
|
|
9808
10081
|
: null,
|
|
9809
10082
|
body_not_patterns_found: bodyNotPatternsFound,
|
|
10083
|
+
body_json_assertions: Array.isArray(statusEvidence.body_json_assertions) ? statusEvidence.body_json_assertions : null,
|
|
10084
|
+
body_json_assertions_failed: bodyJsonAssertionsFailed,
|
|
9810
10085
|
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
9811
10086
|
failures,
|
|
9812
10087
|
};
|
|
@@ -11909,6 +12184,155 @@ function linkProbeResponseFields(response, method) {
|
|
|
11909
12184
|
content_length: contentLength,
|
|
11910
12185
|
};
|
|
11911
12186
|
}
|
|
12187
|
+
function jsonProbeValueType(value) {
|
|
12188
|
+
if (value === null) return "null";
|
|
12189
|
+
if (Array.isArray(value)) return "array";
|
|
12190
|
+
if (typeof value === "boolean") return "boolean";
|
|
12191
|
+
if (typeof value === "number") return "number";
|
|
12192
|
+
if (typeof value === "string") return "string";
|
|
12193
|
+
return "object";
|
|
12194
|
+
}
|
|
12195
|
+
function jsonProbeDeepEqual(left, right) {
|
|
12196
|
+
if (left === right) return true;
|
|
12197
|
+
if (typeof left !== typeof right) return false;
|
|
12198
|
+
if (left === null || right === null) return left === right;
|
|
12199
|
+
if (typeof left !== "object" || typeof right !== "object") return false;
|
|
12200
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
12201
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
12202
|
+
return left.every((item, index) => jsonProbeDeepEqual(item, right[index]));
|
|
12203
|
+
}
|
|
12204
|
+
const leftKeys = Object.keys(left).sort();
|
|
12205
|
+
const rightKeys = Object.keys(right).sort();
|
|
12206
|
+
if (!jsonProbeDeepEqual(leftKeys, rightKeys)) return false;
|
|
12207
|
+
return leftKeys.every((key) => jsonProbeDeepEqual(left[key], right[key]));
|
|
12208
|
+
}
|
|
12209
|
+
function jsonProbeContains(observed, expected) {
|
|
12210
|
+
if (typeof observed === "string" && typeof expected === "string") return observed.includes(expected);
|
|
12211
|
+
if (Array.isArray(observed)) return observed.some((item) => jsonProbeDeepEqual(item, expected));
|
|
12212
|
+
if (observed && expected && typeof observed === "object" && typeof expected === "object" && !Array.isArray(observed) && !Array.isArray(expected)) {
|
|
12213
|
+
return Object.entries(expected).every(([key, value]) => Object.hasOwn(observed, key) && jsonProbeDeepEqual(observed[key], value));
|
|
12214
|
+
}
|
|
12215
|
+
return false;
|
|
12216
|
+
}
|
|
12217
|
+
function parseJsonProbePathSegments(path) {
|
|
12218
|
+
let input = String(path || "").trim();
|
|
12219
|
+
if (!input) throw new Error("path is empty");
|
|
12220
|
+
if (input === "$") return [];
|
|
12221
|
+
if (input.startsWith("$.")) input = input.slice(2);
|
|
12222
|
+
else if (input.startsWith("$[")) input = input.slice(1);
|
|
12223
|
+
const segments = [];
|
|
12224
|
+
let token = "";
|
|
12225
|
+
const pushToken = () => {
|
|
12226
|
+
if (!token) return;
|
|
12227
|
+
segments.push(token);
|
|
12228
|
+
token = "";
|
|
12229
|
+
};
|
|
12230
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
12231
|
+
const char = input[index];
|
|
12232
|
+
if (char === ".") {
|
|
12233
|
+
pushToken();
|
|
12234
|
+
continue;
|
|
12235
|
+
}
|
|
12236
|
+
if (char !== "[") {
|
|
12237
|
+
token += char;
|
|
12238
|
+
continue;
|
|
12239
|
+
}
|
|
12240
|
+
pushToken();
|
|
12241
|
+
const closeIndex = input.indexOf("]", index + 1);
|
|
12242
|
+
if (closeIndex === -1) throw new Error("unterminated bracket at " + index);
|
|
12243
|
+
const bracket = input.slice(index + 1, closeIndex).trim();
|
|
12244
|
+
if (!bracket) throw new Error("empty bracket at " + index);
|
|
12245
|
+
if (/^\d+$/.test(bracket)) {
|
|
12246
|
+
segments.push(Number(bracket));
|
|
12247
|
+
} else if ((bracket.startsWith('"') && bracket.endsWith('"')) || (bracket.startsWith("'") && bracket.endsWith("'"))) {
|
|
12248
|
+
const quoted = bracket.startsWith("'")
|
|
12249
|
+
? '"' + bracket.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + '"'
|
|
12250
|
+
: bracket;
|
|
12251
|
+
segments.push(String(JSON.parse(quoted)));
|
|
12252
|
+
} else {
|
|
12253
|
+
segments.push(bracket);
|
|
12254
|
+
}
|
|
12255
|
+
index = closeIndex;
|
|
12256
|
+
}
|
|
12257
|
+
pushToken();
|
|
12258
|
+
return segments;
|
|
12259
|
+
}
|
|
12260
|
+
function resolveJsonProbePath(root, path) {
|
|
12261
|
+
let segments;
|
|
12262
|
+
try {
|
|
12263
|
+
segments = parseJsonProbePathSegments(path);
|
|
12264
|
+
} catch (error) {
|
|
12265
|
+
return { exists: false, error: String(error && error.message ? error.message : error) };
|
|
12266
|
+
}
|
|
12267
|
+
let current = root;
|
|
12268
|
+
for (const segment of segments) {
|
|
12269
|
+
if (typeof segment === "number") {
|
|
12270
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) return { exists: false };
|
|
12271
|
+
current = current[segment];
|
|
12272
|
+
continue;
|
|
12273
|
+
}
|
|
12274
|
+
if (!current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment)) {
|
|
12275
|
+
return { exists: false };
|
|
12276
|
+
}
|
|
12277
|
+
current = current[segment];
|
|
12278
|
+
}
|
|
12279
|
+
return { exists: true, value: current };
|
|
12280
|
+
}
|
|
12281
|
+
function evaluateJsonProbeAssertion(root, assertion) {
|
|
12282
|
+
const resolved = resolveJsonProbePath(root, assertion.path);
|
|
12283
|
+
const errors = [];
|
|
12284
|
+
const result = {
|
|
12285
|
+
label: assertion.label || assertion.path,
|
|
12286
|
+
path: assertion.path,
|
|
12287
|
+
ok: true,
|
|
12288
|
+
exists: resolved.exists,
|
|
12289
|
+
observed_type: resolved.exists ? jsonProbeValueType(resolved.value) : "missing",
|
|
12290
|
+
};
|
|
12291
|
+
if (resolved.exists) result.observed = resolved.value;
|
|
12292
|
+
if (resolved.error) errors.push(resolved.error);
|
|
12293
|
+
if (Object.hasOwn(assertion, "exists")) {
|
|
12294
|
+
result.expected_exists = assertion.exists;
|
|
12295
|
+
if (resolved.exists !== assertion.exists) errors.push("expected exists=" + assertion.exists);
|
|
12296
|
+
}
|
|
12297
|
+
if (Object.hasOwn(assertion, "type")) {
|
|
12298
|
+
result.type = assertion.type;
|
|
12299
|
+
if (!resolved.exists || jsonProbeValueType(resolved.value) !== assertion.type) errors.push("expected type " + assertion.type);
|
|
12300
|
+
}
|
|
12301
|
+
if (Object.hasOwn(assertion, "equals")) {
|
|
12302
|
+
result.equals = assertion.equals;
|
|
12303
|
+
if (!resolved.exists || !jsonProbeDeepEqual(resolved.value, assertion.equals)) errors.push("expected JSON value equality");
|
|
12304
|
+
}
|
|
12305
|
+
if (Object.hasOwn(assertion, "not_equals")) {
|
|
12306
|
+
result.not_equals = assertion.not_equals;
|
|
12307
|
+
if (resolved.exists && jsonProbeDeepEqual(resolved.value, assertion.not_equals)) errors.push("expected JSON value inequality");
|
|
12308
|
+
}
|
|
12309
|
+
if (Object.hasOwn(assertion, "contains")) {
|
|
12310
|
+
result.contains = assertion.contains;
|
|
12311
|
+
if (!resolved.exists || !jsonProbeContains(resolved.value, assertion.contains)) errors.push("expected JSON value containment");
|
|
12312
|
+
}
|
|
12313
|
+
result.ok = errors.length === 0;
|
|
12314
|
+
if (errors.length) result.errors = errors;
|
|
12315
|
+
return result;
|
|
12316
|
+
}
|
|
12317
|
+
function evaluateJsonProbeAssertions(text, assertions) {
|
|
12318
|
+
const expected = Array.isArray(assertions) ? assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
12319
|
+
if (!expected.length) return [];
|
|
12320
|
+
let parsed;
|
|
12321
|
+
try {
|
|
12322
|
+
parsed = JSON.parse(text);
|
|
12323
|
+
} catch (error) {
|
|
12324
|
+
const message = "response body is not valid JSON: " + String(error && error.message ? error.message : error).slice(0, 200);
|
|
12325
|
+
return expected.map((assertion) => ({
|
|
12326
|
+
label: assertion.label || assertion.path,
|
|
12327
|
+
path: assertion.path,
|
|
12328
|
+
ok: false,
|
|
12329
|
+
exists: false,
|
|
12330
|
+
observed_type: "missing",
|
|
12331
|
+
errors: [message],
|
|
12332
|
+
}));
|
|
12333
|
+
}
|
|
12334
|
+
return expected.map((assertion) => evaluateJsonProbeAssertion(parsed, assertion));
|
|
12335
|
+
}
|
|
11912
12336
|
async function collectHttpStatus(check) {
|
|
11913
12337
|
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
11914
12338
|
const method = httpStatusMethod(check);
|
|
@@ -11925,6 +12349,7 @@ async function collectHttpStatus(check) {
|
|
|
11925
12349
|
const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
11926
12350
|
const bodyNotContains = Array.isArray(check.body_not_contains) ? check.body_not_contains.filter(Boolean) : [];
|
|
11927
12351
|
const bodyNotPatterns = Array.isArray(check.body_not_patterns) ? check.body_not_patterns.filter(Boolean) : [];
|
|
12352
|
+
const bodyJsonAssertions = Array.isArray(check.body_json_assertions) ? check.body_json_assertions.filter((assertion) => assertion && assertion.path) : [];
|
|
11928
12353
|
const options = {
|
|
11929
12354
|
method,
|
|
11930
12355
|
redirect: "follow",
|
|
@@ -11950,17 +12375,18 @@ async function collectHttpStatus(check) {
|
|
|
11950
12375
|
Object.assign(result, linkProbeResponseFields(response, method));
|
|
11951
12376
|
result.url = url;
|
|
11952
12377
|
result.status_text = response.statusText || "";
|
|
11953
|
-
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;
|
|
12378
|
+
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;
|
|
11954
12379
|
if (shouldReadBody) {
|
|
11955
12380
|
try {
|
|
11956
12381
|
const buffer = await response.arrayBuffer();
|
|
11957
12382
|
result.bytes = buffer.byteLength;
|
|
11958
|
-
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length) {
|
|
12383
|
+
if (bodyContains.length || bodyNotContains.length || bodyNotPatterns.length || bodyJsonAssertions.length) {
|
|
11959
12384
|
const text = new TextDecoder().decode(buffer);
|
|
11960
12385
|
result.body_sample = text.slice(0, 1000);
|
|
11961
12386
|
if (bodyContains.length) result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
|
|
11962
12387
|
if (bodyNotContains.length) result.body_not_contains = Object.fromEntries(bodyNotContains.map((forbidden) => [forbidden, text.includes(forbidden)]));
|
|
11963
12388
|
if (bodyNotPatterns.length) result.body_not_patterns = Object.fromEntries(bodyNotPatterns.map((pattern) => [pattern, new RegExp(pattern).test(text)]));
|
|
12389
|
+
if (bodyJsonAssertions.length) result.body_json_assertions = evaluateJsonProbeAssertions(text, bodyJsonAssertions);
|
|
11964
12390
|
}
|
|
11965
12391
|
} catch (error) {
|
|
11966
12392
|
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
@@ -11973,6 +12399,7 @@ async function collectHttpStatus(check) {
|
|
|
11973
12399
|
&& (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
|
|
11974
12400
|
&& (!bodyNotContains.length || bodyNotContains.every((forbidden) => result.body_not_contains && result.body_not_contains[forbidden] === false))
|
|
11975
12401
|
&& (!bodyNotPatterns.length || bodyNotPatterns.every((pattern) => result.body_not_patterns && result.body_not_patterns[pattern] === false))
|
|
12402
|
+
&& (!bodyJsonAssertions.length || (Array.isArray(result.body_json_assertions) && result.body_json_assertions.every((assertion) => assertion.ok === true)))
|
|
11976
12403
|
&& !result.error;
|
|
11977
12404
|
return result;
|
|
11978
12405
|
} catch (error) {
|
|
@@ -13685,6 +14112,21 @@ function profileHttpStatusAssertionKeys(evidence, viewports, field) {
|
|
|
13685
14112
|
}
|
|
13686
14113
|
return [...keys];
|
|
13687
14114
|
}
|
|
14115
|
+
function profileHttpStatusJsonAssertionCount(viewports) {
|
|
14116
|
+
if (!viewports.length) return void 0;
|
|
14117
|
+
let passed = 0;
|
|
14118
|
+
let total = 0;
|
|
14119
|
+
for (const viewport of viewports) {
|
|
14120
|
+
if (!Array.isArray(viewport.body_json_assertions)) continue;
|
|
14121
|
+
for (const assertion of viewport.body_json_assertions) {
|
|
14122
|
+
const record = cliRecord(assertion);
|
|
14123
|
+
if (!record) continue;
|
|
14124
|
+
total += 1;
|
|
14125
|
+
if (record.ok === true) passed += 1;
|
|
14126
|
+
}
|
|
14127
|
+
}
|
|
14128
|
+
return total ? { passed, total } : void 0;
|
|
14129
|
+
}
|
|
13688
14130
|
function profileHttpStatusSummaryMarkdown(result) {
|
|
13689
14131
|
const httpStatusChecks = result.checks.filter((check) => check.type === "http_status");
|
|
13690
14132
|
const lines = [];
|
|
@@ -13715,10 +14157,12 @@ function profileHttpStatusSummaryMarkdown(result) {
|
|
|
13715
14157
|
profileHttpStatusAssertionKeys(evidence, viewports, "body_not_patterns"),
|
|
13716
14158
|
false
|
|
13717
14159
|
);
|
|
14160
|
+
const bodyJsonAssertions = profileHttpStatusJsonAssertionCount(viewports);
|
|
13718
14161
|
const bodyParts = [
|
|
13719
14162
|
bodyContains ? `body_contains ${bodyContains.passed}/${bodyContains.total}` : "",
|
|
13720
14163
|
bodyNotContains ? `body_not_contains clean ${bodyNotContains.passed}/${bodyNotContains.total}` : "",
|
|
13721
|
-
bodyNotPatterns ? `body_not_patterns clean ${bodyNotPatterns.passed}/${bodyNotPatterns.total}` : ""
|
|
14164
|
+
bodyNotPatterns ? `body_not_patterns clean ${bodyNotPatterns.passed}/${bodyNotPatterns.total}` : "",
|
|
14165
|
+
bodyJsonAssertions ? `body_json_assertions ${bodyJsonAssertions.passed}/${bodyJsonAssertions.total}` : ""
|
|
13722
14166
|
].filter(Boolean);
|
|
13723
14167
|
lines.push(
|
|
13724
14168
|
`- ${label}: ${method}${url ? ` ${markdownInlineCode(url)}` : ""}, statuses ${statuses.length ? statuses.join("/") : "unknown"}${bodyParts.length ? `, ${bodyParts.join(", ")}` : ""}, failures ${failedTotal}`
|