@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
|
@@ -29,6 +29,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
29
29
|
"frame_no_horizontal_overflow",
|
|
30
30
|
"text_visible",
|
|
31
31
|
"text_absent",
|
|
32
|
+
"http_status",
|
|
32
33
|
"link_status",
|
|
33
34
|
"artifact_link_status",
|
|
34
35
|
"route_inventory",
|
|
@@ -778,16 +779,27 @@ function normalizeCheck(input, index) {
|
|
|
778
779
|
if (type === "route_inventory" && !expectedRoutes?.length) {
|
|
779
780
|
throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
|
|
780
781
|
}
|
|
782
|
+
const isHttpStatusCheck = type === "http_status";
|
|
781
783
|
const isLinkStatusCheck = type === "link_status" || type === "artifact_link_status";
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
+
const isStatusCheck = isHttpStatusCheck || isLinkStatusCheck;
|
|
785
|
+
const requestUrl = stringFromOwn(input, "url", "endpoint_url", "endpointUrl", "endpoint", "request_url", "requestUrl", "path");
|
|
786
|
+
if (isHttpStatusCheck && !requestUrl) {
|
|
787
|
+
throw new Error(`checks[${index}] http_status requires url.`);
|
|
788
|
+
}
|
|
789
|
+
const method = stringFromOwn(input, "method", "http_method", "httpMethod")?.toUpperCase();
|
|
790
|
+
if (isHttpStatusCheck && method && !/^[A-Z]+$/.test(method)) {
|
|
791
|
+
throw new Error(`checks[${index}] http_status method must contain only letters.`);
|
|
792
|
+
}
|
|
793
|
+
const hasBodyJson = hasOwn(input, "body_json") || hasOwn(input, "bodyJson") || hasOwn(input, "json");
|
|
794
|
+
const expectedStatus = isStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
|
|
795
|
+
const allowedStatuses = isStatusCheck ? normalizeHttpStatuses(
|
|
784
796
|
input.allowed_statuses ?? input.allowedStatuses ?? input.expected_statuses ?? input.expectedStatuses,
|
|
785
797
|
`checks[${index}] allowed_statuses`
|
|
786
798
|
) : void 0;
|
|
787
799
|
const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
|
|
788
|
-
const minBytes =
|
|
789
|
-
const expectedContentType =
|
|
790
|
-
const allowedContentTypes =
|
|
800
|
+
const minBytes = isStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
|
|
801
|
+
const expectedContentType = isStatusCheck ? stringValue(input.expected_content_type) || stringValue(input.expectedContentType) || stringValue(input.content_type) || stringValue(input.contentType) : void 0;
|
|
802
|
+
const allowedContentTypes = isStatusCheck ? normalizeStringList(
|
|
791
803
|
input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
|
|
792
804
|
`checks[${index}] allowed_content_types`
|
|
793
805
|
) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
|
|
@@ -808,6 +820,11 @@ function normalizeCheck(input, index) {
|
|
|
808
820
|
expected_url: expectedUrl,
|
|
809
821
|
expected_routes: expectedRoutes,
|
|
810
822
|
selector: stringValue(input.selector),
|
|
823
|
+
url: isHttpStatusCheck ? requestUrl : void 0,
|
|
824
|
+
method: isHttpStatusCheck ? method || "GET" : void 0,
|
|
825
|
+
headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
|
|
826
|
+
body: isHttpStatusCheck ? stringValue(input.body) : void 0,
|
|
827
|
+
body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
|
|
811
828
|
expected_texts: expectedTexts,
|
|
812
829
|
link_selector: stringValue(input.link_selector) || stringValue(input.linkSelector),
|
|
813
830
|
source_selector: stringValue(input.source_selector) || stringValue(input.sourceSelector),
|
|
@@ -831,7 +848,7 @@ function normalizeCheck(input, index) {
|
|
|
831
848
|
max_links: maxLinks,
|
|
832
849
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
833
850
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
834
|
-
require_nonzero_bytes:
|
|
851
|
+
require_nonzero_bytes: isStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
835
852
|
min_bytes: minBytes,
|
|
836
853
|
allowed_content_types: allowedContentTypes,
|
|
837
854
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
@@ -930,14 +947,32 @@ function selectorKey(check) {
|
|
|
930
947
|
function linkStatusSelector(check) {
|
|
931
948
|
return check.selector || check.link_selector || "a[href]";
|
|
932
949
|
}
|
|
933
|
-
function
|
|
950
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
951
|
+
const rawUrl = check.url || "";
|
|
952
|
+
if (!rawUrl) return "";
|
|
953
|
+
try {
|
|
954
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
955
|
+
} catch {
|
|
956
|
+
return rawUrl;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function httpStatusMethod(check) {
|
|
960
|
+
return (check.method || "GET").toUpperCase();
|
|
961
|
+
}
|
|
962
|
+
function httpStatusKey(check, baseUrl) {
|
|
963
|
+
return `${httpStatusMethod(check)} ${httpStatusRequestUrl(check, baseUrl)}`;
|
|
964
|
+
}
|
|
965
|
+
function httpStatusAllowedStatuses(check) {
|
|
934
966
|
if (check.allowed_statuses?.length) return check.allowed_statuses;
|
|
935
967
|
if (check.expected_status !== void 0) return [check.expected_status];
|
|
936
968
|
return void 0;
|
|
937
969
|
}
|
|
938
|
-
function
|
|
970
|
+
function linkStatusAllowedStatuses(check) {
|
|
971
|
+
return httpStatusAllowedStatuses(check);
|
|
972
|
+
}
|
|
973
|
+
function httpStatusIsAllowed(status, check) {
|
|
939
974
|
if (status === void 0) return false;
|
|
940
|
-
const allowed =
|
|
975
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
941
976
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
942
977
|
}
|
|
943
978
|
function linkStatusObservedBytes(result) {
|
|
@@ -963,7 +998,7 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
963
998
|
}
|
|
964
999
|
function linkStatusResultOk(result, check) {
|
|
965
1000
|
const status = numberValue(result.status);
|
|
966
|
-
if (!
|
|
1001
|
+
if (!httpStatusIsAllowed(status, check)) return false;
|
|
967
1002
|
if (stringValue(result.error)) return false;
|
|
968
1003
|
if (result.ok === false) return false;
|
|
969
1004
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -977,6 +1012,54 @@ function linkStatusResultOk(result, check) {
|
|
|
977
1012
|
}
|
|
978
1013
|
return true;
|
|
979
1014
|
}
|
|
1015
|
+
function httpStatusEvidenceForCheck(viewport, check) {
|
|
1016
|
+
const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
|
|
1017
|
+
return isRecord(evidence) ? evidence : void 0;
|
|
1018
|
+
}
|
|
1019
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
1020
|
+
const key = httpStatusKey(check, viewport.url);
|
|
1021
|
+
const statusEvidence = httpStatusEvidenceForCheck(viewport, check);
|
|
1022
|
+
if (!statusEvidence) {
|
|
1023
|
+
return {
|
|
1024
|
+
viewport: viewport.name,
|
|
1025
|
+
key,
|
|
1026
|
+
url: httpStatusRequestUrl(check, viewport.url),
|
|
1027
|
+
method: httpStatusMethod(check),
|
|
1028
|
+
ok: false,
|
|
1029
|
+
status: null,
|
|
1030
|
+
failures: [{ code: "http_status_evidence_missing" }]
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
const failures = [];
|
|
1034
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
1035
|
+
failures.push({
|
|
1036
|
+
code: "http_status_failed",
|
|
1037
|
+
url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
1038
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
1039
|
+
method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
|
|
1040
|
+
error: stringValue(statusEvidence.error) ?? null,
|
|
1041
|
+
content_type: stringValue(statusEvidence.content_type) ?? null,
|
|
1042
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
1043
|
+
allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
|
|
1044
|
+
min_bytes: check.min_bytes ?? null,
|
|
1045
|
+
allowed_content_types: check.allowed_content_types ?? null
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
viewport: viewport.name,
|
|
1050
|
+
key,
|
|
1051
|
+
url: stringValue(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
1052
|
+
method: stringValue(statusEvidence.method) ?? httpStatusMethod(check),
|
|
1053
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
1054
|
+
status_text: stringValue(statusEvidence.status_text) ?? null,
|
|
1055
|
+
ok: failures.length === 0,
|
|
1056
|
+
error: stringValue(statusEvidence.error) ?? null,
|
|
1057
|
+
content_type: stringValue(statusEvidence.content_type) ?? null,
|
|
1058
|
+
content_length: numberValue(statusEvidence.content_length) ?? null,
|
|
1059
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
1060
|
+
failures
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
980
1063
|
function linkStatusEvidenceForCheck(viewport, check) {
|
|
981
1064
|
const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
|
|
982
1065
|
return isRecord(evidence) ? evidence : void 0;
|
|
@@ -1567,6 +1650,28 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
1567
1650
|
message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
|
|
1568
1651
|
};
|
|
1569
1652
|
}
|
|
1653
|
+
if (check.type === "http_status") {
|
|
1654
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
1655
|
+
const method = httpStatusMethod(check);
|
|
1656
|
+
const summaries = viewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
1657
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
1658
|
+
return {
|
|
1659
|
+
type: check.type,
|
|
1660
|
+
label: checkLabel(check),
|
|
1661
|
+
status: failed.length ? "failed" : "passed",
|
|
1662
|
+
evidence: {
|
|
1663
|
+
url,
|
|
1664
|
+
method,
|
|
1665
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
1666
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
1667
|
+
min_bytes: check.min_bytes ?? null,
|
|
1668
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
1669
|
+
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
1670
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue(summary.viewport) ?? null, failure })) : [])
|
|
1671
|
+
},
|
|
1672
|
+
message: failed.length ? `HTTP status failed in ${failed.length} viewport(s).` : void 0
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1570
1675
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
1571
1676
|
const selector = linkStatusSelector(check);
|
|
1572
1677
|
const summaries = viewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -2176,16 +2281,37 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
2176
2281
|
function linkStatusSelector(check) {
|
|
2177
2282
|
return check.selector || check.link_selector || "a[href]";
|
|
2178
2283
|
}
|
|
2179
|
-
function
|
|
2284
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
2285
|
+
const rawUrl = check.url || "";
|
|
2286
|
+
if (!rawUrl) return "";
|
|
2287
|
+
try {
|
|
2288
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
2289
|
+
} catch {
|
|
2290
|
+
return rawUrl;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
function httpStatusMethod(check) {
|
|
2294
|
+
return String(check.method || "GET").toUpperCase();
|
|
2295
|
+
}
|
|
2296
|
+
function httpStatusKey(check, baseUrl) {
|
|
2297
|
+
return httpStatusMethod(check) + " " + httpStatusRequestUrl(check, baseUrl);
|
|
2298
|
+
}
|
|
2299
|
+
function httpStatusAllowedStatuses(check) {
|
|
2180
2300
|
if (Array.isArray(check.allowed_statuses) && check.allowed_statuses.length) return check.allowed_statuses;
|
|
2181
2301
|
if (typeof check.expected_status === "number" && Number.isFinite(check.expected_status)) return [check.expected_status];
|
|
2182
2302
|
return undefined;
|
|
2183
2303
|
}
|
|
2184
|
-
function
|
|
2304
|
+
function linkStatusAllowedStatuses(check) {
|
|
2305
|
+
return httpStatusAllowedStatuses(check);
|
|
2306
|
+
}
|
|
2307
|
+
function httpStatusIsAllowed(status, check) {
|
|
2185
2308
|
if (typeof status !== "number" || !Number.isFinite(status)) return false;
|
|
2186
|
-
const allowed =
|
|
2309
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
2187
2310
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
2188
2311
|
}
|
|
2312
|
+
function linkStatusIsAllowed(status, check) {
|
|
2313
|
+
return httpStatusIsAllowed(status, check);
|
|
2314
|
+
}
|
|
2189
2315
|
function linkStatusObservedBytes(result) {
|
|
2190
2316
|
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
2191
2317
|
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
@@ -2210,7 +2336,7 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
2210
2336
|
}
|
|
2211
2337
|
function linkStatusResultOk(result, check) {
|
|
2212
2338
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
2213
|
-
if (!
|
|
2339
|
+
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
2214
2340
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
2215
2341
|
if (result.ok === false) return false;
|
|
2216
2342
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -2224,6 +2350,50 @@ function linkStatusResultOk(result, check) {
|
|
|
2224
2350
|
}
|
|
2225
2351
|
return true;
|
|
2226
2352
|
}
|
|
2353
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
2354
|
+
const key = httpStatusKey(check, viewport && viewport.url);
|
|
2355
|
+
const statusEvidence = viewport && viewport.http_statuses && viewport.http_statuses[key];
|
|
2356
|
+
if (!statusEvidence || typeof statusEvidence !== "object" || Array.isArray(statusEvidence)) {
|
|
2357
|
+
return {
|
|
2358
|
+
viewport: viewport && viewport.name,
|
|
2359
|
+
key,
|
|
2360
|
+
url: httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2361
|
+
method: httpStatusMethod(check),
|
|
2362
|
+
ok: false,
|
|
2363
|
+
status: null,
|
|
2364
|
+
failures: [{ code: "http_status_evidence_missing" }],
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
const failures = [];
|
|
2368
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
2369
|
+
failures.push({
|
|
2370
|
+
code: "http_status_failed",
|
|
2371
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2372
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
2373
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
2374
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
2375
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
2376
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
2377
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
2378
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
2379
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
return {
|
|
2383
|
+
viewport: viewport && viewport.name,
|
|
2384
|
+
key,
|
|
2385
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
2386
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
2387
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
2388
|
+
status_text: typeof statusEvidence.status_text === "string" ? statusEvidence.status_text : null,
|
|
2389
|
+
ok: failures.length === 0,
|
|
2390
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
2391
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
2392
|
+
content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
|
|
2393
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
2394
|
+
failures,
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2227
2397
|
function summarizeLinkStatusEvidence(viewport, check) {
|
|
2228
2398
|
const selector = linkStatusSelector(check);
|
|
2229
2399
|
const linkEvidence = viewport && viewport.link_statuses && viewport.link_statuses[selector];
|
|
@@ -2979,6 +3149,31 @@ function assessProfile(profile, evidence) {
|
|
|
2979
3149
|
});
|
|
2980
3150
|
continue;
|
|
2981
3151
|
}
|
|
3152
|
+
if (check.type === "http_status") {
|
|
3153
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
3154
|
+
const method = httpStatusMethod(check);
|
|
3155
|
+
const summaries = checkViewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
3156
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
3157
|
+
checks.push({
|
|
3158
|
+
type: check.type,
|
|
3159
|
+
label: check.label || check.type,
|
|
3160
|
+
status: failed.length ? "failed" : "passed",
|
|
3161
|
+
evidence: {
|
|
3162
|
+
url,
|
|
3163
|
+
method,
|
|
3164
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
3165
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
3166
|
+
min_bytes: check.min_bytes ?? null,
|
|
3167
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
3168
|
+
viewports: summaries,
|
|
3169
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
3170
|
+
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
3171
|
+
: []),
|
|
3172
|
+
},
|
|
3173
|
+
message: failed.length ? "HTTP status failed in " + failed.length + " viewport(s)." : undefined,
|
|
3174
|
+
});
|
|
3175
|
+
continue;
|
|
3176
|
+
}
|
|
2982
3177
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
2983
3178
|
const selector = linkStatusSelector(check);
|
|
2984
3179
|
const summaries = checkViewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -4176,6 +4371,65 @@ function linkProbeResponseFields(response, method) {
|
|
|
4176
4371
|
content_length: contentLength,
|
|
4177
4372
|
};
|
|
4178
4373
|
}
|
|
4374
|
+
async function collectHttpStatus(check) {
|
|
4375
|
+
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
4376
|
+
const method = httpStatusMethod(check);
|
|
4377
|
+
const headers = check.headers && typeof check.headers === "object" && !Array.isArray(check.headers)
|
|
4378
|
+
? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim()))
|
|
4379
|
+
: {};
|
|
4380
|
+
let body;
|
|
4381
|
+
if (check.body_json !== undefined) {
|
|
4382
|
+
body = JSON.stringify(check.body_json);
|
|
4383
|
+
if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
|
|
4384
|
+
} else if (typeof check.body === "string") {
|
|
4385
|
+
body = check.body;
|
|
4386
|
+
}
|
|
4387
|
+
const options = {
|
|
4388
|
+
method,
|
|
4389
|
+
redirect: "follow",
|
|
4390
|
+
cache: "no-store",
|
|
4391
|
+
headers,
|
|
4392
|
+
};
|
|
4393
|
+
if (body !== undefined && method !== "GET" && method !== "HEAD") options.body = body;
|
|
4394
|
+
const result = {
|
|
4395
|
+
version: "riddle-proof.http-status.v1",
|
|
4396
|
+
url,
|
|
4397
|
+
method,
|
|
4398
|
+
status: null,
|
|
4399
|
+
ok: false,
|
|
4400
|
+
error: null,
|
|
4401
|
+
request_body_bytes: typeof body === "string" ? body.length : 0,
|
|
4402
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
4403
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
4404
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
4405
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
4406
|
+
};
|
|
4407
|
+
try {
|
|
4408
|
+
const response = await fetch(url, options);
|
|
4409
|
+
Object.assign(result, linkProbeResponseFields(response, method));
|
|
4410
|
+
result.url = url;
|
|
4411
|
+
result.status_text = response.statusText || "";
|
|
4412
|
+
const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes));
|
|
4413
|
+
if (shouldReadBody) {
|
|
4414
|
+
try {
|
|
4415
|
+
const buffer = await response.arrayBuffer();
|
|
4416
|
+
result.bytes = buffer.byteLength;
|
|
4417
|
+
} catch (error) {
|
|
4418
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
result.ok = linkProbeAllowed(result.status, check)
|
|
4422
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
4423
|
+
&& (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
4424
|
+
&& (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
|
|
4425
|
+
&& !result.error;
|
|
4426
|
+
return result;
|
|
4427
|
+
} catch (error) {
|
|
4428
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
4429
|
+
result.ok = false;
|
|
4430
|
+
return result;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4179
4433
|
async function probeLinkStatus(candidate, check) {
|
|
4180
4434
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
4181
4435
|
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
@@ -4868,6 +5122,7 @@ async function captureViewport(viewport) {
|
|
|
4868
5122
|
const frames = {};
|
|
4869
5123
|
const text_sequences = {};
|
|
4870
5124
|
const text_matches = {};
|
|
5125
|
+
const http_statuses = {};
|
|
4871
5126
|
const link_statuses = {};
|
|
4872
5127
|
for (const check of profile.checks || []) {
|
|
4873
5128
|
if (!profileCheckAppliesToViewport(check, viewport)) continue;
|
|
@@ -4894,6 +5149,10 @@ async function captureViewport(viewport) {
|
|
|
4894
5149
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
4895
5150
|
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
4896
5151
|
}
|
|
5152
|
+
if (check.type === "http_status") {
|
|
5153
|
+
const key = httpStatusKey(check, page.url() || targetUrl);
|
|
5154
|
+
http_statuses[key] = http_statuses[key] || await collectHttpStatus(check);
|
|
5155
|
+
}
|
|
4897
5156
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
4898
5157
|
const selector = linkStatusSelector(check);
|
|
4899
5158
|
link_statuses[selector] = link_statuses[selector] || await collectLinkStatus(check);
|
|
@@ -4961,6 +5220,7 @@ async function captureViewport(viewport) {
|
|
|
4961
5220
|
frames,
|
|
4962
5221
|
text_sequences,
|
|
4963
5222
|
text_matches,
|
|
5223
|
+
http_statuses,
|
|
4964
5224
|
link_statuses,
|
|
4965
5225
|
route_inventory: routeInventory,
|
|
4966
5226
|
setup_action_results: setupActionResults,
|
|
@@ -5010,6 +5270,19 @@ function buildProfileEvidence(currentViewports) {
|
|
|
5010
5270
|
),
|
|
5011
5271
|
})),
|
|
5012
5272
|
})),
|
|
5273
|
+
http_status: currentViewports
|
|
5274
|
+
.filter((viewport) => viewport.http_statuses && Object.keys(viewport.http_statuses).length)
|
|
5275
|
+
.map((viewport) => ({
|
|
5276
|
+
viewport: viewport.name,
|
|
5277
|
+
requests: Object.entries(viewport.http_statuses || {}).map(([key, statusSet]) => ({
|
|
5278
|
+
key,
|
|
5279
|
+
url: statusSet && statusSet.url,
|
|
5280
|
+
method: statusSet && statusSet.method,
|
|
5281
|
+
status: statusSet && statusSet.status,
|
|
5282
|
+
ok: statusSet && statusSet.ok === true,
|
|
5283
|
+
error: statusSet && statusSet.error,
|
|
5284
|
+
})),
|
|
5285
|
+
})),
|
|
5013
5286
|
link_status: currentViewports
|
|
5014
5287
|
.filter((viewport) => viewport.link_statuses && Object.keys(viewport.link_statuses).length)
|
|
5015
5288
|
.map((viewport) => ({
|