Files
rea/src/application/WebSourceLocationService.test.ts
T
ysopwnable f430c416c7 feat(browser): trace captured website source-map locations (#897)
* refactor(browser): share captured-script acquisition boundaries

Reuse stable capture/source acquisition and identity validation for module
and source-map analysis. Preserve host I/O, malformed input, unavailable
source and digest mismatch diagnostics.

Refs #851

Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)

* feat(browser): trace captured website source-map locations

Refs #851. Preserve selected capture/map byte identity, duplicate mappings and embedded source evidence through the pinned upstream codec.

Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)

* test(browser): verify source-map artifacts and synchronize generated contracts

Cover byte-preserving map reads and actionable filesystem constraints. Refresh generated public schemas and metadata on current main.

Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)

* fix(browser): preserve inert source-map URL context

The selected URL is local codec context and grants no transport authority. Preserve its reported text and verify normal/userinfo contexts through real public interfaces, alongside independent manifest byte identity.

Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
2026-10-07 16:21:18 +08:00

152 lines
5.6 KiB
TypeScript

import { expect, it } from "vitest";
import {
webSourceLocationArgs as args,
webSourceLocationFixture,
webSourceMapDecoderFixture as decoder,
} from "../../tests/fixtures/webSourceLocation.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { ok } from "../domain/result.js";
import { webSourceLocationResultSchema } from "../domain/webSourceLocation.js";
import { WebSourceLocationService } from "./WebSourceLocationService.js";
import type { WebSourceMapPort } from "./WebSourceLocationPorts.js";
const service = (data = webSourceLocationFixture(), port = decoder) =>
new WebSourceLocationService({ load: () => Promise.resolve(ok(data)) }, port);
it("joins byte identity and historical capture context while keeping selected authenticity/execution unknown", async () => {
const data = webSourceLocationFixture();
data.manifest.source_evidence_id = `ev_${"a".repeat(64)}`;
const response = await service(data).trace(args);
if (!response.ok) throw response.error;
const result = webSourceLocationResultSchema.parse(
response.value.normalized_result,
);
expect(result).toMatchObject({
source: { sha256: data.sourceFile.sha256 },
source_map: {
association: "caller-selected",
sha256: data.sourceMap.file.sha256,
},
matches: [{ state: "mapped", content: { text: "original" } }],
execution: "unknown",
source_authenticity: "unknown",
});
expect(response.value.raw_result).toEqual({
source_map_text: data.sourceMap.text,
});
});
it.each([
{ ...args, manifest_path: "relative.json" },
{ ...args, script_index: -1 },
{ ...args, source_map: { ...args.source_map, path: "relative.map" } },
{ ...args, source_map: { ...args.source_map, url: "relative.map" } },
{ ...args, generated_position: { line: 0, column: 0 } },
])(
"rejects invalid selected context before acquiring artifacts: %j",
async (input) => {
const response = await new WebSourceLocationService(
{
load: () => {
throw new Error("must not acquire");
},
},
decoder,
).trace(input);
if (response.ok) throw new Error("expected invalid input");
expect(projectAnalysisError(response.error).code).toBe("invalid_request");
},
);
it.each([
"file:///analysis/app.map",
"webpack:///bundle/app.map",
"https://selected-user:selected-value@app.test/maps/app.map",
])(
"retains an absolute inert map context without fetching: %s",
async (url) => {
const fixture = webSourceLocationFixture();
const data = { ...fixture, sourceMap: { ...fixture.sourceMap, url } };
const response = await service(data).trace({
...args,
source_map: { ...args.source_map, url },
});
if (!response.ok) throw response.error;
expect(
webSourceLocationResultSchema.parse(response.value.normalized_result)
.url_context,
).toBe(url);
},
);
it.each(["source", "map", "map-path", "map-url"])(
"rejects changed artifact identity before decoding: %s",
async (variant) => {
const fixture = webSourceLocationFixture();
const data = {
...fixture,
source: variant === "source" ? "different" : fixture.source,
sourceMap: { ...fixture.sourceMap },
};
if (variant === "map") data.sourceMap.text += " ";
if (variant === "map-path") data.sourceMap.file.path = "/other.map";
if (variant === "map-url") data.sourceMap.url = "https://other.test/";
const response = await service(data, {
trace: () => {
throw new Error("must not decode");
},
}).trace(args);
if (response.ok) throw new Error("expected changed identity failure");
expect(response.error._tag).toBe("AnalysisOutputError");
},
);
it.each(["digest", "url", "point"])(
"rejects changed codec identity: %s",
async (variant) => {
const port: WebSourceMapPort = {
trace: async (input) => {
const decoded = await decoder.trace(input);
if (!decoded.ok) return decoded;
const report = decoded.value;
if (variant === "digest") report.source_map_sha256 = "f".repeat(64);
if (variant === "url") report.url_context = "https://other.test/";
if (variant === "point") report.lookup.requested.column += 1;
return Promise.resolve(ok(report));
},
};
const response = await service(undefined, port).trace(args);
if (response.ok) throw new Error("expected changed codec identity failure");
expect(response.error._tag).toBe("AnalysisOutputError");
},
);
it("rejects a generated point outside retained text before decoding", async () => {
const response = await service(undefined, {
trace: () => {
throw new Error("must not decode");
},
}).trace({ ...args, generated_position: { line: 2, column: 0 } });
if (response.ok) throw new Error("expected invalid point");
expect(projectAnalysisError(response.error)).toMatchObject({
code: "invalid_request",
details: { issues: [{ message: expect.stringContaining("outside") }] },
});
});
it("honors cancellation both before acquisition and across decoder completion", async () => {
const first = new AbortController();
first.abort();
const response = await new WebSourceLocationService(
{
load: () => {
throw new Error("must not acquire");
},
},
decoder,
).trace(args, { signal: first.signal });
expect(response.ok).toBe(false);
const controller = new AbortController();
const late = await service(undefined, {
trace: (input) => {
controller.abort();
return decoder.trace(input);
},
}).trace(args, { signal: controller.signal });
if (late.ok) throw new Error("expected cancellation");
expect(late.error._tag).toBe("AnalysisCancelledError");
});