mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 22:06:10 +00:00
fix(browser): retain module resolution through finalization
This commit is contained in:
1 parent
52b73ee857
commit
94bc7ed322
9 files changed
+923
-222
No files matched your search
@@ -1,16 +1,23 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { WebModuleTraceService } from "./WebModuleTraceService.js";
|
||||
import { ok } from "../domain/result.js";
|
||||
import { err, ok } from "../domain/result.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import {
|
||||
webModuleTraceResultSchema,
|
||||
type WebModuleResolution,
|
||||
type WebModuleResolutionRequest,
|
||||
} from "../domain/webModuleTrace.js";
|
||||
import {
|
||||
webModuleArtifactsFixture,
|
||||
webModuleResolverFixture,
|
||||
} from "../../tests/fixtures/webModuleTrace.js";
|
||||
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { parseEvidence } from "../domain/evidence.js";
|
||||
|
||||
const args = { manifest_path: "/analysis/manifest.json", script_index: 0 };
|
||||
it.each([
|
||||
{ ...args, importer_url: "file:///tmp/main.js" },
|
||||
@@ -135,7 +142,7 @@ describe("selected module trace workflow", () => {
|
||||
});
|
||||
it("honors cancellation before any artifacts are acquired", async () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
controller.abort(null);
|
||||
const service = new WebModuleTraceService(
|
||||
{
|
||||
load: () => {
|
||||
@@ -146,6 +153,219 @@ describe("selected module trace workflow", () => {
|
||||
);
|
||||
const result = await service.trace(args, { signal: controller.signal });
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
if (!result.ok) {
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it.each([null, { request: "opaque cancellation" }])(
|
||||
"retains completed module Evidence and the exact late abort reason: %j",
|
||||
async (reason) => {
|
||||
const controller = new AbortController();
|
||||
const artifacts = webModuleArtifactsFixture();
|
||||
const service = new WebModuleTraceService(
|
||||
{ load: async () => ok(artifacts) },
|
||||
{
|
||||
resolve: async (input) => {
|
||||
const result = await webModuleResolverFixture.resolve(input);
|
||||
controller.abort(reason);
|
||||
return result;
|
||||
},
|
||||
},
|
||||
);
|
||||
const result = await service.trace(args, { signal: controller.signal });
|
||||
if (result.ok) throw new Error("expected cancelled completion");
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "trace_web_module_imports",
|
||||
confidence: "derived",
|
||||
normalized_result: {
|
||||
source: artifacts.sourceFile,
|
||||
imports: [
|
||||
{
|
||||
resolution: { state: "resolved", url: "https://app.test/dep.js" },
|
||||
execution: "unknown",
|
||||
},
|
||||
{
|
||||
resolution: { state: "unknown", reason: "computed-specifier" },
|
||||
execution: "unknown",
|
||||
},
|
||||
],
|
||||
},
|
||||
raw_result: { fixture: true },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("does not start the native resolver after cancellation during artifact admission", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = { request: "cancelled local artifact read" };
|
||||
const result = await new WebModuleTraceService(
|
||||
{
|
||||
load: async () => {
|
||||
controller.abort(reason);
|
||||
return ok(webModuleArtifactsFixture());
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: async () => {
|
||||
throw new Error("must not start resolver");
|
||||
},
|
||||
},
|
||||
).trace(args, { signal: controller.signal });
|
||||
if (result.ok) throw new Error("expected cancelled admission");
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
});
|
||||
|
||||
it.each(["incomplete", "provider-failure"])(
|
||||
"keeps resolver %s ahead of late cancellation",
|
||||
async (kind) => {
|
||||
const controller = new AbortController();
|
||||
const primary = new AnalysisOutputError(
|
||||
"trace_web_module_imports",
|
||||
"controlled resolver failure",
|
||||
);
|
||||
const result = await new WebModuleTraceService(
|
||||
{ load: async () => ok(webModuleArtifactsFixture()) },
|
||||
{
|
||||
resolve: async () => {
|
||||
controller.abort({ request: "late cancellation" });
|
||||
return kind === "provider-failure"
|
||||
? err(primary)
|
||||
: ok({
|
||||
engine: { id: "fixture", version: "1" },
|
||||
resolutions: [],
|
||||
diagnostics: [],
|
||||
rawResult: {},
|
||||
});
|
||||
},
|
||||
},
|
||||
).trace(args, { signal: controller.signal });
|
||||
if (result.ok) throw new Error("expected original failure");
|
||||
expect(result.error).toBeInstanceOf(AnalysisOutputError);
|
||||
if (kind === "provider-failure") expect(result.error).toBe(primary);
|
||||
expect(result.error.partialObservation).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"matching",
|
||||
"importer",
|
||||
"map-base",
|
||||
"map-value",
|
||||
"specifiers",
|
||||
"missing-resolution",
|
||||
])(
|
||||
"adopts only a complete resolution for the exact selected request: %s",
|
||||
async (kind) => {
|
||||
const artifacts = webModuleArtifactsFixture();
|
||||
const importMap = {
|
||||
file: { path: "/analysis/map.json", sha256: "c".repeat(64), bytes: 2 },
|
||||
baseUrl: "https://app.test/maps/",
|
||||
value: { imports: { dep: "./dep.js" } },
|
||||
};
|
||||
let failure: AnalysisCancelledError | undefined;
|
||||
const result = await new WebModuleTraceService(
|
||||
{ load: async () => ok({ ...artifacts, importMap }) },
|
||||
{
|
||||
resolve: async (request) => {
|
||||
const resolved = await webModuleResolverFixture.resolve(request);
|
||||
if (!resolved.ok) throw resolved.error;
|
||||
let observed: WebModuleResolutionRequest = request;
|
||||
if (kind === "importer")
|
||||
observed = { ...request, importerUrl: "https://other.test/" };
|
||||
if (kind === "map-base")
|
||||
observed = {
|
||||
...request,
|
||||
importMap: {
|
||||
baseUrl: "https://other.test/",
|
||||
value: importMap.value,
|
||||
},
|
||||
};
|
||||
if (kind === "map-value")
|
||||
observed = {
|
||||
...request,
|
||||
importMap: {
|
||||
baseUrl: importMap.baseUrl,
|
||||
value: { imports: { dep: "./other.js" } },
|
||||
},
|
||||
};
|
||||
if (kind === "specifiers")
|
||||
observed = { ...request, specifiers: ["./other.js"] };
|
||||
failure = new AnalysisCancelledError("trace_web_module_imports", {
|
||||
cause: null,
|
||||
partialObservation: {
|
||||
kind: "web-module-resolution",
|
||||
request: observed,
|
||||
batch:
|
||||
kind === "missing-resolution"
|
||||
? { ...resolved.value, resolutions: [] }
|
||||
: resolved.value,
|
||||
},
|
||||
});
|
||||
return err(failure);
|
||||
},
|
||||
},
|
||||
).trace({
|
||||
...args,
|
||||
import_map: { path: importMap.file.path, base_url: importMap.baseUrl },
|
||||
});
|
||||
if (result.ok) throw new Error("expected original cancellation");
|
||||
expect(result.error).toBe(failure);
|
||||
expect(result.error.cause).toBeNull();
|
||||
if (kind === "matching") {
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "trace_web_module_imports",
|
||||
normalized_result: {
|
||||
import_map: {
|
||||
path: importMap.file.path,
|
||||
base_url: importMap.baseUrl,
|
||||
},
|
||||
imports: [
|
||||
{
|
||||
resolution: { state: "resolved", url: "https://app.test/dep.js" },
|
||||
},
|
||||
{ resolution: { state: "unknown", reason: "computed-specifier" } },
|
||||
],
|
||||
},
|
||||
});
|
||||
} else
|
||||
expect(result.error.partialObservation).toHaveProperty(
|
||||
"kind",
|
||||
"web-module-resolution",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("retains a completed engine-free trace when artifact loading finishes under cancellation", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = { request: "engine-free completion" };
|
||||
const result = await new WebModuleTraceService(
|
||||
{
|
||||
load: async () => {
|
||||
controller.abort(reason);
|
||||
return ok(webModuleArtifactsFixture("import(name)"));
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: async () => {
|
||||
throw new Error("no literal imports to resolve");
|
||||
},
|
||||
},
|
||||
).trace(args, { signal: controller.signal });
|
||||
if (result.ok) throw new Error("expected cancelled completion");
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "trace_web_module_imports",
|
||||
normalized_result: {
|
||||
engine: null,
|
||||
imports: [
|
||||
{ resolution: { state: "unknown", reason: "computed-specifier" } },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user