@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/cli.cjs
CHANGED
|
@@ -6922,6 +6922,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
|
|
|
6922
6922
|
"frame_no_horizontal_overflow",
|
|
6923
6923
|
"text_visible",
|
|
6924
6924
|
"text_absent",
|
|
6925
|
+
"http_status",
|
|
6925
6926
|
"link_status",
|
|
6926
6927
|
"artifact_link_status",
|
|
6927
6928
|
"route_inventory",
|
|
@@ -7671,19 +7672,34 @@ function normalizeCheck(input, index) {
|
|
|
7671
7672
|
if (type === "route_inventory" && !expectedRoutes?.length) {
|
|
7672
7673
|
throw new Error(`checks[${index}] route_inventory requires expected_routes.`);
|
|
7673
7674
|
}
|
|
7675
|
+
const isHttpStatusCheck = type === "http_status";
|
|
7674
7676
|
const isLinkStatusCheck = type === "link_status" || type === "artifact_link_status";
|
|
7675
|
-
const
|
|
7676
|
-
const
|
|
7677
|
+
const isStatusCheck = isHttpStatusCheck || isLinkStatusCheck;
|
|
7678
|
+
const requestUrl = stringFromOwn(input, "url", "endpoint_url", "endpointUrl", "endpoint", "request_url", "requestUrl", "path");
|
|
7679
|
+
if (isHttpStatusCheck && !requestUrl) {
|
|
7680
|
+
throw new Error(`checks[${index}] http_status requires url.`);
|
|
7681
|
+
}
|
|
7682
|
+
const method = stringFromOwn(input, "method", "http_method", "httpMethod")?.toUpperCase();
|
|
7683
|
+
if (isHttpStatusCheck && method && !/^[A-Z]+$/.test(method)) {
|
|
7684
|
+
throw new Error(`checks[${index}] http_status method must contain only letters.`);
|
|
7685
|
+
}
|
|
7686
|
+
const hasBodyJson = hasOwn(input, "body_json") || hasOwn(input, "bodyJson") || hasOwn(input, "json");
|
|
7687
|
+
const expectedStatus = isStatusCheck ? normalizeHttpStatus(input.expected_status ?? input.expectedStatus ?? input.status, `checks[${index}] expected_status`) : void 0;
|
|
7688
|
+
const allowedStatuses = isStatusCheck ? normalizeHttpStatuses(
|
|
7677
7689
|
input.allowed_statuses ?? input.allowedStatuses ?? input.expected_statuses ?? input.expectedStatuses,
|
|
7678
7690
|
`checks[${index}] allowed_statuses`
|
|
7679
7691
|
) : void 0;
|
|
7680
7692
|
const maxLinks = isLinkStatusCheck ? normalizePositiveInteger(input.max_links ?? input.maxLinks ?? input.limit, `checks[${index}] max_links`, 500) : void 0;
|
|
7681
|
-
const minBytes =
|
|
7682
|
-
const expectedContentType =
|
|
7683
|
-
const allowedContentTypes =
|
|
7693
|
+
const minBytes = isStatusCheck ? normalizePositiveInteger(input.min_bytes ?? input.minBytes ?? input.min_response_bytes ?? input.minResponseBytes, `checks[${index}] min_bytes`) : void 0;
|
|
7694
|
+
const expectedContentType = isStatusCheck ? stringValue2(input.expected_content_type) || stringValue2(input.expectedContentType) || stringValue2(input.content_type) || stringValue2(input.contentType) : void 0;
|
|
7695
|
+
const allowedContentTypes = isStatusCheck ? normalizeStringList(
|
|
7684
7696
|
input.allowed_content_types ?? input.allowedContentTypes ?? input.expected_content_types ?? input.expectedContentTypes,
|
|
7685
7697
|
`checks[${index}] allowed_content_types`
|
|
7686
7698
|
) ?? (expectedContentType ? [expectedContentType] : void 0) : void 0;
|
|
7699
|
+
const bodyContains = isHttpStatusCheck ? normalizeStringList(
|
|
7700
|
+
input.body_contains ?? input.bodyContains ?? input.expected_body_contains ?? input.expectedBodyContains ?? input.response_body_contains ?? input.responseBodyContains ?? input.body_includes ?? input.bodyIncludes,
|
|
7701
|
+
`checks[${index}] body_contains`
|
|
7702
|
+
) : void 0;
|
|
7687
7703
|
if (isLinkStatusCheck) {
|
|
7688
7704
|
if (minCount !== void 0 && (!Number.isInteger(minCount) || minCount < 0)) {
|
|
7689
7705
|
throw new Error(`checks[${index}] ${type} min_count must be a non-negative integer.`);
|
|
@@ -7701,6 +7717,12 @@ function normalizeCheck(input, index) {
|
|
|
7701
7717
|
expected_url: expectedUrl,
|
|
7702
7718
|
expected_routes: expectedRoutes,
|
|
7703
7719
|
selector: stringValue2(input.selector),
|
|
7720
|
+
url: isHttpStatusCheck ? requestUrl : void 0,
|
|
7721
|
+
method: isHttpStatusCheck ? method || "GET" : void 0,
|
|
7722
|
+
headers: isHttpStatusCheck ? stringRecord(input.headers) : void 0,
|
|
7723
|
+
body: isHttpStatusCheck ? stringValue2(input.body) : void 0,
|
|
7724
|
+
body_json: isHttpStatusCheck && hasBodyJson ? toJsonValue(input.body_json ?? input.bodyJson ?? input.json) : void 0,
|
|
7725
|
+
body_contains: bodyContains,
|
|
7704
7726
|
expected_texts: expectedTexts,
|
|
7705
7727
|
link_selector: stringValue2(input.link_selector) || stringValue2(input.linkSelector),
|
|
7706
7728
|
source_selector: stringValue2(input.source_selector) || stringValue2(input.sourceSelector),
|
|
@@ -7724,7 +7746,7 @@ function normalizeCheck(input, index) {
|
|
|
7724
7746
|
max_links: maxLinks,
|
|
7725
7747
|
same_origin_only: isLinkStatusCheck ? input.same_origin_only === true || input.sameOriginOnly === true : void 0,
|
|
7726
7748
|
dedupe: isLinkStatusCheck ? input.dedupe === false ? false : true : void 0,
|
|
7727
|
-
require_nonzero_bytes:
|
|
7749
|
+
require_nonzero_bytes: isStatusCheck ? input.require_nonzero_bytes === true || input.requireNonzeroBytes === true : void 0,
|
|
7728
7750
|
min_bytes: minBytes,
|
|
7729
7751
|
allowed_content_types: allowedContentTypes,
|
|
7730
7752
|
allow_get_fallback: isLinkStatusCheck ? input.allow_get_fallback === false || input.allowGetFallback === false ? false : true : void 0,
|
|
@@ -7823,14 +7845,32 @@ function selectorKey(check) {
|
|
|
7823
7845
|
function linkStatusSelector(check) {
|
|
7824
7846
|
return check.selector || check.link_selector || "a[href]";
|
|
7825
7847
|
}
|
|
7826
|
-
function
|
|
7848
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
7849
|
+
const rawUrl = check.url || "";
|
|
7850
|
+
if (!rawUrl) return "";
|
|
7851
|
+
try {
|
|
7852
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
7853
|
+
} catch {
|
|
7854
|
+
return rawUrl;
|
|
7855
|
+
}
|
|
7856
|
+
}
|
|
7857
|
+
function httpStatusMethod(check) {
|
|
7858
|
+
return (check.method || "GET").toUpperCase();
|
|
7859
|
+
}
|
|
7860
|
+
function httpStatusKey(check, baseUrl) {
|
|
7861
|
+
return `${httpStatusMethod(check)} ${httpStatusRequestUrl(check, baseUrl)}`;
|
|
7862
|
+
}
|
|
7863
|
+
function httpStatusAllowedStatuses(check) {
|
|
7827
7864
|
if (check.allowed_statuses?.length) return check.allowed_statuses;
|
|
7828
7865
|
if (check.expected_status !== void 0) return [check.expected_status];
|
|
7829
7866
|
return void 0;
|
|
7830
7867
|
}
|
|
7831
|
-
function
|
|
7868
|
+
function linkStatusAllowedStatuses(check) {
|
|
7869
|
+
return httpStatusAllowedStatuses(check);
|
|
7870
|
+
}
|
|
7871
|
+
function httpStatusIsAllowed(status, check) {
|
|
7832
7872
|
if (status === void 0) return false;
|
|
7833
|
-
const allowed =
|
|
7873
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
7834
7874
|
return allowed?.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
7835
7875
|
}
|
|
7836
7876
|
function linkStatusObservedBytes(result) {
|
|
@@ -7854,9 +7894,15 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
7854
7894
|
return actual === normalized;
|
|
7855
7895
|
});
|
|
7856
7896
|
}
|
|
7897
|
+
function httpStatusBodyContainsFailures(result, check) {
|
|
7898
|
+
const expected = check.body_contains?.filter(Boolean) ?? [];
|
|
7899
|
+
if (!expected.length) return [];
|
|
7900
|
+
const observed = isRecord(result.body_contains) ? result.body_contains : {};
|
|
7901
|
+
return expected.filter((text) => observed[text] !== true);
|
|
7902
|
+
}
|
|
7857
7903
|
function linkStatusResultOk(result, check) {
|
|
7858
7904
|
const status = numberValue(result.status);
|
|
7859
|
-
if (!
|
|
7905
|
+
if (!httpStatusIsAllowed(status, check)) return false;
|
|
7860
7906
|
if (stringValue2(result.error)) return false;
|
|
7861
7907
|
if (result.ok === false) return false;
|
|
7862
7908
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -7868,8 +7914,64 @@ function linkStatusResultOk(result, check) {
|
|
|
7868
7914
|
const observedBytes = linkStatusObservedBytes(result);
|
|
7869
7915
|
if (observedBytes === void 0 || observedBytes < check.min_bytes) return false;
|
|
7870
7916
|
}
|
|
7917
|
+
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
7871
7918
|
return true;
|
|
7872
7919
|
}
|
|
7920
|
+
function httpStatusEvidenceForCheck(viewport, check) {
|
|
7921
|
+
const evidence = viewport.http_statuses?.[httpStatusKey(check, viewport.url)];
|
|
7922
|
+
return isRecord(evidence) ? evidence : void 0;
|
|
7923
|
+
}
|
|
7924
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
7925
|
+
const key = httpStatusKey(check, viewport.url);
|
|
7926
|
+
const statusEvidence = httpStatusEvidenceForCheck(viewport, check);
|
|
7927
|
+
if (!statusEvidence) {
|
|
7928
|
+
return {
|
|
7929
|
+
viewport: viewport.name,
|
|
7930
|
+
key,
|
|
7931
|
+
url: httpStatusRequestUrl(check, viewport.url),
|
|
7932
|
+
method: httpStatusMethod(check),
|
|
7933
|
+
ok: false,
|
|
7934
|
+
status: null,
|
|
7935
|
+
failures: [{ code: "http_status_evidence_missing" }]
|
|
7936
|
+
};
|
|
7937
|
+
}
|
|
7938
|
+
const failures = [];
|
|
7939
|
+
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
7940
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
7941
|
+
failures.push({
|
|
7942
|
+
code: "http_status_failed",
|
|
7943
|
+
url: stringValue2(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
7944
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
7945
|
+
method: stringValue2(statusEvidence.method) ?? httpStatusMethod(check),
|
|
7946
|
+
error: stringValue2(statusEvidence.error) ?? null,
|
|
7947
|
+
content_type: stringValue2(statusEvidence.content_type) ?? null,
|
|
7948
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
7949
|
+
allowed_statuses: httpStatusAllowedStatuses(check) ?? ["2xx", "3xx"],
|
|
7950
|
+
min_bytes: check.min_bytes ?? null,
|
|
7951
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
7952
|
+
body_contains: check.body_contains ?? null,
|
|
7953
|
+
body_contains_missing: bodyContainsMissing,
|
|
7954
|
+
body_sample: stringValue2(statusEvidence.body_sample) ?? null
|
|
7955
|
+
});
|
|
7956
|
+
}
|
|
7957
|
+
return {
|
|
7958
|
+
viewport: viewport.name,
|
|
7959
|
+
key,
|
|
7960
|
+
url: stringValue2(statusEvidence.url) ?? httpStatusRequestUrl(check, viewport.url),
|
|
7961
|
+
method: stringValue2(statusEvidence.method) ?? httpStatusMethod(check),
|
|
7962
|
+
status: numberValue(statusEvidence.status) ?? null,
|
|
7963
|
+
status_text: stringValue2(statusEvidence.status_text) ?? null,
|
|
7964
|
+
ok: failures.length === 0,
|
|
7965
|
+
error: stringValue2(statusEvidence.error) ?? null,
|
|
7966
|
+
content_type: stringValue2(statusEvidence.content_type) ?? null,
|
|
7967
|
+
content_length: numberValue(statusEvidence.content_length) ?? null,
|
|
7968
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
7969
|
+
body_contains: isRecord(statusEvidence.body_contains) ? toJsonValue(statusEvidence.body_contains) : null,
|
|
7970
|
+
body_contains_missing: bodyContainsMissing,
|
|
7971
|
+
body_sample: stringValue2(statusEvidence.body_sample) ?? null,
|
|
7972
|
+
failures
|
|
7973
|
+
};
|
|
7974
|
+
}
|
|
7873
7975
|
function linkStatusEvidenceForCheck(viewport, check) {
|
|
7874
7976
|
const evidence = viewport.link_statuses?.[linkStatusSelector(check)];
|
|
7875
7977
|
return isRecord(evidence) ? evidence : void 0;
|
|
@@ -8460,6 +8562,29 @@ function assessCheckFromEvidence(check, evidence) {
|
|
|
8460
8562
|
message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
|
|
8461
8563
|
};
|
|
8462
8564
|
}
|
|
8565
|
+
if (check.type === "http_status") {
|
|
8566
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
8567
|
+
const method = httpStatusMethod(check);
|
|
8568
|
+
const summaries = viewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
8569
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
8570
|
+
return {
|
|
8571
|
+
type: check.type,
|
|
8572
|
+
label: checkLabel(check),
|
|
8573
|
+
status: failed.length ? "failed" : "passed",
|
|
8574
|
+
evidence: {
|
|
8575
|
+
url,
|
|
8576
|
+
method,
|
|
8577
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
8578
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
8579
|
+
min_bytes: check.min_bytes ?? null,
|
|
8580
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
8581
|
+
body_contains: check.body_contains ?? [],
|
|
8582
|
+
viewports: summaries.map((summary) => toJsonValue(summary)),
|
|
8583
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures) ? summary.failures.map((failure) => toJsonValue({ viewport: stringValue2(summary.viewport) ?? null, failure })) : [])
|
|
8584
|
+
},
|
|
8585
|
+
message: failed.length ? `HTTP status failed in ${failed.length} viewport(s).` : void 0
|
|
8586
|
+
};
|
|
8587
|
+
}
|
|
8463
8588
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
8464
8589
|
const selector = linkStatusSelector(check);
|
|
8465
8590
|
const summaries = viewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -9053,16 +9178,37 @@ function textOrderMatch(texts, expectedTexts) {
|
|
|
9053
9178
|
function linkStatusSelector(check) {
|
|
9054
9179
|
return check.selector || check.link_selector || "a[href]";
|
|
9055
9180
|
}
|
|
9056
|
-
function
|
|
9181
|
+
function httpStatusRequestUrl(check, baseUrl) {
|
|
9182
|
+
const rawUrl = check.url || "";
|
|
9183
|
+
if (!rawUrl) return "";
|
|
9184
|
+
try {
|
|
9185
|
+
return baseUrl ? new URL(rawUrl, baseUrl).href : new URL(rawUrl).href;
|
|
9186
|
+
} catch {
|
|
9187
|
+
return rawUrl;
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
function httpStatusMethod(check) {
|
|
9191
|
+
return String(check.method || "GET").toUpperCase();
|
|
9192
|
+
}
|
|
9193
|
+
function httpStatusKey(check, baseUrl) {
|
|
9194
|
+
return httpStatusMethod(check) + " " + httpStatusRequestUrl(check, baseUrl);
|
|
9195
|
+
}
|
|
9196
|
+
function httpStatusAllowedStatuses(check) {
|
|
9057
9197
|
if (Array.isArray(check.allowed_statuses) && check.allowed_statuses.length) return check.allowed_statuses;
|
|
9058
9198
|
if (typeof check.expected_status === "number" && Number.isFinite(check.expected_status)) return [check.expected_status];
|
|
9059
9199
|
return undefined;
|
|
9060
9200
|
}
|
|
9061
|
-
function
|
|
9201
|
+
function linkStatusAllowedStatuses(check) {
|
|
9202
|
+
return httpStatusAllowedStatuses(check);
|
|
9203
|
+
}
|
|
9204
|
+
function httpStatusIsAllowed(status, check) {
|
|
9062
9205
|
if (typeof status !== "number" || !Number.isFinite(status)) return false;
|
|
9063
|
-
const allowed =
|
|
9206
|
+
const allowed = httpStatusAllowedStatuses(check);
|
|
9064
9207
|
return Array.isArray(allowed) && allowed.length ? allowed.includes(status) : status >= 200 && status < 400;
|
|
9065
9208
|
}
|
|
9209
|
+
function linkStatusIsAllowed(status, check) {
|
|
9210
|
+
return httpStatusIsAllowed(status, check);
|
|
9211
|
+
}
|
|
9066
9212
|
function linkStatusObservedBytes(result) {
|
|
9067
9213
|
const bytes = typeof result.bytes === "number" && Number.isFinite(result.bytes) ? result.bytes : undefined;
|
|
9068
9214
|
const contentLength = typeof result.content_length === "number" && Number.isFinite(result.content_length) ? result.content_length : undefined;
|
|
@@ -9085,9 +9231,17 @@ function linkStatusContentTypeOk(result, check) {
|
|
|
9085
9231
|
return actual === normalized;
|
|
9086
9232
|
});
|
|
9087
9233
|
}
|
|
9234
|
+
function httpStatusBodyContainsFailures(result, check) {
|
|
9235
|
+
const expected = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
9236
|
+
if (!expected.length) return [];
|
|
9237
|
+
const observed = result && typeof result.body_contains === "object" && !Array.isArray(result.body_contains)
|
|
9238
|
+
? result.body_contains
|
|
9239
|
+
: {};
|
|
9240
|
+
return expected.filter((text) => observed[text] !== true);
|
|
9241
|
+
}
|
|
9088
9242
|
function linkStatusResultOk(result, check) {
|
|
9089
9243
|
if (!result || typeof result !== "object" || Array.isArray(result)) return false;
|
|
9090
|
-
if (!
|
|
9244
|
+
if (!httpStatusIsAllowed(result.status, check)) return false;
|
|
9091
9245
|
if (typeof result.error === "string" && result.error.trim()) return false;
|
|
9092
9246
|
if (result.ok === false) return false;
|
|
9093
9247
|
if (!linkStatusContentTypeOk(result, check)) return false;
|
|
@@ -9099,8 +9253,62 @@ function linkStatusResultOk(result, check) {
|
|
|
9099
9253
|
const observedBytes = linkStatusObservedBytes(result);
|
|
9100
9254
|
if (observedBytes === undefined || observedBytes < check.min_bytes) return false;
|
|
9101
9255
|
}
|
|
9256
|
+
if (httpStatusBodyContainsFailures(result, check).length) return false;
|
|
9102
9257
|
return true;
|
|
9103
9258
|
}
|
|
9259
|
+
function summarizeHttpStatusEvidence(viewport, check) {
|
|
9260
|
+
const key = httpStatusKey(check, viewport && viewport.url);
|
|
9261
|
+
const statusEvidence = viewport && viewport.http_statuses && viewport.http_statuses[key];
|
|
9262
|
+
if (!statusEvidence || typeof statusEvidence !== "object" || Array.isArray(statusEvidence)) {
|
|
9263
|
+
return {
|
|
9264
|
+
viewport: viewport && viewport.name,
|
|
9265
|
+
key,
|
|
9266
|
+
url: httpStatusRequestUrl(check, viewport && viewport.url),
|
|
9267
|
+
method: httpStatusMethod(check),
|
|
9268
|
+
ok: false,
|
|
9269
|
+
status: null,
|
|
9270
|
+
failures: [{ code: "http_status_evidence_missing" }],
|
|
9271
|
+
};
|
|
9272
|
+
}
|
|
9273
|
+
const failures = [];
|
|
9274
|
+
const bodyContainsMissing = httpStatusBodyContainsFailures(statusEvidence, check);
|
|
9275
|
+
if (!linkStatusResultOk(statusEvidence, check)) {
|
|
9276
|
+
failures.push({
|
|
9277
|
+
code: "http_status_failed",
|
|
9278
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
9279
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
9280
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
9281
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
9282
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
9283
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
9284
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
9285
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
9286
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
9287
|
+
body_contains: Array.isArray(check.body_contains) ? check.body_contains : null,
|
|
9288
|
+
body_contains_missing: bodyContainsMissing,
|
|
9289
|
+
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
9290
|
+
});
|
|
9291
|
+
}
|
|
9292
|
+
return {
|
|
9293
|
+
viewport: viewport && viewport.name,
|
|
9294
|
+
key,
|
|
9295
|
+
url: typeof statusEvidence.url === "string" ? statusEvidence.url : httpStatusRequestUrl(check, viewport && viewport.url),
|
|
9296
|
+
method: typeof statusEvidence.method === "string" ? statusEvidence.method : httpStatusMethod(check),
|
|
9297
|
+
status: typeof statusEvidence.status === "number" && Number.isFinite(statusEvidence.status) ? statusEvidence.status : null,
|
|
9298
|
+
status_text: typeof statusEvidence.status_text === "string" ? statusEvidence.status_text : null,
|
|
9299
|
+
ok: failures.length === 0,
|
|
9300
|
+
error: typeof statusEvidence.error === "string" ? statusEvidence.error : null,
|
|
9301
|
+
content_type: typeof statusEvidence.content_type === "string" ? statusEvidence.content_type : null,
|
|
9302
|
+
content_length: typeof statusEvidence.content_length === "number" && Number.isFinite(statusEvidence.content_length) ? statusEvidence.content_length : null,
|
|
9303
|
+
bytes: linkStatusObservedBytes(statusEvidence) ?? null,
|
|
9304
|
+
body_contains: statusEvidence.body_contains && typeof statusEvidence.body_contains === "object" && !Array.isArray(statusEvidence.body_contains)
|
|
9305
|
+
? statusEvidence.body_contains
|
|
9306
|
+
: null,
|
|
9307
|
+
body_contains_missing: bodyContainsMissing,
|
|
9308
|
+
body_sample: typeof statusEvidence.body_sample === "string" ? statusEvidence.body_sample : null,
|
|
9309
|
+
failures,
|
|
9310
|
+
};
|
|
9311
|
+
}
|
|
9104
9312
|
function summarizeLinkStatusEvidence(viewport, check) {
|
|
9105
9313
|
const selector = linkStatusSelector(check);
|
|
9106
9314
|
const linkEvidence = viewport && viewport.link_statuses && viewport.link_statuses[selector];
|
|
@@ -9856,6 +10064,31 @@ function assessProfile(profile, evidence) {
|
|
|
9856
10064
|
});
|
|
9857
10065
|
continue;
|
|
9858
10066
|
}
|
|
10067
|
+
if (check.type === "http_status") {
|
|
10068
|
+
const url = httpStatusRequestUrl(check, evidence.target_url);
|
|
10069
|
+
const method = httpStatusMethod(check);
|
|
10070
|
+
const summaries = checkViewports.map((viewport) => summarizeHttpStatusEvidence(viewport, check));
|
|
10071
|
+
const failed = summaries.filter((summary) => Array.isArray(summary.failures) && summary.failures.length > 0);
|
|
10072
|
+
checks.push({
|
|
10073
|
+
type: check.type,
|
|
10074
|
+
label: check.label || check.type,
|
|
10075
|
+
status: failed.length ? "failed" : "passed",
|
|
10076
|
+
evidence: {
|
|
10077
|
+
url,
|
|
10078
|
+
method,
|
|
10079
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
10080
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
10081
|
+
min_bytes: check.min_bytes ?? null,
|
|
10082
|
+
allowed_content_types: check.allowed_content_types ?? null,
|
|
10083
|
+
viewports: summaries,
|
|
10084
|
+
failures: failed.flatMap((summary) => Array.isArray(summary.failures)
|
|
10085
|
+
? summary.failures.map((failure) => ({ viewport: summary.viewport || null, failure }))
|
|
10086
|
+
: []),
|
|
10087
|
+
},
|
|
10088
|
+
message: failed.length ? "HTTP status failed in " + failed.length + " viewport(s)." : undefined,
|
|
10089
|
+
});
|
|
10090
|
+
continue;
|
|
10091
|
+
}
|
|
9859
10092
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
9860
10093
|
const selector = linkStatusSelector(check);
|
|
9861
10094
|
const summaries = checkViewports.map((viewport) => summarizeLinkStatusEvidence(viewport, check));
|
|
@@ -11053,6 +11286,72 @@ function linkProbeResponseFields(response, method) {
|
|
|
11053
11286
|
content_length: contentLength,
|
|
11054
11287
|
};
|
|
11055
11288
|
}
|
|
11289
|
+
async function collectHttpStatus(check) {
|
|
11290
|
+
const url = httpStatusRequestUrl(check, page.url() || targetUrl);
|
|
11291
|
+
const method = httpStatusMethod(check);
|
|
11292
|
+
const headers = check.headers && typeof check.headers === "object" && !Array.isArray(check.headers)
|
|
11293
|
+
? Object.fromEntries(Object.entries(check.headers).map(([key, value]) => [key, String(value)]).filter(([key]) => key.trim()))
|
|
11294
|
+
: {};
|
|
11295
|
+
let body;
|
|
11296
|
+
if (check.body_json !== undefined) {
|
|
11297
|
+
body = JSON.stringify(check.body_json);
|
|
11298
|
+
if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
|
|
11299
|
+
} else if (typeof check.body === "string") {
|
|
11300
|
+
body = check.body;
|
|
11301
|
+
}
|
|
11302
|
+
const bodyContains = Array.isArray(check.body_contains) ? check.body_contains.filter(Boolean) : [];
|
|
11303
|
+
const options = {
|
|
11304
|
+
method,
|
|
11305
|
+
redirect: "follow",
|
|
11306
|
+
cache: "no-store",
|
|
11307
|
+
headers,
|
|
11308
|
+
};
|
|
11309
|
+
if (body !== undefined && method !== "GET" && method !== "HEAD") options.body = body;
|
|
11310
|
+
const result = {
|
|
11311
|
+
version: "riddle-proof.http-status.v1",
|
|
11312
|
+
url,
|
|
11313
|
+
method,
|
|
11314
|
+
status: null,
|
|
11315
|
+
ok: false,
|
|
11316
|
+
error: null,
|
|
11317
|
+
request_body_bytes: typeof body === "string" ? body.length : 0,
|
|
11318
|
+
allowed_statuses: httpStatusAllowedStatuses(check) || ["2xx", "3xx"],
|
|
11319
|
+
require_nonzero_bytes: check.require_nonzero_bytes === true,
|
|
11320
|
+
min_bytes: typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? check.min_bytes : null,
|
|
11321
|
+
allowed_content_types: Array.isArray(check.allowed_content_types) ? check.allowed_content_types : null,
|
|
11322
|
+
};
|
|
11323
|
+
try {
|
|
11324
|
+
const response = await fetch(url, options);
|
|
11325
|
+
Object.assign(result, linkProbeResponseFields(response, method));
|
|
11326
|
+
result.url = url;
|
|
11327
|
+
result.status_text = response.statusText || "";
|
|
11328
|
+
const shouldReadBody = check.require_nonzero_bytes === true || (typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || bodyContains.length > 0;
|
|
11329
|
+
if (shouldReadBody) {
|
|
11330
|
+
try {
|
|
11331
|
+
const buffer = await response.arrayBuffer();
|
|
11332
|
+
result.bytes = buffer.byteLength;
|
|
11333
|
+
if (bodyContains.length) {
|
|
11334
|
+
const text = new TextDecoder().decode(buffer);
|
|
11335
|
+
result.body_sample = text.slice(0, 1000);
|
|
11336
|
+
result.body_contains = Object.fromEntries(bodyContains.map((expected) => [expected, text.includes(expected)]));
|
|
11337
|
+
}
|
|
11338
|
+
} catch (error) {
|
|
11339
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
11340
|
+
}
|
|
11341
|
+
}
|
|
11342
|
+
result.ok = linkProbeAllowed(result.status, check)
|
|
11343
|
+
&& linkProbeContentTypeAllowed(result, check)
|
|
11344
|
+
&& (check.require_nonzero_bytes !== true || ((linkProbeObservedBytes(result) || 0) > 0))
|
|
11345
|
+
&& (!(typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes)) || ((linkProbeObservedBytes(result) || 0) >= check.min_bytes))
|
|
11346
|
+
&& (!bodyContains.length || bodyContains.every((expected) => result.body_contains && result.body_contains[expected] === true))
|
|
11347
|
+
&& !result.error;
|
|
11348
|
+
return result;
|
|
11349
|
+
} catch (error) {
|
|
11350
|
+
result.error = String(error && error.message ? error.message : error).slice(0, 500);
|
|
11351
|
+
result.ok = false;
|
|
11352
|
+
return result;
|
|
11353
|
+
}
|
|
11354
|
+
}
|
|
11056
11355
|
async function probeLinkStatus(candidate, check) {
|
|
11057
11356
|
const requireNonzeroBytes = check.require_nonzero_bytes === true;
|
|
11058
11357
|
const minBytes = typeof check.min_bytes === "number" && Number.isFinite(check.min_bytes) ? Math.max(1, Math.floor(check.min_bytes)) : null;
|
|
@@ -11745,6 +12044,7 @@ async function captureViewport(viewport) {
|
|
|
11745
12044
|
const frames = {};
|
|
11746
12045
|
const text_sequences = {};
|
|
11747
12046
|
const text_matches = {};
|
|
12047
|
+
const http_statuses = {};
|
|
11748
12048
|
const link_statuses = {};
|
|
11749
12049
|
for (const check of profile.checks || []) {
|
|
11750
12050
|
if (!profileCheckAppliesToViewport(check, viewport)) continue;
|
|
@@ -11771,6 +12071,10 @@ async function captureViewport(viewport) {
|
|
|
11771
12071
|
selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
|
|
11772
12072
|
frames[check.selector] = frames[check.selector] || await frameEvidence(check.selector);
|
|
11773
12073
|
}
|
|
12074
|
+
if (check.type === "http_status") {
|
|
12075
|
+
const key = httpStatusKey(check, page.url() || targetUrl);
|
|
12076
|
+
http_statuses[key] = http_statuses[key] || await collectHttpStatus(check);
|
|
12077
|
+
}
|
|
11774
12078
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
11775
12079
|
const selector = linkStatusSelector(check);
|
|
11776
12080
|
link_statuses[selector] = link_statuses[selector] || await collectLinkStatus(check);
|
|
@@ -11838,6 +12142,7 @@ async function captureViewport(viewport) {
|
|
|
11838
12142
|
frames,
|
|
11839
12143
|
text_sequences,
|
|
11840
12144
|
text_matches,
|
|
12145
|
+
http_statuses,
|
|
11841
12146
|
link_statuses,
|
|
11842
12147
|
route_inventory: routeInventory,
|
|
11843
12148
|
setup_action_results: setupActionResults,
|
|
@@ -11887,6 +12192,19 @@ function buildProfileEvidence(currentViewports) {
|
|
|
11887
12192
|
),
|
|
11888
12193
|
})),
|
|
11889
12194
|
})),
|
|
12195
|
+
http_status: currentViewports
|
|
12196
|
+
.filter((viewport) => viewport.http_statuses && Object.keys(viewport.http_statuses).length)
|
|
12197
|
+
.map((viewport) => ({
|
|
12198
|
+
viewport: viewport.name,
|
|
12199
|
+
requests: Object.entries(viewport.http_statuses || {}).map(([key, statusSet]) => ({
|
|
12200
|
+
key,
|
|
12201
|
+
url: statusSet && statusSet.url,
|
|
12202
|
+
method: statusSet && statusSet.method,
|
|
12203
|
+
status: statusSet && statusSet.status,
|
|
12204
|
+
ok: statusSet && statusSet.ok === true,
|
|
12205
|
+
error: statusSet && statusSet.error,
|
|
12206
|
+
})),
|
|
12207
|
+
})),
|
|
11890
12208
|
link_status: currentViewports
|
|
11891
12209
|
.filter((viewport) => viewport.link_statuses && Object.keys(viewport.link_statuses).length)
|
|
11892
12210
|
.map((viewport) => ({
|
|
@@ -12344,6 +12662,10 @@ function profileResultMarkdown(result) {
|
|
|
12344
12662
|
if (linkStatusSummaryLines.length) {
|
|
12345
12663
|
lines.push("", "## Link Status", "", ...linkStatusSummaryLines);
|
|
12346
12664
|
}
|
|
12665
|
+
const httpStatusSummaryLines = profileHttpStatusSummaryMarkdown(result);
|
|
12666
|
+
if (httpStatusSummaryLines.length) {
|
|
12667
|
+
lines.push("", "## HTTP Status", "", ...httpStatusSummaryLines);
|
|
12668
|
+
}
|
|
12347
12669
|
const environmentBlockerLines = profileEnvironmentBlockerMarkdown(result);
|
|
12348
12670
|
if (environmentBlockerLines.length) {
|
|
12349
12671
|
lines.push("", "## Environment Blocker", "", ...environmentBlockerLines);
|
|
@@ -12420,6 +12742,13 @@ function profileCheckMarkdownTarget(check) {
|
|
|
12420
12742
|
if (selector && maxOverflow !== void 0) return `${markdownInlineCode(selector)} <= ${maxOverflow}px`;
|
|
12421
12743
|
return selector ? markdownInlineCode(selector) : maxOverflow !== void 0 ? `<= ${maxOverflow}px` : void 0;
|
|
12422
12744
|
}
|
|
12745
|
+
if (check.type === "http_status") {
|
|
12746
|
+
const url = cliString(evidence.url);
|
|
12747
|
+
const method = cliString(evidence.method);
|
|
12748
|
+
const statuses = Array.isArray(evidence.allowed_statuses) ? evidence.allowed_statuses.map((status) => cliString(status) || String(status)).filter(Boolean) : [];
|
|
12749
|
+
const target = [method, url ? markdownInlineCode(url) : ""].filter(Boolean).join(" ");
|
|
12750
|
+
return statuses.length ? `${target} -> ${statuses.join("/")}` : target || void 0;
|
|
12751
|
+
}
|
|
12423
12752
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
12424
12753
|
const expectedCount = cliFiniteNumber(evidence.expected_count);
|
|
12425
12754
|
const minCount = cliFiniteNumber(evidence.min_count);
|
|
@@ -12647,6 +12976,24 @@ function profileLinkStatusSummaryMarkdown(result) {
|
|
|
12647
12976
|
}
|
|
12648
12977
|
return lines;
|
|
12649
12978
|
}
|
|
12979
|
+
function profileHttpStatusSummaryMarkdown(result) {
|
|
12980
|
+
const httpStatusChecks = result.checks.filter((check) => check.type === "http_status");
|
|
12981
|
+
const lines = [];
|
|
12982
|
+
for (const check of httpStatusChecks) {
|
|
12983
|
+
const evidence = cliRecord(check.evidence);
|
|
12984
|
+
if (!evidence) continue;
|
|
12985
|
+
const label = check.label || check.type;
|
|
12986
|
+
const url = cliString(evidence.url);
|
|
12987
|
+
const method = cliString(evidence.method) || "GET";
|
|
12988
|
+
const viewports = Array.isArray(evidence.viewports) ? evidence.viewports.map(cliRecord).filter((viewport) => Boolean(viewport)) : [];
|
|
12989
|
+
const statuses = viewports.map((viewport) => cliFiniteNumber(viewport.status)).map((status) => status === void 0 ? "error" : String(status));
|
|
12990
|
+
const failedTotal = Array.isArray(evidence.failures) ? evidence.failures.length : 0;
|
|
12991
|
+
lines.push(
|
|
12992
|
+
`- ${label}: ${method}${url ? ` ${markdownInlineCode(url)}` : ""}, statuses ${statuses.length ? statuses.join("/") : "unknown"}, failures ${failedTotal}`
|
|
12993
|
+
);
|
|
12994
|
+
}
|
|
12995
|
+
return lines;
|
|
12996
|
+
}
|
|
12650
12997
|
function writeProfileOutput(outputDir, result) {
|
|
12651
12998
|
if (!outputDir) return;
|
|
12652
12999
|
(0, import_node_fs6.mkdirSync)(outputDir, { recursive: true });
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
profileStatusExitCode,
|
|
11
11
|
resolveRiddleProofProfileTargetUrl,
|
|
12
12
|
resolveRiddleProofProfileTimeoutSec
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-W5PBTTMJ.js";
|
|
14
14
|
import {
|
|
15
15
|
createRiddleApiClient,
|
|
16
16
|
parseRiddleViewport
|
|
@@ -346,6 +346,10 @@ function profileResultMarkdown(result) {
|
|
|
346
346
|
if (linkStatusSummaryLines.length) {
|
|
347
347
|
lines.push("", "## Link Status", "", ...linkStatusSummaryLines);
|
|
348
348
|
}
|
|
349
|
+
const httpStatusSummaryLines = profileHttpStatusSummaryMarkdown(result);
|
|
350
|
+
if (httpStatusSummaryLines.length) {
|
|
351
|
+
lines.push("", "## HTTP Status", "", ...httpStatusSummaryLines);
|
|
352
|
+
}
|
|
349
353
|
const environmentBlockerLines = profileEnvironmentBlockerMarkdown(result);
|
|
350
354
|
if (environmentBlockerLines.length) {
|
|
351
355
|
lines.push("", "## Environment Blocker", "", ...environmentBlockerLines);
|
|
@@ -422,6 +426,13 @@ function profileCheckMarkdownTarget(check) {
|
|
|
422
426
|
if (selector && maxOverflow !== void 0) return `${markdownInlineCode(selector)} <= ${maxOverflow}px`;
|
|
423
427
|
return selector ? markdownInlineCode(selector) : maxOverflow !== void 0 ? `<= ${maxOverflow}px` : void 0;
|
|
424
428
|
}
|
|
429
|
+
if (check.type === "http_status") {
|
|
430
|
+
const url = cliString(evidence.url);
|
|
431
|
+
const method = cliString(evidence.method);
|
|
432
|
+
const statuses = Array.isArray(evidence.allowed_statuses) ? evidence.allowed_statuses.map((status) => cliString(status) || String(status)).filter(Boolean) : [];
|
|
433
|
+
const target = [method, url ? markdownInlineCode(url) : ""].filter(Boolean).join(" ");
|
|
434
|
+
return statuses.length ? `${target} -> ${statuses.join("/")}` : target || void 0;
|
|
435
|
+
}
|
|
425
436
|
if (check.type === "link_status" || check.type === "artifact_link_status") {
|
|
426
437
|
const expectedCount = cliFiniteNumber(evidence.expected_count);
|
|
427
438
|
const minCount = cliFiniteNumber(evidence.min_count);
|
|
@@ -649,6 +660,24 @@ function profileLinkStatusSummaryMarkdown(result) {
|
|
|
649
660
|
}
|
|
650
661
|
return lines;
|
|
651
662
|
}
|
|
663
|
+
function profileHttpStatusSummaryMarkdown(result) {
|
|
664
|
+
const httpStatusChecks = result.checks.filter((check) => check.type === "http_status");
|
|
665
|
+
const lines = [];
|
|
666
|
+
for (const check of httpStatusChecks) {
|
|
667
|
+
const evidence = cliRecord(check.evidence);
|
|
668
|
+
if (!evidence) continue;
|
|
669
|
+
const label = check.label || check.type;
|
|
670
|
+
const url = cliString(evidence.url);
|
|
671
|
+
const method = cliString(evidence.method) || "GET";
|
|
672
|
+
const viewports = Array.isArray(evidence.viewports) ? evidence.viewports.map(cliRecord).filter((viewport) => Boolean(viewport)) : [];
|
|
673
|
+
const statuses = viewports.map((viewport) => cliFiniteNumber(viewport.status)).map((status) => status === void 0 ? "error" : String(status));
|
|
674
|
+
const failedTotal = Array.isArray(evidence.failures) ? evidence.failures.length : 0;
|
|
675
|
+
lines.push(
|
|
676
|
+
`- ${label}: ${method}${url ? ` ${markdownInlineCode(url)}` : ""}, statuses ${statuses.length ? statuses.join("/") : "unknown"}, failures ${failedTotal}`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
return lines;
|
|
680
|
+
}
|
|
652
681
|
function writeProfileOutput(outputDir, result) {
|
|
653
682
|
if (!outputDir) return;
|
|
654
683
|
mkdirSync(outputDir, { recursive: true });
|