* feat(browser): trace captured website module imports Compose verified exported scripts, inert native import syntax and a narrow Chromium resolution port. Preserve query/fragment identity, import-map context, all reported capture candidates and explicit unknown execution. Share named CLI/MCP contracts and retain native reports, diagnostics and cleanup failures. Add focused boundary regressions and a real module-loading oracle to the existing conditional Chrome lane. No captured application code is executed by module tracing, and no selected website asset is refetched. Refs #851, #774 Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): preserve module contexts and source bindings Keep capture completeness inline, validate actual source text against its digest, and separate unsupported URL contexts from malformed input. Use the importer origin for native module resolution and the selected map base independently, including mixed HTTP/HTTPS and escaped HTML attributes. Refs #851, #774 Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { NativeModuleResolver } from "./NativeModuleResolver.js";
|
|
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
|
|
|
const input = {
|
|
importerUrl: "https://app.test/main.js",
|
|
importMap: null,
|
|
specifiers: ["./dep.js"],
|
|
};
|
|
describe("optional native resolution acquisition", () => {
|
|
it("awaits owned cleanup when cancellation arrives during acquisition", async () => {
|
|
const controller = new AbortController();
|
|
let closed = false;
|
|
const provider = new NativeModuleResolver(
|
|
{ REA_BROWSER_EXECUTABLE: "/browser" },
|
|
() => {
|
|
controller.abort();
|
|
return Promise.resolve({
|
|
version: () => "fixture",
|
|
newContext: () => {
|
|
throw new Error("must not acquire context");
|
|
},
|
|
close: () => {
|
|
closed = true;
|
|
return Promise.resolve();
|
|
},
|
|
});
|
|
},
|
|
);
|
|
const result = await provider.resolve(input, { signal: controller.signal });
|
|
expect(closed).toBe(true);
|
|
if (result.ok) throw new Error("expected cancelled result");
|
|
expect(result.error._tag).toBe("AnalysisCancelledError");
|
|
});
|
|
it("returns cleanup_incomplete when owned cleanup fails after an engine error", async () => {
|
|
const provider = new NativeModuleResolver(
|
|
{ REA_BROWSER_EXECUTABLE: "/browser" },
|
|
() =>
|
|
Promise.resolve({
|
|
version: () => "fixture",
|
|
newContext: () =>
|
|
Promise.reject(new Error("fixture context failure")),
|
|
close: () =>
|
|
Promise.reject(new Error("fixture owned cleanup refusal")),
|
|
}),
|
|
);
|
|
const result = await provider.resolve(input);
|
|
if (result.ok) throw new Error("expected cleanup failure");
|
|
expect(projectAnalysisError(result.error).code).toBe("cleanup_incomplete");
|
|
expect(result.error.cleanupIncomplete).toBe(true);
|
|
});
|
|
it("does not load the browser engine when the selected operation lacks its configuration", async () => {
|
|
const provider = new NativeModuleResolver({}, () => {
|
|
throw new Error("must not launch");
|
|
});
|
|
const result = await provider.resolve(input);
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok)
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "capability_unavailable",
|
|
message: expect.stringContaining("REA_BROWSER_EXECUTABLE"),
|
|
});
|
|
});
|
|
it("does not acquire a browser after caller cancellation", async () => {
|
|
const controller = new AbortController();
|
|
controller.abort();
|
|
const provider = new NativeModuleResolver(
|
|
{ REA_BROWSER_EXECUTABLE: "/browser" },
|
|
() => {
|
|
throw new Error("must not launch");
|
|
},
|
|
);
|
|
const result = await provider.resolve(input, { signal: controller.signal });
|
|
if (result.ok) throw new Error("expected cancellation");
|
|
expect(result.error._tag).toBe("AnalysisCancelledError");
|
|
});
|
|
it("retains the selected executable and actual startup diagnostic", async () => {
|
|
const provider = new NativeModuleResolver(
|
|
{ REA_BROWSER_EXECUTABLE: "/missing/browser" },
|
|
() => Promise.reject(new Error("spawn EACCES fixture")),
|
|
);
|
|
const result = await provider.resolve(input);
|
|
if (result.ok) throw new Error("expected startup failure");
|
|
expect(projectAnalysisError(result.error).details).toMatchObject({
|
|
diagnostics: {
|
|
executable: "/missing/browser",
|
|
error_message: "spawn EACCES fixture",
|
|
},
|
|
});
|
|
});
|
|
});
|