Files
reverse-engineering-anything/src/browser/CdpCaptureEvents.shapeBudget.test.ts
T

272 lines
8.8 KiB
TypeScript

import { expect, it } from "vitest";
import { inspectWebPageInputSchema } from "../domain/browserObservation.js";
import { WEB_RUNTIME_LIMITS } from "../domain/webRuntime.js";
import { CdpCaptureEvents } from "./CdpCaptureEvents.js";
const origin = "http://127.0.0.1:43127";
const deep = (depth: number) => '{"a":'.repeat(depth) + "0" + "}".repeat(depth);
const createEvents = (selected = true) =>
new CdpCaptureEvents(
inspectWebPageInputSchema.parse({
cdp_endpoint: origin,
allowed_origins: [origin],
target_id: "page",
include_console_text: true,
include_json_body_shapes: selected,
include_websocket_shapes: selected,
}),
new Set([origin]),
);
const request = (
events: CdpCaptureEvents,
id: string,
body: string,
redirect = false,
) => {
events.ingest({
method: "Network.requestWillBeSent",
params: {
requestId: id,
timestamp: redirect ? 2 : 1,
initiator: { type: "other" },
request: {
url: `${origin}/${redirect ? "next" : "first"}`,
method: "POST",
headers: { "Content-Type": "application/json" },
postData: body,
},
...(redirect
? {
redirectResponse: {
url: `${origin}/first`,
status: 302,
encodedDataLength: 0,
},
}
: {}),
},
});
};
const response = (events: CdpCaptureEvents, id: string, body: string) => {
events.ingest({
method: "Network.responseReceived",
params: {
requestId: id,
response: {
url: `${origin}/first`,
status: 200,
mimeType: "application/json",
},
},
});
const candidate = events
.responseBodyRequests()
.find(({ requestId }) => requestId === id);
if (candidate === undefined)
throw new Error("Expected selected response body");
events.ingestResponseBody(candidate, { body, base64Encoded: false });
return candidate;
};
const openSocket = (events: CdpCaptureEvents) =>
events.ingest({
method: "Network.webSocketCreated",
params: { requestId: "socket", url: `ws://${origin.slice(7)}/live` },
});
const frame = (events: CdpCaptureEvents, payloadData: string) =>
events.ingest({
method: "Network.webSocketFrameReceived",
params: { requestId: "socket", response: { opcode: 1, payloadData } },
});
it.each([
["\tAPPLICATION/JSON ; charset=utf-8\t", true],
["application/problem+json", true],
["model/example+json", true],
["+json", false],
["application /problem+json", false],
["application/problem\n+json", false],
["application/\u212a+json", false],
["\napplication/json", false],
["text/plain", false],
])(
"admits shapes only from valid media types while preserving %j",
(mediaType, selected) => {
const events = createEvents();
events.ingest({
method: "Network.requestWillBeSent",
params: {
requestId: "typed",
timestamp: 1,
initiator: { type: "other" },
request: {
url: `${origin}/typed`,
method: "POST",
headers: { "Content-Type": mediaType },
postData: '{"count":1}',
},
},
});
expect(events.network.get("typed")?.body_shapes.request !== null).toBe(
selected,
);
events.ingest({
method: "Network.responseReceived",
params: {
requestId: "typed",
response: {
url: `${origin}/typed`,
status: 200,
mimeType: mediaType,
},
},
});
expect(events.responseBodyRequests().length).toBe(selected ? 1 : 0);
expect(events.network.get("typed")?.mime_type).toBe(mediaType);
},
);
it("distinguishes valid over-budget websocket JSON from non-JSON text", () => {
const events = createEvents();
openSocket(events);
frame(events, deep(760));
frame(events, "ordinary non-JSON text");
expect(events.webSocketConnectionsById.get("socket")?.events).toMatchObject([
{ payload_shape: { format: "text", json_shape: null } },
]);
const coverage = events.completeness.snapshot();
expect(coverage.dropped_events.websocket_frames).toBe(1);
expect(coverage.excluded).toContainEqual({
section: "websocket_shapes",
reason: "resource_budget_exhausted",
count: 1,
});
});
it("retains the peer body facts when a selected request or response shape exceeds the allowance", () => {
const events = createEvents();
request(events, "request-large", deep(760));
response(events, "request-large", '{"response":true}');
expect(events.network.get("request-large")?.body_shapes).toMatchObject({
status: "partial",
request: null,
response: { root_type: "object" },
});
request(events, "response-large", '{"request":true}');
response(events, "response-large", deep(760));
expect(events.network.get("response-large")?.body_shapes).toMatchObject({
status: "partial",
request: { root_type: "object" },
response: null,
});
const coverage = events.completeness.snapshot();
expect(coverage.truncated_sections).toContain("json_body_shapes");
expect(coverage.excluded).toContainEqual({
section: "json_body_shapes",
reason: "resource_budget_exhausted",
count: 2,
});
});
it("replaces response receipts without charging the same retained result a hundred times", () => {
const events = createEvents();
request(events, "repeat", '{"request":true}');
const candidate = response(events, "repeat", deep(100));
const retained = events.retainedEventBytes;
for (let index = 0; index < 100; index++)
events.ingestResponseBody(candidate, {
body: deep(100),
base64Encoded: false,
});
expect(events.retainedEventBytes).toBe(retained);
response(events, "repeat", '{"smaller":true}');
const shapes = events.network.get("repeat")?.body_shapes;
if (shapes === undefined) throw new Error("Expected retained request");
expect(shapes.status).toBe("included");
expect(events.retainedEventBytes).toBe(
Buffer.byteLength(JSON.stringify(shapes.request)) +
Buffer.byteLength(JSON.stringify(shapes.response)),
);
expect(events.completeness.snapshot().truncated_sections).not.toContain(
"json_body_shapes",
);
});
it("refunds replaced and removed request records while ignoring stale response occurrences", () => {
const events = createEvents();
request(events, "redirect", deep(100));
const stale = response(events, "redirect", deep(100));
request(events, "redirect", '{"next":true}', true);
const retained = events.retainedEventBytes;
events.ingestResponseBody(stale, { body: deep(760), base64Encoded: false });
expect(events.retainedEventBytes).toBe(retained);
expect(events.network.get("redirect")?.body_shapes.response).toBeNull();
events.ingest({
method: "Network.responseReceived",
params: {
requestId: "redirect",
response: { url: "https://excluded.invalid/next", status: 200 },
},
});
expect(events.network.has("redirect")).toBe(false);
expect(events.retainedEventBytes).toBe(0);
request(events, "fresh", deep(600));
expect(events.network.get("fresh")?.body_shapes.status).toBe("included");
events.beginAuthorizedFrame("frame");
expect(events.retainedEventBytes).toBe(0);
request(events, "after-reset", deep(600));
expect(events.network.get("after-reset")?.body_shapes.status).toBe(
"included",
);
});
it("shares the remaining allowance across console entries, body shapes and websocket shapes", () => {
const events = createEvents();
events.ingest({
method: "Runtime.consoleAPICalled",
params: {
type: "log",
timestamp: 1,
args: [{ type: "string", value: "x".repeat(1024 * 1024) }],
stackTrace: {
callFrames: [
{ url: `${origin}/app.js`, lineNumber: 0, columnNumber: 0 },
],
},
},
});
request(events, "body", deep(600));
expect(events.console).toHaveLength(1);
expect(events.network.get("body")?.body_shapes.status).toBe("included");
openSocket(events);
frame(events, deep(400));
expect(events.webSocketConnectionsById.get("socket")?.events).toHaveLength(0);
expect(events.completeness.snapshot().dropped_events.websocket_frames).toBe(
1,
);
expect(events.retainedEventBytes).toBeLessThanOrEqual(
WEB_RUNTIME_LIMITS.retainedEventBytes,
);
const empty = createEvents();
openSocket(empty);
frame(empty, deep(400));
expect(empty.webSocketConnectionsById.get("socket")?.events).toHaveLength(1);
});
it("does not infer unselected body or websocket shapes", () => {
const events = createEvents(false);
request(events, "unselected", deep(760));
expect(events.network.get("unselected")?.body_shapes).toEqual({
status: "not_approved",
request: null,
response: null,
});
expect(events.responseBodyRequests()).toEqual([]);
openSocket(events);
frame(events, deep(760));
expect(events.webSocketConnectionsById.get("socket")?.events).toMatchObject([
{ payload_shape: null },
]);
expect(events.completeness.snapshot().dropped_events.total).toBe(0);
});