mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(browser): share decoded response emptiness across capture views
This commit is contained in:
1 parent
836cbd3244
commit
59022b6a3a
5 files changed
+70
-12
No files matched your search
@@ -465,7 +465,9 @@ api-surface grouping, then keeps the groups on the primary origin, the origin
|
||||
with the most JSON-like records (`application/json`, `application/*+json`,
|
||||
`text/plain`, `text/markdown`), whose path starts with `/api/` or `/apis/` or
|
||||
whose content types are JSON-like, and the write groups answered with no body
|
||||
(a bodiless `204 DELETE`). Every other group is listed under
|
||||
(a bodiless `204 DELETE`). Empty retained response text establishes a bodiless
|
||||
write only when its encoding is understood and unredacted; an opaque or hidden
|
||||
encoding leaves the body unknown. Every other group is listed under
|
||||
`excluded` with reason `other-origin`, `ui-page-or-asset` or `non-json`, and
|
||||
retained records without a usable request line are counted in a limitation.
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type WebNetworkCaptureRecord,
|
||||
} from "../webNetworkCapture.js";
|
||||
import { completeWithinViewCoverage } from "./analysisViewCoverage.js";
|
||||
import { recordedResponseBodyIsEmpty } from "./recordedResponseBody.js";
|
||||
import type { UnsignedAnalysisView } from "./analysisView.js";
|
||||
import { WebNetworkProjectionAccumulator } from "./WebNetworkProjectionAccumulator.js";
|
||||
import {
|
||||
@@ -105,14 +106,14 @@ const responseTextSchema = z.object({
|
||||
}),
|
||||
});
|
||||
/** Whether a record's response had no body: a 204 or 205, an empty text, or a recorded size of zero; a missing text alone may be a body not kept. */
|
||||
const emptyResponse = (reported: unknown): boolean => {
|
||||
const parsed = responseTextSchema.safeParse(reported);
|
||||
const emptyResponse = (record: WebNetworkCaptureRecord): boolean => {
|
||||
const parsed = responseTextSchema.safeParse(record.reported);
|
||||
if (!parsed.success) return false;
|
||||
const { status, content } = parsed.data.response;
|
||||
return (
|
||||
status === 204 ||
|
||||
status === 205 ||
|
||||
content?.text === "" ||
|
||||
recordedResponseBodyIsEmpty(record) ||
|
||||
((content?.text === undefined || content.text === null) &&
|
||||
content?.size === 0)
|
||||
);
|
||||
@@ -292,7 +293,7 @@ export class EnvironmentManifestAccumulator extends WebNetworkProjectionAccumula
|
||||
route,
|
||||
queryNames,
|
||||
{ key, group },
|
||||
{ shape: bodyShape.value, empty: emptyResponse(record.reported) },
|
||||
{ shape: bodyShape.value, empty: emptyResponse(record) },
|
||||
);
|
||||
} finally {
|
||||
this.releaseTemporary();
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import {
|
||||
base64PrefixChars,
|
||||
readRecordedResponseBody,
|
||||
recordedResponseBodyIsEmpty,
|
||||
type RecordedResponseBody,
|
||||
} from "./recordedResponseBody.js";
|
||||
import {
|
||||
@@ -296,13 +297,7 @@ export class EnvironmentStateAccumulator extends EnvironmentManifestAccumulator
|
||||
const entry = this.#entries.get(ordinal);
|
||||
if (entry === undefined) return false;
|
||||
if (entry.facts.status === 204) return true;
|
||||
// Emptiness needs only a tiny decoded prefix; never decode a whole body outside the state budget.
|
||||
const body = readRecordedResponseBody(entry.record, 0);
|
||||
return (
|
||||
body.bodyText === "" &&
|
||||
body.bodyEncoding === undefined &&
|
||||
body.bodyCut !== true
|
||||
);
|
||||
return recordedResponseBodyIsEmpty(entry.record);
|
||||
}
|
||||
|
||||
/** Project the init state of the manifest's primary origin; records keep timeline order. */
|
||||
|
||||
@@ -1102,6 +1102,54 @@ it("keeps a write answered with no body as a tool whatever its content type, but
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps opaque or redacted empty response text unknown when selecting bodiless writes", () => {
|
||||
const capture = syntheticCapture([
|
||||
["POST", "https://s.test/opaque/1", "text/html"],
|
||||
["POST", "https://s.test/hidden/1", "text/html"],
|
||||
["POST", "https://s.test/base64/1", "text/html"],
|
||||
]);
|
||||
for (const [index, record] of capture.records.entries()) {
|
||||
record.reported = {
|
||||
...(record.reported as Record<string, unknown>),
|
||||
response: {
|
||||
status: 200,
|
||||
content: {
|
||||
mimeType: "text/html",
|
||||
text: "",
|
||||
...(index === 0 ? { encoding: "opaque" } : {}),
|
||||
...(index === 2 ? { encoding: "base64" } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
if (index === 1)
|
||||
record.redactions = [
|
||||
{
|
||||
pointer: "/response/content",
|
||||
reason: "explicit-sensitive-value",
|
||||
scope: "property-name",
|
||||
},
|
||||
];
|
||||
if (index === 2)
|
||||
record.binary_fields = [
|
||||
{
|
||||
pointer: "/response/content/text",
|
||||
representation: "har-base64-content",
|
||||
state: "retained",
|
||||
content_base64: "",
|
||||
bytes: 0,
|
||||
sha256:
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
},
|
||||
];
|
||||
}
|
||||
const projected = manifest([], capture);
|
||||
expect(projected.tools.map(line)).toEqual(["POST /base64/{numeric}"]);
|
||||
expect(projected.excluded.map((entry) => entry.path_template)).toEqual([
|
||||
"/hidden/{numeric}",
|
||||
"/opaque/{numeric}",
|
||||
]);
|
||||
});
|
||||
|
||||
it("refuses a parent whose subject is absent or names another path or digest", () => {
|
||||
const parent = parentOf();
|
||||
const refusal = {
|
||||
|
||||
@@ -70,3 +70,15 @@ export const readRecordedResponseBody = (
|
||||
return { bodyText: encoded, bodyEncoding: "base64", ...flags };
|
||||
}
|
||||
};
|
||||
|
||||
/** Prove decoded emptiness with a bounded prefix, preserving encoding and cut uncertainty. */
|
||||
export const recordedResponseBodyIsEmpty = (
|
||||
record: WebNetworkCaptureRecord,
|
||||
): boolean => {
|
||||
const body = readRecordedResponseBody(record, 0);
|
||||
return (
|
||||
body.bodyText === "" &&
|
||||
body.bodyEncoding === undefined &&
|
||||
body.bodyCut !== true
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user