@riddledc/riddle-proof 0.7.97 → 0.7.98
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-OMTWNL4B.js} +287 -14
- package/dist/cli.cjs +316 -14
- package/dist/cli.js +30 -1
- package/dist/index.cjs +287 -14
- package/dist/index.js +1 -1
- package/dist/profile.cjs +287 -14
- package/dist/profile.d.cts +7 -1
- package/dist/profile.d.ts +7 -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,16 +822,27 @@ 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;
|
|
@@ -851,6 +863,11 @@ function normalizeCheck(input, index) {
|
|
|
851
863
|
expected_url: expectedUrl,
|
|
852
864
|
expected_routes: expectedRoutes,
|
|
853
865
|
selector: stringValue(input.selector),
|
|
866
|
+
url: isHttpStatusCheck ? requestUrl : void 0,
|
|
867
|
+
method: isHttpStatusCheck ? method || "GET" : void 0,
|
|
868
|
+
headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
|
|
869
|
+
body: isHttpStatusCheck ? stringValue(input.body) : void 0,
|
|
870
|
+
body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
|
|
854
871
|
expected_texts: expectedTexts,
|
|
855
872
|
link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
|
|
856
873
|
source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
|
|
@@ -874,7 +891,7 @@ function normalizeCheck(input, index) {
|
|
|
874
891
|
max_links: maxLinks,
|
|
875
892
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
876
893
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
877
|
-
require_nonzero_bytes:
|
|
894
|
+
require_nonzero_bytes: isStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
878
895
|
min_bytes: minBytes,
|
|
879
896
|
allowed_content_types: allowedContentTypes,
|
|
880
897
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
@@ -973,14 +990,32 @@ function selectorKey(check) {
|
|
|
973
990
|
function linkStatusSelector(check) {
|
|
974
991
|
return check.selector || check.link_selector || "a[href]";
|
|
975
992
|
}
|
|
976
|
-
function
|
|
993
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
994
|
+
const rawUrl = check.url || "";
|
|
995
|
+
if (!rawUrl) return "";
|
|
996
|
+
try {
|
|
997
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
998
|
+
} catch {
|
|
999
|
+
return rawUrl;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
function httpStatusMethod(check) {
|
|
1003
|
+
return (check.method || "GET").toUpperCase();
|
|
1004
|
+
}
|
|
1005
|
+
function httpStatusKey(check, baseUrl) {
|
|
1006
|
+
return `${httpStatusMethod(check)} ${httpStatusRequestUrl(check, baseUrl)}`;
|
|
1007
|
+
}
|
|
1008
|
+
function httpStatusAllowedStatuses(check) {
|
|
977
1009
|
if (check.allowed_statuses?.length) return check.allowed_statuses;
|
|
978
1010
|
if (check.expected_status !== void 0) return [check.expected_status];
|
|
979
1011
|
return void 0;
|
|
980
1012
|
}
|
|
981
|
-
function
|
|
1013
|
+
function linkStatusAllowedStatuses(check) {
|
|
1014
|
+
return httpStatusAllowedStatuses(check);
|
|
1015
|
+
}
|
|
1016
|
+
function httpStatusIsAllowed(status, check) {
|
|
982
1017
|
if (status === void 0) return false;
|
|
983
|
-
const allowed =
|
|
1018
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
984
1019
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
985
1020
|
}
|
|
986
1021
|
function linkStatusObservedBytes(result) {
|
|
@@ -1006,7 +1041,7 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
1006
1041
|
}
|
|
1007
1042
|
function linkStatusResultOk(result, check) {
|
|
1008
1043
|
const status = numberValue(result.status);
|
|
1009
|
-
if (!
|
|
1044
|
+
if (!httpStatusIsAllowed(status, check)) return false;
|
|
1010
1045
|
if (stringValue(result.error)) return false;
|
|
1011
1046
|
if (result.ok === false) return false;
|
|
1012
1047
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -1020,6 +1055,54 @@ function linkStatusResultOk(result, check) {
|
|
|
1020
1055
|
}
|
|
1021
1056
|
return true;
|
|
1022
1057
|
}
|
|
1058
|
+
function httpStatusEvidenceForCheck(viewport, check) {
|
|
1059
|
+
const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
|
|
1060
|
+
return isRecord(evidence) ? evidence : void 0;
|
|
1061
|
+
}
|
|
1062
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
1063
|
+
const key = httpStatusKey(check, viewport.url);
|
|
1064
|
+
const statusEvidence = httpStatusEvidenceForCheck(viewport, check);
|
|
1065
|
+
if (!statusEvidence) {
|
|
1066
|
+
return {
|
|
1067
|
+
viewport: viewport.name,
|
|
1068
|
+
key,
|
|
1069
|
+
url: httpStatusRequestUrl(check, viewport.url),
|
|
1070
|
+
method: httpStatusMethod(check),
|
|
1071
|
+
ok: false,
|
|
1072
|
+
status: null,
|
|
1073
|
+
failures: [{ code: "http_status_evidence_missing" }]
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
const failures = [];
|
|
1077
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
1078
|
+
failures.push({
|
|
1079
|
+
code: "http_status_failed",
|
|
1080
|
+
url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
1081
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
1082
|
+
method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
|
|
1083
|
+
error: stringValue(statusEvidence.error) ?? null,
|
|
1084
|
+
content_type: stringValue(statusEvidence.content_type) ?? null,
|
|
1085
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
1086
|
+
allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
|
|
1087
|
+
min_bytes: check.min_bytes ?? null,
|
|
1088
|
+
allowed_content_types: check.allowed_content_types ?? null
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
viewport: viewport.name,
|
|
1093
|
+
key,
|
|
1094
|
+
url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
1095
|
+
method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
|
|
1096
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
1097
|
+
status_text: stringValue(statusEvidence.status_text) ?? null,
|
|
1098
|
+
ok: failures.length === 0,
|
|
1099
|
+
error: stringValue(statusEvidence.error) ?? null,
|
|
1100
|
+
content_type: stringValue(statusEvidence.content_type) ?? null,
|
|
1101
|
+
content_length: numberValue(statusEvidence.content_length) ?? null,
|
|
1102
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
1103
|
+
failures
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1023
1106
|
function linkStatusEvidenceForCheck(viewport, check) {
|
|
1024
1107
|
const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
|
|
1025
1108
|
return isRecord(evidence) ? evidence : void 0;
|
|
@@ -1610,6 +1693,28 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1610
1693
|
message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
|
|
1611
1694
|
};
|
|
1612
1695
|
}
|
|
1696
|
+
if (check.type === "http_status") {
|
|
1697
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
1698
|
+
const method = httpStatusMethod(check);
|
|
1699
|
+
const summaries = viewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
1700
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
1701
|
+
return {
|
|
1702
|
+
type: check.type,
|
|
1703
|
+
label: checkLabel(check),
|
|
1704
|
+
status: failed.length ? "failed" : "passed",
|
|
1705
|
+
evidence: {
|
|
1706
|
+
url,
|
|
1707
|
+
method,
|
|
1708
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
1709
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
1710
|
+
min_bytes: check.min_bytes ?? null,
|
|
1711
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1712
|
+
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
1713
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
1714
|
+
},
|
|
1715
|
+
message: failed.length ? `HTTP status failed in ${failed.length} viewport(s).` : void 0
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1613
1718
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
1614
1719
|
const selector = linkStatusSelector(check);
|
|
1615
1720
|
const summaries = viewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -2219,16 +2324,37 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
2219
2324
|
function linkStatusSelector(check) {
|
|
2220
2325
|
return check.selector || check.link_selector || "a[href]";
|
|
2221
2326
|
}
|
|
2222
|
-
function
|
|
2327
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
2328
|
+
const rawUrl = check.url || "";
|
|
2329
|
+
if (!rawUrl) return "";
|
|
2330
|
+
try {
|
|
2331
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
2332
|
+
} catch {
|
|
2333
|
+
return rawUrl;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
function httpStatusMethod(check) {
|
|
2337
|
+
return String(check.method || "GET").toUpperCase();
|
|
2338
|
+
}
|
|
2339
|
+
function httpStatusKey(check, baseUrl) {
|
|
2340
|
+
return httpStatusMethod(check) + " " + httpStatusRequestUrl(check, baseUrl);
|
|
2341
|
+
}
|
|
2342
|
+
function httpStatusAllowedStatuses(check) {
|
|
2223
2343
|
if (Array.isArray(check.allowed_statuses) && check.allowed_statuses.length) return check.allowed_statuses;
|
|
2224
2344
|
if (typeof check.expected_status === "number" && Number.isFinite(check.expected_status)) return [check.expected_status];
|
|
2225
2345
|
return undefined;
|
|
2226
2346
|
}
|
|
2227
|
-
function
|
|
2347
|
+
function linkStatusAllowedStatuses(check) {
|
|
2348
|
+
return httpStatusAllowedStatuses(check);
|
|
2349
|
+
}
|
|
2350
|
+
function httpStatusIsAllowed(status, check) {
|
|
2228
2351
|
if (typeof status !== "number" || !Number.isFinite(status)) return false;
|
|
2229
|
-
const allowed =
|
|
2352
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
2230
2353
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
2231
2354
|
}
|
|
2355
|
+
function linkStatusIsAllowed(status, check) {
|
|
2356
|
+
return httpStatusIsAllowed(status, check);
|
|
2357
|
+
}
|
|
2232
2358
|
function linkStatusObservedBytes(result) {
|
|
2233
2359
|
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
2234
2360
|
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
@@ -2253,7 +2379,7 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
2253
2379
|
}
|
|
2254
2380
|
function linkStatusResultOk(result, check) {
|
|
2255
2381
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2256
|
-
if (!
|
|
2382
|
+
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
2257
2383
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
2258
2384
|
if (result.ok === false) return false;
|
|
2259
2385
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -2267,6 +2393,50 @@ function linkStatusResultOk(result, check) {
|
|
|
2267
2393
|
}
|
|
2268
2394
|
return true;
|
|
2269
2395
|
}
|
|
2396
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
2397
|
+
const key = httpStatusKey(check, viewport && viewport.url);
|
|
2398
|
+
const statusEvidence = viewport && viewport.http_statuses && viewport.http_statuses[key];
|
|
2399
|
+
if (!statusEvidence || typeof statusEvidence !== "object" || Array.isArray(statusEvidence)) {
|
|
2400
|
+
return {
|
|
2401
|
+
viewport: viewport && viewport.name,
|
|
2402
|
+
key,
|
|
2403
|
+
url: httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2404
|
+
method: httpStatusMethod(check),
|
|
2405
|
+
ok: false,
|
|
2406
|
+
status: null,
|
|
2407
|
+
failures: [{ code: "http_status_evidence_missing" }],
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
const failures = [];
|
|
2411
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
2412
|
+
failures.push({
|
|
2413
|
+
code: "http_status_failed",
|
|
2414
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2415
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
2416
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
2417
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
2418
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
2419
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
2420
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
2421
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2422
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
return {
|
|
2426
|
+
viewport: viewport && viewport.name,
|
|
2427
|
+
key,
|
|
2428
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2429
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
2430
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
2431
|
+
status_text: typeof statusEvidence.status_text === "string" ? statusEvidence.status_text : null,
|
|
2432
|
+
ok: failures.length === 0,
|
|
2433
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
2434
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
2435
|
+
content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
|
|
2436
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
2437
|
+
failures,
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2270
2440
|
function summarizeLinkStatusEvidence(viewport, check) {
|
|
2271
2441
|
const selector = linkStatusSelector(check);
|
|
2272
2442
|
const linkEvidence = viewport && viewport.link_statuses && viewport.link_statuses[selector];
|
|
@@ -3022,6 +3192,31 @@ function assessProfile(profile, evidence) {
|
|
|
3022
3192
|
});
|
|
3023
3193
|
continue;
|
|
3024
3194
|
}
|
|
3195
|
+
if (check.type === "http_status") {
|
|
3196
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
3197
|
+
const method = httpStatusMethod(check);
|
|
3198
|
+
const summaries = checkViewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
3199
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
3200
|
+
checks.push({
|
|
3201
|
+
type: check.type,
|
|
3202
|
+
label: check.label || check.type,
|
|
3203
|
+
status: failed.length ? "failed" : "passed",
|
|
3204
|
+
evidence: {
|
|
3205
|
+
url,
|
|
3206
|
+
method,
|
|
3207
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
3208
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
3209
|
+
min_bytes: check.min_bytes ?? null,
|
|
3210
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
3211
|
+
viewports: summaries,
|
|
3212
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
3213
|
+
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
3214
|
+
: []),
|
|
3215
|
+
},
|
|
3216
|
+
message: failed.length ? "HTTP status failed in " + failed.length + " viewport(s)." : undefined,
|
|
3217
|
+
});
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3025
3220
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
3026
3221
|
const selector = linkStatusSelector(check);
|
|
3027
3222
|
const summaries = checkViewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -4219,6 +4414,65 @@ function linkProbeResponseFields(response, method) {
|
|
|
4219
4414
|
content_length: contentLength,
|
|
4220
4415
|
};
|
|
4221
4416
|
}
|
|
4417
|
+
async function collectHttpStatus(check) {
|
|
4418
|
+
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
4419
|
+
const method = httpStatusMethod(check);
|
|
4420
|
+
const headers = check.headers && typeof check.headers === "object" && !Array.isArray(check.headers)
|
|
4421
|
+
? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim()))
|
|
4422
|
+
: {};
|
|
4423
|
+
let body;
|
|
4424
|
+
if (check.body_json !== undefined) {
|
|
4425
|
+
body = JSON.stringify(check.body_json);
|
|
4426
|
+
if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
|
|
4427
|
+
} else if (typeof check.body === "string") {
|
|
4428
|
+
body = check.body;
|
|
4429
|
+
}
|
|
4430
|
+
const options = {
|
|
4431
|
+
method,
|
|
4432
|
+
redirect: "follow",
|
|
4433
|
+
cache: "no-store",
|
|
4434
|
+
headers,
|
|
4435
|
+
};
|
|
4436
|
+
if (body !== undefined && method !== "GET" && method !== "HEAD") options.body = body;
|
|
4437
|
+
const result = {
|
|
4438
|
+
version: "riddle-proof.http-status.v1",
|
|
4439
|
+
url,
|
|
4440
|
+
method,
|
|
4441
|
+
status: null,
|
|
4442
|
+
ok: false,
|
|
4443
|
+
error: null,
|
|
4444
|
+
request_body_bytes: typeof body === "string" ? body.length : 0,
|
|
4445
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
4446
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
4447
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4448
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4449
|
+
};
|
|
4450
|
+
try {
|
|
4451
|
+
const response = await fetch(url, options);
|
|
4452
|
+
Object.assign(result, linkProbeResponseFields(response, method));
|
|
4453
|
+
result.url = url;
|
|
4454
|
+
result.status_text = response.statusText || "";
|
|
4455
|
+
const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes));
|
|
4456
|
+
if (shouldReadBody) {
|
|
4457
|
+
try {
|
|
4458
|
+
const buffer = await response.arrayBuffer();
|
|
4459
|
+
result.bytes = buffer.byteLength;
|
|
4460
|
+
} catch (error) {
|
|
4461
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
result.ok = linkProbeAllowed(result.status, check)
|
|
4465
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
4466
|
+
&& (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
4467
|
+
&& (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
|
|
4468
|
+
&& !result.error;
|
|
4469
|
+
return result;
|
|
4470
|
+
} catch (error) {
|
|
4471
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
4472
|
+
result.ok = false;
|
|
4473
|
+
return result;
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4222
4476
|
async function probeLinkStatus(candidate, check) {
|
|
4223
4477
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
4224
4478
|
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
@@ -4911,6 +5165,7 @@ async function captureViewport(viewport) {
|
|
|
4911
5165
|
const frames = {};
|
|
4912
5166
|
const text_sequences = {};
|
|
4913
5167
|
const text_matches = {};
|
|
5168
|
+
const http_statuses = {};
|
|
4914
5169
|
const link_statuses = {};
|
|
4915
5170
|
for (const check of profile.checks || []) {
|
|
4916
5171
|
if (!profileCheckAppliesToViewport(check, viewport)) continue;
|
|
@@ -4937,6 +5192,10 @@ async function captureViewport(viewport) {
|
|
|
4937
5192
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
4938
5193
|
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
4939
5194
|
}
|
|
5195
|
+
if (check.type === "http_status") {
|
|
5196
|
+
const key = httpStatusKey(check, page.url() || targetUrl);
|
|
5197
|
+
http_statuses[key] = http_statuses[key] || await collectHttpStatus(check);
|
|
5198
|
+
}
|
|
4940
5199
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
4941
5200
|
const selector = linkStatusSelector(check);
|
|
4942
5201
|
link_statuses[selector] = link_statuses[selector] || await collectLinkStatus(check);
|
|
@@ -5004,6 +5263,7 @@ async function captureViewport(viewport) {
|
|
|
5004
5263
|
frames,
|
|
5005
5264
|
text_sequences,
|
|
5006
5265
|
text_matches,
|
|
5266
|
+
http_statuses,
|
|
5007
5267
|
link_statuses,
|
|
5008
5268
|
route_inventory: routeInventory,
|
|
5009
5269
|
setup_action_results: setupActionResults,
|
|
@@ -5053,6 +5313,19 @@ function buildProfileEvidence(currentViewports) {
|
|
|
5053
5313
|
),
|
|
5054
5314
|
})),
|
|
5055
5315
|
})),
|
|
5316
|
+
http_status: currentViewports
|
|
5317
|
+
.filter((viewport) => viewport.http_statuses && Object.keys(viewport.http_statuses).length)
|
|
5318
|
+
.map((viewport) => ({
|
|
5319
|
+
viewport: viewport.name,
|
|
5320
|
+
requests: Object.entries(viewport.http_statuses || {}).map(([key, statusSet]) => ({
|
|
5321
|
+
key,
|
|
5322
|
+
url: statusSet && statusSet.url,
|
|
5323
|
+
method: statusSet && statusSet.method,
|
|
5324
|
+
status: statusSet && statusSet.status,
|
|
5325
|
+
ok: statusSet && statusSet.ok === true,
|
|
5326
|
+
error: statusSet && statusSet.error,
|
|
5327
|
+
})),
|
|
5328
|
+
})),
|
|
5056
5329
|
link_status: currentViewports
|
|
5057
5330
|
.filter((viewport) => viewport.link_statuses && Object.keys(viewport.link_statuses).length)
|
|
5058
5331
|
.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,11 @@ 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;
|
|
117
122
|
expected_texts?: string[];
|
|
118
123
|
link_selector?: string;
|
|
119
124
|
source_selector?: string;
|
|
@@ -205,6 +210,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
205
210
|
frames?: Record<string, Record<string, JsonValue>>;
|
|
206
211
|
text_sequences?: Record<string, Record<string, JsonValue>>;
|
|
207
212
|
text_matches?: Record<string, boolean>;
|
|
213
|
+
http_statuses?: Record<string, Record<string, JsonValue>>;
|
|
208
214
|
link_statuses?: Record<string, Record<string, JsonValue>>;
|
|
209
215
|
route_inventory?: Record<string, JsonValue>;
|
|
210
216
|
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,11 @@ 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;
|
|
117
122
|
expected_texts?: string[];
|
|
118
123
|
link_selector?: string;
|
|
119
124
|
source_selector?: string;
|
|
@@ -205,6 +210,7 @@ interface RiddleProofProfileViewportEvidence {
|
|
|
205
210
|
frames?: Record<string, Record<string, JsonValue>>;
|
|
206
211
|
text_sequences?: Record<string, Record<string, JsonValue>>;
|
|
207
212
|
text_matches?: Record<string, boolean>;
|
|
213
|
+
http_statuses?: Record<string, Record<string, JsonValue>>;
|
|
208
214
|
link_statuses?: Record<string, Record<string, JsonValue>>;
|
|
209
215
|
route_inventory?: Record<string, JsonValue>;
|
|
210
216
|
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-OMTWNL4B.js";
|
|
23
23
|
export {
|
|
24
24
|
RIDDLE_PROOF_PROFILE_CHECK_TYPES,
|
|
25
25
|
RIDDLE_PROOF_PROFILE_EVIDENCE_VERSION,
|