@riddledc/riddle-proof 0.7.97 → 0.7.99
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/dist/{chunk-G3UY3SHZ.js → chunk-W5PBTTMJ.js} +332 -14
- package/dist/cli.cjs +361 -14
- package/dist/cli.js +30 -1
- package/dist/index.cjs +332 -14
- package/dist/index.js +1 -1
- package/dist/profile.cjs +332 -14
- package/dist/profile.d.cts +8 -1
- package/dist/profile.d.ts +8 -1
- package/dist/profile.js +1 -1
- package/package.json +1 -1
package/dist/profile.cjs
CHANGED
|
@@ -72,6 +72,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
72
72
|
"frame_no_horizontal_overflow",
|
|
73
73
|
"text_visible",
|
|
74
74
|
"text_absent",
|
|
75
|
+
"http_status",
|
|
75
76
|
"link_status",
|
|
76
77
|
"artifact_link_status",
|
|
77
78
|
"route_inventory",
|
|
@@ -821,19 +822,34 @@ function normalizeCheck(input, index) {
|
|
|
821
822
|
if (type === "route_inventory" && !expectedRoutes?.length) {
|
|
822
823
|
throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
|
|
823
824
|
}
|
|
825
|
+
const isHttpStatusCheck = type === "http_status";
|
|
824
826
|
const isLinkStatusCheck = type === "link_status" || type === "artifact_link_status";
|
|
825
|
-
const
|
|
826
|
-
const
|
|
827
|
+
const isStatusCheck = isHttpStatusCheck || isLinkStatusCheck;
|
|
828
|
+
const requestUrl = stringFromOwn(input, "url", "endpoint_url", "endpointUrl", "endpoint", "request_url", "requestUrl", "path");
|
|
829
|
+
if (isHttpStatusCheck && !requestUrl) {
|
|
830
|
+
throw new Error(`checks[${index}] http_status requires url.`);
|
|
831
|
+
}
|
|
832
|
+
const method = stringFromOwn(input, "method", "http_method", "httpMethod")?.toUpperCase();
|
|
833
|
+
if (isHttpStatusCheck && method && !/^[A-Z]+$/.test(method)) {
|
|
834
|
+
throw new Error(`checks[${index}] http_status method must contain only letters.`);
|
|
835
|
+
}
|
|
836
|
+
const hasBodyJson = hasOwn(input, "body_json") || hasOwn(input, "bodyJson") || hasOwn(input, "json");
|
|
837
|
+
const expectedStatus = isStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
|
|
838
|
+
const allowedStatuses = isStatusCheck ? normalizeHttpStatuses(
|
|
827
839
|
input.allowed_statuses ?? input.allowedStatuses ?? input.expected_statuses ?? input.expectedStatuses,
|
|
828
840
|
`checks[${index}] allowed_statuses`
|
|
829
841
|
) : void 0;
|
|
830
842
|
const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
|
|
831
|
-
const minBytes =
|
|
832
|
-
const expectedContentType =
|
|
833
|
-
const allowedContentTypes =
|
|
843
|
+
const minBytes = isStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
|
|
844
|
+
const expectedContentType = isStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
|
|
845
|
+
const allowedContentTypes = isStatusCheck ? normalizeStringList(
|
|
834
846
|
input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
|
|
835
847
|
`checks[${index}] allowed_content_types`
|
|
836
848
|
) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
|
|
849
|
+
const bodyContains = isHttpStatusCheck ? normalizeStringList(
|
|
850
|
+
input.body_contains ?? input.bodyContains ?? input.expected_body_contains ?? input.expectedBodyContains ?? input.response_body_contains ?? input.responseBodyContains ?? input.body_includes ?? input.bodyIncludes,
|
|
851
|
+
`checks[${index}] body_contains`
|
|
852
|
+
) : void 0;
|
|
837
853
|
if (isLinkStatusCheck) {
|
|
838
854
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
839
855
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -851,6 +867,12 @@ function normalizeCheck(input, index) {
|
|
|
851
867
|
expected_url: expectedUrl,
|
|
852
868
|
expected_routes: expectedRoutes,
|
|
853
869
|
selector: stringValue(input.selector),
|
|
870
|
+
url: isHttpStatusCheck ? requestUrl : void 0,
|
|
871
|
+
method: isHttpStatusCheck ? method || "GET" : void 0,
|
|
872
|
+
headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
|
|
873
|
+
body: isHttpStatusCheck ? stringValue(input.body) : void 0,
|
|
874
|
+
body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
|
|
875
|
+
body_contains: bodyContains,
|
|
854
876
|
expected_texts: expectedTexts,
|
|
855
877
|
link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
|
|
856
878
|
source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
|
|
@@ -874,7 +896,7 @@ function normalizeCheck(input, index) {
|
|
|
874
896
|
max_links: maxLinks,
|
|
875
897
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
876
898
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
877
|
-
require_nonzero_bytes:
|
|
899
|
+
require_nonzero_bytes: isStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
878
900
|
min_bytes: minBytes,
|
|
879
901
|
allowed_content_types: allowedContentTypes,
|
|
880
902
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
@@ -973,14 +995,32 @@ function selectorKey(check) {
|
|
|
973
995
|
function linkStatusSelector(check) {
|
|
974
996
|
return check.selector || check.link_selector || "a[href]";
|
|
975
997
|
}
|
|
976
|
-
function
|
|
998
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
999
|
+
const rawUrl = check.url || "";
|
|
1000
|
+
if (!rawUrl) return "";
|
|
1001
|
+
try {
|
|
1002
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
1003
|
+
} catch {
|
|
1004
|
+
return rawUrl;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function httpStatusMethod(check) {
|
|
1008
|
+
return (check.method || "GET").toUpperCase();
|
|
1009
|
+
}
|
|
1010
|
+
function httpStatusKey(check, baseUrl) {
|
|
1011
|
+
return `${httpStatusMethod(check)} ${httpStatusRequestUrl(check, baseUrl)}`;
|
|
1012
|
+
}
|
|
1013
|
+
function httpStatusAllowedStatuses(check) {
|
|
977
1014
|
if (check.allowed_statuses?.length) return check.allowed_statuses;
|
|
978
1015
|
if (check.expected_status !== void 0) return [check.expected_status];
|
|
979
1016
|
return void 0;
|
|
980
1017
|
}
|
|
981
|
-
function
|
|
1018
|
+
function linkStatusAllowedStatuses(check) {
|
|
1019
|
+
return httpStatusAllowedStatuses(check);
|
|
1020
|
+
}
|
|
1021
|
+
function httpStatusIsAllowed(status, check) {
|
|
982
1022
|
if (status === void 0) return false;
|
|
983
|
-
const allowed =
|
|
1023
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
984
1024
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
985
1025
|
}
|
|
986
1026
|
function linkStatusObservedBytes(result) {
|
|
@@ -1004,9 +1044,15 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
1004
1044
|
return actual === normalized;
|
|
1005
1045
|
});
|
|
1006
1046
|
}
|
|
1047
|
+
function httpStatusBodyContainsFailures(result, check) {
|
|
1048
|
+
const expected = check.body_contains?.filter(Boolean) ?? [];
|
|
1049
|
+
if (!expected.length) return [];
|
|
1050
|
+
const observed = isRecord(result.body_contains) ? result.body_contains : {};
|
|
1051
|
+
return expected.filter((text) => observed[text] !== true);
|
|
1052
|
+
}
|
|
1007
1053
|
function linkStatusResultOk(result, check) {
|
|
1008
1054
|
const status = numberValue(result.status);
|
|
1009
|
-
if (!
|
|
1055
|
+
if (!httpStatusIsAllowed(status, check)) return false;
|
|
1010
1056
|
if (stringValue(result.error)) return false;
|
|
1011
1057
|
if (result.ok === false) return false;
|
|
1012
1058
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -1018,8 +1064,64 @@ function linkStatusResultOk(result, check) {
|
|
|
1018
1064
|
const observedBytes = linkStatusObservedBytes(result);
|
|
1019
1065
|
if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
|
|
1020
1066
|
}
|
|
1067
|
+
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
1021
1068
|
return true;
|
|
1022
1069
|
}
|
|
1070
|
+
function httpStatusEvidenceForCheck(viewport, check) {
|
|
1071
|
+
const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
|
|
1072
|
+
return isRecord(evidence) ? evidence : void 0;
|
|
1073
|
+
}
|
|
1074
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
1075
|
+
const key = httpStatusKey(check, viewport.url);
|
|
1076
|
+
const statusEvidence = httpStatusEvidenceForCheck(viewport, check);
|
|
1077
|
+
if (!statusEvidence) {
|
|
1078
|
+
return {
|
|
1079
|
+
viewport: viewport.name,
|
|
1080
|
+
key,
|
|
1081
|
+
url: httpStatusRequestUrl(check, viewport.url),
|
|
1082
|
+
method: httpStatusMethod(check),
|
|
1083
|
+
ok: false,
|
|
1084
|
+
status: null,
|
|
1085
|
+
failures: [{ code: "http_status_evidence_missing" }]
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
const failures = [];
|
|
1089
|
+
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
1090
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
1091
|
+
failures.push({
|
|
1092
|
+
code: "http_status_failed",
|
|
1093
|
+
url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
1094
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
1095
|
+
method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
|
|
1096
|
+
error: stringValue(statusEvidence.error) ?? null,
|
|
1097
|
+
content_type: stringValue(statusEvidence.content_type) ?? null,
|
|
1098
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
1099
|
+
allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
|
|
1100
|
+
min_bytes: check.min_bytes ?? null,
|
|
1101
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1102
|
+
body_contains: check.body_contains ?? null,
|
|
1103
|
+
body_contains_missing: bodyContainsMissing,
|
|
1104
|
+
body_sample: stringValue(statusEvidence.body_sample) ?? null
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
viewport: viewport.name,
|
|
1109
|
+
key,
|
|
1110
|
+
url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
1111
|
+
method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
|
|
1112
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
1113
|
+
status_text: stringValue(statusEvidence.status_text) ?? null,
|
|
1114
|
+
ok: failures.length === 0,
|
|
1115
|
+
error: stringValue(statusEvidence.error) ?? null,
|
|
1116
|
+
content_type: stringValue(statusEvidence.content_type) ?? null,
|
|
1117
|
+
content_length: numberValue(statusEvidence.content_length) ?? null,
|
|
1118
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
1119
|
+
body_contains: isRecord(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
|
|
1120
|
+
body_contains_missing: bodyContainsMissing,
|
|
1121
|
+
body_sample: stringValue(statusEvidence.body_sample) ?? null,
|
|
1122
|
+
failures
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1023
1125
|
function linkStatusEvidenceForCheck(viewport, check) {
|
|
1024
1126
|
const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
|
|
1025
1127
|
return isRecord(evidence) ? evidence : void 0;
|
|
@@ -1610,6 +1712,29 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1610
1712
|
message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
|
|
1611
1713
|
};
|
|
1612
1714
|
}
|
|
1715
|
+
if (check.type === "http_status") {
|
|
1716
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
1717
|
+
const method = httpStatusMethod(check);
|
|
1718
|
+
const summaries = viewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
1719
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
1720
|
+
return {
|
|
1721
|
+
type: check.type,
|
|
1722
|
+
label: checkLabel(check),
|
|
1723
|
+
status: failed.length ? "failed" : "passed",
|
|
1724
|
+
evidence: {
|
|
1725
|
+
url,
|
|
1726
|
+
method,
|
|
1727
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
1728
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
1729
|
+
min_bytes: check.min_bytes ?? null,
|
|
1730
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1731
|
+
body_contains: check.body_contains ?? [],
|
|
1732
|
+
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
1733
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
1734
|
+
},
|
|
1735
|
+
message: failed.length ? `HTTP status failed in ${failed.length} viewport(s).` : void 0
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1613
1738
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
1614
1739
|
const selector = linkStatusSelector(check);
|
|
1615
1740
|
const summaries = viewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -2219,16 +2344,37 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
2219
2344
|
function linkStatusSelector(check) {
|
|
2220
2345
|
return check.selector || check.link_selector || "a[href]";
|
|
2221
2346
|
}
|
|
2222
|
-
function
|
|
2347
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
2348
|
+
const rawUrl = check.url || "";
|
|
2349
|
+
if (!rawUrl) return "";
|
|
2350
|
+
try {
|
|
2351
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
2352
|
+
} catch {
|
|
2353
|
+
return rawUrl;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
function httpStatusMethod(check) {
|
|
2357
|
+
return String(check.method || "GET").toUpperCase();
|
|
2358
|
+
}
|
|
2359
|
+
function httpStatusKey(check, baseUrl) {
|
|
2360
|
+
return httpStatusMethod(check) + " " + httpStatusRequestUrl(check, baseUrl);
|
|
2361
|
+
}
|
|
2362
|
+
function httpStatusAllowedStatuses(check) {
|
|
2223
2363
|
if (Array.isArray(check.allowed_statuses) && check.allowed_statuses.length) return check.allowed_statuses;
|
|
2224
2364
|
if (typeof check.expected_status === "number" && Number.isFinite(check.expected_status)) return [check.expected_status];
|
|
2225
2365
|
return undefined;
|
|
2226
2366
|
}
|
|
2227
|
-
function
|
|
2367
|
+
function linkStatusAllowedStatuses(check) {
|
|
2368
|
+
return httpStatusAllowedStatuses(check);
|
|
2369
|
+
}
|
|
2370
|
+
function httpStatusIsAllowed(status, check) {
|
|
2228
2371
|
if (typeof status !== "number" || !Number.isFinite(status)) return false;
|
|
2229
|
-
const allowed =
|
|
2372
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
2230
2373
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
2231
2374
|
}
|
|
2375
|
+
function linkStatusIsAllowed(status, check) {
|
|
2376
|
+
return httpStatusIsAllowed(status, check);
|
|
2377
|
+
}
|
|
2232
2378
|
function linkStatusObservedBytes(result) {
|
|
2233
2379
|
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
2234
2380
|
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
@@ -2251,9 +2397,17 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
2251
2397
|
return actual === normalized;
|
|
2252
2398
|
});
|
|
2253
2399
|
}
|
|
2400
|
+
function httpStatusBodyContainsFailures(result, check) {
|
|
2401
|
+
const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
2402
|
+
if (!expected.length) return [];
|
|
2403
|
+
const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
|
|
2404
|
+
? result.body_contains
|
|
2405
|
+
: {};
|
|
2406
|
+
return expected.filter((text) => observed[text] !== true);
|
|
2407
|
+
}
|
|
2254
2408
|
function linkStatusResultOk(result, check) {
|
|
2255
2409
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2256
|
-
if (!
|
|
2410
|
+
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
2257
2411
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
2258
2412
|
if (result.ok === false) return false;
|
|
2259
2413
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -2265,8 +2419,62 @@ function linkStatusResultOk(result, check) {
|
|
|
2265
2419
|
const observedBytes = linkStatusObservedBytes(result);
|
|
2266
2420
|
if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
|
|
2267
2421
|
}
|
|
2422
|
+
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
2268
2423
|
return true;
|
|
2269
2424
|
}
|
|
2425
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
2426
|
+
const key = httpStatusKey(check, viewport && viewport.url);
|
|
2427
|
+
const statusEvidence = viewport && viewport.http_statuses && viewport.http_statuses[key];
|
|
2428
|
+
if (!statusEvidence || typeof statusEvidence !== "object" || Array.isArray(statusEvidence)) {
|
|
2429
|
+
return {
|
|
2430
|
+
viewport: viewport && viewport.name,
|
|
2431
|
+
key,
|
|
2432
|
+
url: httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2433
|
+
method: httpStatusMethod(check),
|
|
2434
|
+
ok: false,
|
|
2435
|
+
status: null,
|
|
2436
|
+
failures: [{ code: "http_status_evidence_missing" }],
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
const failures = [];
|
|
2440
|
+
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
2441
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
2442
|
+
failures.push({
|
|
2443
|
+
code: "http_status_failed",
|
|
2444
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2445
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
2446
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
2447
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
2448
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
2449
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
2450
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
2451
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2452
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2453
|
+
body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
|
|
2454
|
+
body_contains_missing: bodyContainsMissing,
|
|
2455
|
+
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
return {
|
|
2459
|
+
viewport: viewport && viewport.name,
|
|
2460
|
+
key,
|
|
2461
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2462
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
2463
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
2464
|
+
status_text: typeof statusEvidence.status_text === "string" ? statusEvidence.status_text : null,
|
|
2465
|
+
ok: failures.length === 0,
|
|
2466
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
2467
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
2468
|
+
content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
|
|
2469
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
2470
|
+
body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
|
|
2471
|
+
? statusEvidence.body_contains
|
|
2472
|
+
: null,
|
|
2473
|
+
body_contains_missing: bodyContainsMissing,
|
|
2474
|
+
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
2475
|
+
failures,
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2270
2478
|
function summarizeLinkStatusEvidence(viewport, check) {
|
|
2271
2479
|
const selector = linkStatusSelector(check);
|
|
2272
2480
|
const linkEvidence = viewport && viewport.link_statuses && viewport.link_statuses[selector];
|
|
@@ -3022,6 +3230,31 @@ function assessProfile(profile, evidence) {
|
|
|
3022
3230
|
});
|
|
3023
3231
|
continue;
|
|
3024
3232
|
}
|
|
3233
|
+
if (check.type === "http_status") {
|
|
3234
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
3235
|
+
const method = httpStatusMethod(check);
|
|
3236
|
+
const summaries = checkViewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
3237
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
3238
|
+
checks.push({
|
|
3239
|
+
type: check.type,
|
|
3240
|
+
label: check.label || check.type,
|
|
3241
|
+
status: failed.length ? "failed" : "passed",
|
|
3242
|
+
evidence: {
|
|
3243
|
+
url,
|
|
3244
|
+
method,
|
|
3245
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
3246
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
3247
|
+
min_bytes: check.min_bytes ?? null,
|
|
3248
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
3249
|
+
viewports: summaries,
|
|
3250
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
3251
|
+
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
3252
|
+
: []),
|
|
3253
|
+
},
|
|
3254
|
+
message: failed.length ? "HTTP status failed in " + failed.length + " viewport(s)." : undefined,
|
|
3255
|
+
});
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3025
3258
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
3026
3259
|
const selector = linkStatusSelector(check);
|
|
3027
3260
|
const summaries = checkViewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -4219,6 +4452,72 @@ function linkProbeResponseFields(response, method) {
|
|
|
4219
4452
|
content_length: contentLength,
|
|
4220
4453
|
};
|
|
4221
4454
|
}
|
|
4455
|
+
async function collectHttpStatus(check) {
|
|
4456
|
+
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
4457
|
+
const method = httpStatusMethod(check);
|
|
4458
|
+
const headers = check.headers && typeof check.headers === "object" && !Array.isArray(check.headers)
|
|
4459
|
+
? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim()))
|
|
4460
|
+
: {};
|
|
4461
|
+
let body;
|
|
4462
|
+
if (check.body_json !== undefined) {
|
|
4463
|
+
body = JSON.stringify(check.body_json);
|
|
4464
|
+
if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
|
|
4465
|
+
} else if (typeof check.body === "string") {
|
|
4466
|
+
body = check.body;
|
|
4467
|
+
}
|
|
4468
|
+
const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
4469
|
+
const options = {
|
|
4470
|
+
method,
|
|
4471
|
+
redirect: "follow",
|
|
4472
|
+
cache: "no-store",
|
|
4473
|
+
headers,
|
|
4474
|
+
};
|
|
4475
|
+
if (body !== undefined && method !== "GET" && method !== "HEAD") options.body = body;
|
|
4476
|
+
const result = {
|
|
4477
|
+
version: "riddle-proof.http-status.v1",
|
|
4478
|
+
url,
|
|
4479
|
+
method,
|
|
4480
|
+
status: null,
|
|
4481
|
+
ok: false,
|
|
4482
|
+
error: null,
|
|
4483
|
+
request_body_bytes: typeof body === "string" ? body.length : 0,
|
|
4484
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
4485
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
4486
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4487
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4488
|
+
};
|
|
4489
|
+
try {
|
|
4490
|
+
const response = await fetch(url, options);
|
|
4491
|
+
Object.assign(result, linkProbeResponseFields(response, method));
|
|
4492
|
+
result.url = url;
|
|
4493
|
+
result.status_text = response.statusText || "";
|
|
4494
|
+
const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0;
|
|
4495
|
+
if (shouldReadBody) {
|
|
4496
|
+
try {
|
|
4497
|
+
const buffer = await response.arrayBuffer();
|
|
4498
|
+
result.bytes = buffer.byteLength;
|
|
4499
|
+
if (bodyContains.length) {
|
|
4500
|
+
const text = new TextDecoder().decode(buffer);
|
|
4501
|
+
result.body_sample = text.slice(0, 1000);
|
|
4502
|
+
result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
|
|
4503
|
+
}
|
|
4504
|
+
} catch (error) {
|
|
4505
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
result.ok = linkProbeAllowed(result.status, check)
|
|
4509
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
4510
|
+
&& (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
4511
|
+
&& (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
|
|
4512
|
+
&& (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
|
|
4513
|
+
&& !result.error;
|
|
4514
|
+
return result;
|
|
4515
|
+
} catch (error) {
|
|
4516
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
4517
|
+
result.ok = false;
|
|
4518
|
+
return result;
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4222
4521
|
async function probeLinkStatus(candidate, check) {
|
|
4223
4522
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
4224
4523
|
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
@@ -4911,6 +5210,7 @@ async function captureViewport(viewport) {
|
|
|
4911
5210
|
const frames = {};
|
|
4912
5211
|
const text_sequences = {};
|
|
4913
5212
|
const text_matches = {};
|
|
5213
|
+
const http_statuses = {};
|
|
4914
5214
|
const link_statuses = {};
|
|
4915
5215
|
for (const check of profile.checks || []) {
|
|
4916
5216
|
if (!profileCheckAppliesToViewport(check, viewport)) continue;
|
|
@@ -4937,6 +5237,10 @@ async function captureViewport(viewport) {
|
|
|
4937
5237
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
4938
5238
|
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
4939
5239
|
}
|
|
5240
|
+
if (check.type === "http_status") {
|
|
5241
|
+
const key = httpStatusKey(check, page.url() || targetUrl);
|
|
5242
|
+
http_statuses[key] = http_statuses[key] || await collectHttpStatus(check);
|
|
5243
|
+
}
|
|
4940
5244
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
4941
5245
|
const selector = linkStatusSelector(check);
|
|
4942
5246
|
link_statuses[selector] = link_statuses[selector] || await collectLinkStatus(check);
|
|
@@ -5004,6 +5308,7 @@ async function captureViewport(viewport) {
|
|
|
5004
5308
|
frames,
|
|
5005
5309
|
text_sequences,
|
|
5006
5310
|
text_matches,
|
|
5311
|
+
http_statuses,
|
|
5007
5312
|
link_statuses,
|
|
5008
5313
|
route_inventory: routeInventory,
|
|
5009
5314
|
setup_action_results: setupActionResults,
|
|
@@ -5053,6 +5358,19 @@ function buildProfileEvidence(currentViewports) {
|
|
|
5053
5358
|
),
|
|
5054
5359
|
})),
|
|
5055
5360
|
})),
|
|
5361
|
+
http_status: currentViewports
|
|
5362
|
+
.filter((viewport) => viewport.http_statuses && Object.keys(viewport.http_statuses).length)
|
|
5363
|
+
.map((viewport) => ({
|
|
5364
|
+
viewport: viewport.name,
|
|
5365
|
+
requests: Object.entries(viewport.http_statuses || {}).map(([key, statusSet]) => ({
|
|
5366
|
+
key,
|
|
5367
|
+
url: statusSet && statusSet.url,
|
|
5368
|
+
method: statusSet && statusSet.method,
|
|
5369
|
+
status: statusSet && statusSet.status,
|
|
5370
|
+
ok: statusSet && statusSet.ok === true,
|
|
5371
|
+
error: statusSet && statusSet.error,
|
|
5372
|
+
})),
|
|
5373
|
+
})),
|
|
5056
5374
|
link_status: currentViewports
|
|
5057
5375
|
.filter((viewport) => viewport.link_statuses && Object.keys(viewport.link_statuses).length)
|
|
5058
5376
|
.map((viewport) => ({
|
package/dist/profile.d.cts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_call"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -114,6 +114,12 @@ interface RiddleProofProfileCheck {
|
|
|
114
114
|
expected_url?: string;
|
|
115
115
|
expected_routes?: RiddleProofProfileRouteInventoryRoute[];
|
|
116
116
|
selector?: string;
|
|
117
|
+
url?: string;
|
|
118
|
+
method?: string;
|
|
119
|
+
headers?: Record<string, string>;
|
|
120
|
+
body?: string;
|
|
121
|
+
body_json?: JsonValue;
|
|
122
|
+
body_contains?: string[];
|
|
117
123
|
expected_texts?: string[];
|
|
118
124
|
link_selector?: string;
|
|
119
125
|
source_selector?: string;
|
|
@@ -205,6 +211,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
205
211
|
frames?: Record<string, Record<string, JsonValue>>;
|
|
206
212
|
text_sequences?: Record<string, Record<string, JsonValue>>;
|
|
207
213
|
text_matches?: Record<string, boolean>;
|
|
214
|
+
http_statuses?: Record<string, Record<string, JsonValue>>;
|
|
208
215
|
link_statuses?: Record<string, Record<string, JsonValue>>;
|
|
209
216
|
route_inventory?: Record<string, JsonValue>;
|
|
210
217
|
setup_action_results?: Array<Record<string, JsonValue>>;
|
package/dist/profile.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ declare const RIDDLE_PROOF_PROFILE_VERSION: "riddle-proof.profile.v1";
|
|
|
4
4
|
declare const RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION: "riddle-proof.profile-evidence.v1";
|
|
5
5
|
declare const RIDDLE_PROOF_PROFILE_RESULT_VERSION: "riddle-proof.profile-result.v1";
|
|
6
6
|
declare const RIDDLE_PROOF_PROFILE_STATUSES: readonly ["passed", "product_regression", "proof_insufficient", "environment_blocked", "configuration_error", "needs_human_review"];
|
|
7
|
-
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
7
|
+
declare const RIDDLE_PROOF_PROFILE_CHECK_TYPES: readonly ["route_loaded", "url_search_param_equals", "url_search_param_absent", "selector_visible", "selector_absent", "selector_count_at_least", "selector_count_equals", "selector_count_equal", "selector_count_eq", "selector_text_visible", "selector_text_absent", "selector_text_order", "frame_text_visible", "frame_url_equals", "frame_url_matches", "frame_no_horizontal_overflow", "text_visible", "text_absent", "http_status", "link_status", "artifact_link_status", "route_inventory", "no_horizontal_overflow", "no_mobile_horizontal_overflow", "no_fatal_console_errors"];
|
|
8
8
|
declare const RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES: readonly ["click", "drag", "press", "fill", "set_input_value", "assert_text_visible", "assert_text_absent", "assert_selector_count", "assert_window_value", "assert_window_number", "local_storage", "session_storage", "clear_storage", "clear_console", "dialog_response", "screenshot", "wait", "wait_for_selector", "wait_for_text", "window_call"];
|
|
9
9
|
type RiddleProofProfileStatus = typeof RIDDLE_PROOF_PROFILE_STATUSES[number];
|
|
10
10
|
type RiddleProofProfileCheckType = typeof RIDDLE_PROOF_PROFILE_CHECK_TYPES[number];
|
|
@@ -114,6 +114,12 @@ interface RiddleProofProfileCheck {
|
|
|
114
114
|
expected_url?: string;
|
|
115
115
|
expected_routes?: RiddleProofProfileRouteInventoryRoute[];
|
|
116
116
|
selector?: string;
|
|
117
|
+
url?: string;
|
|
118
|
+
method?: string;
|
|
119
|
+
headers?: Record<string, string>;
|
|
120
|
+
body?: string;
|
|
121
|
+
body_json?: JsonValue;
|
|
122
|
+
body_contains?: string[];
|
|
117
123
|
expected_texts?: string[];
|
|
118
124
|
link_selector?: string;
|
|
119
125
|
source_selector?: string;
|
|
@@ -205,6 +211,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
205
211
|
frames?: Record<string, Record<string, JsonValue>>;
|
|
206
212
|
text_sequences?: Record<string, Record<string, JsonValue>>;
|
|
207
213
|
text_matches?: Record<string, boolean>;
|
|
214
|
+
http_statuses?: Record<string, Record<string, JsonValue>>;
|
|
208
215
|
link_statuses?: Record<string, Record<string, JsonValue>>;
|
|
209
216
|
route_inventory?: Record<string, JsonValue>;
|
|
210
217
|
setup_action_results?: Array<Record<string, JsonValue>>;
|
package/dist/profile.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveRiddleProofProfileTimeoutSec,
|
|
20
20
|
slugifyRiddleProofProfileName,
|
|
21
21
|
summarizeRiddleProofProfileResult
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-W5PBTTMJ.js";
|
|
23
23
|
export {
|
|
24
24
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
25
25
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|