Files
rea/tests/boundary/cli/scenarioManifestCoverage.test.ts
T
morluto 2f2b07f750 fix(environment): count scenario bodies after request admission
Move body-retention coverage accounting to the manifest request-line owner. Preserve source retention facts by ordinal and remove the competing eligibility rule from scenario projection.
2026-10-24 10:17:23 +08:00

128 lines
4.0 KiB
TypeScript

import { Cli } from "incur";
import { expect, it } from "vitest";
import { registerAnalysisViewCommands } from "../../../src/cli/analysisViewCommands.js";
import { sanitizeBrowserUrl } from "../../../src/domain/browserObservation.js";
import { browserScenarioCaptureSchema } from "../../../src/domain/browserScenarioCapture.js";
import { silentLogger } from "../../../src/logger.js";
import {
scenarioCaptureEvidence,
scenarioCaptureFixture,
} from "../../fixtures/browserScenarioCapture.js";
const unusableUrls = [
"data:text/plain,no-origin",
"file:///public/example.txt",
"blob:https://example.test/resource",
"not a url",
"https:example.test/api/items",
];
const manifest = async (
urls: readonly string[],
body: "absent" | "redacted",
) => {
const original = scenarioCaptureFixture(
urls.map(() => ({
method: "GET",
url: "/api/items",
...(body === "absent"
? {}
: {
status: 200,
mediaType: "application/json",
body: '{"token":"[REDACTED:token]"}',
}),
})),
);
let request = 0;
const capture = browserScenarioCaptureSchema.parse({
...original,
events: {
...original.events,
items: original.events.items.map((event) => {
if (event.kind === "request")
return { ...event, url: sanitizeBrowserUrl(urls[request++]!) };
if (event.kind === "network-content" && event.body.state === "captured")
return { ...event, body: { ...event.body, redacted: true } };
return event;
}),
},
});
const cli = Cli.create("scenario-coverage", { sync: false });
registerAnalysisViewCommands(cli, silentLogger);
let stdout = "";
let exitCode = 0;
await cli.serve(
[
"inspect-analysis-view",
JSON.stringify({
source: { kind: "inline", evidence: scenarioCaptureEvidence(capture) },
view: { kind: "environment-manifest" },
}),
"--json",
],
{
env: {},
stdout: (text) => {
stdout += text;
},
exit: (code) => {
exitCode = code;
},
},
);
expect(exitCode).toBe(0);
return (
JSON.parse(stdout) as {
normalized_result: {
records: { ordinal: number }[];
limitations: string[];
};
}
).normalized_result;
};
it.each(
unusableUrls.flatMap((url) =>
(["absent", "redacted"] as const).map((body) => ({ url, body })),
),
)(
"excludes $url from scenario $body body-retention coverage through the CLI handler",
async ({ url, body }) => {
const result = await manifest([url], body);
expect(result.records).toEqual([]);
expect(result.limitations).toContain(
"1 retained records without a usable request line are not represented in the manifest.",
);
expect(
result.limitations.filter((limitation) =>
limitation.includes("scenario transactions with a usable request line"),
),
).toEqual([]);
},
);
it.each(["absent", "redacted"] as const)(
"counts only the admitted request in mixed scenario %s body-retention coverage",
async (body) => {
const urls = [...unusableUrls, "https://example.test/api/control"];
const result = await manifest(urls, body);
expect(result.records.map((record) => record.ordinal)).toEqual([
unusableUrls.length,
]);
expect(result.limitations).toContain(
`${String(unusableUrls.length)} retained records without a usable request line are not represented in the manifest.`,
);
expect(
result.limitations.filter((limitation) =>
limitation.includes("scenario transactions with a usable request line"),
),
).toEqual([
body === "absent"
? "1 of 1 scenario transactions with a usable request line retained no response body; tools citing them replay only the recorded status and content type, either of which may be absent."
: "1 of 1 scenario transactions with a usable request line retained a response body with declared secret values replaced by redaction markers; tools citing them replay the redacted bytes.",
]);
},
);