diff --git a/src/application/desktop/DesktopApplicationState.test.ts b/src/application/desktop/DesktopApplicationState.test.ts index 8ccc3d3b0f..2be605bd5e 100644 --- a/src/application/desktop/DesktopApplicationState.test.ts +++ b/src/application/desktop/DesktopApplicationState.test.ts @@ -910,6 +910,7 @@ describe("inspectDesktopApplicationState database sidecars", () => { it("reports cancellation during the last database read instead of Evidence", async () => { const controller = new AbortController(); + const reason = { stage: "last database read" }; let reads = 0; const folder = await writeCaptureFolder( extended({ @@ -925,7 +926,7 @@ describe("inspectDesktopApplicationState database sidecars", () => { ...readers, sqlite: async (database, signal) => { reads += 1; - if (reads === 3) controller.abort(new Error("stopped")); + if (reads === 3) controller.abort(reason); return readers.sqlite(database, signal); }, }, @@ -934,6 +935,9 @@ describe("inspectDesktopApplicationState database sidecars", () => { expect(reads).toBe(3); expect(result).toBeInstanceOf(AnalysisCancelledError); + expect(result instanceof AnalysisCancelledError && result.cause).toBe( + reason, + ); }); }); diff --git a/src/application/desktop/DesktopBuildSolves.test.ts b/src/application/desktop/DesktopBuildSolves.test.ts index 0cbcf77fc9..9648775030 100644 --- a/src/application/desktop/DesktopBuildSolves.test.ts +++ b/src/application/desktop/DesktopBuildSolves.test.ts @@ -321,6 +321,7 @@ it.each([ expect(result.error.cleanupResources).toContain(join(bundle, suffix)); if (cancel) { expect(result.error._tag).toBe("AnalysisCancelledError"); + expect(result.error.cause).toBe(reason); expect(result.error.partialObservation).toMatchObject({ kind: "partial-task-measurement", solves: [{}, {}, {}], diff --git a/src/application/desktop/DesktopBuildTurns.test.ts b/src/application/desktop/DesktopBuildTurns.test.ts index 33705bfd9a..b74464ce81 100644 --- a/src/application/desktop/DesktopBuildTurns.test.ts +++ b/src/application/desktop/DesktopBuildTurns.test.ts @@ -57,11 +57,14 @@ it("keeps the place of a call cancelled while it waits, so the one after it stil return ok("last"); }); await settled(); - caller.abort(new Error("the client went away")); - expect(await cancelled).toMatchObject({ + const reason = { caller: "the client went away" }; + caller.abort(reason); + const cancellation = await cancelled; + expect(cancellation).toMatchObject({ ok: false, error: { _tag: "AnalysisCancelledError", operation: "cancelled" }, }); + expect(!cancellation.ok && cancellation.error.cause).toBe(reason); await settled(); expect(log).toEqual(["running"]); running.end(); diff --git a/src/application/desktop/DesktopRemoteTaskReaders.test.ts b/src/application/desktop/DesktopRemoteTaskReaders.test.ts index f2aac76297..e8eb37d366 100644 --- a/src/application/desktop/DesktopRemoteTaskReaders.test.ts +++ b/src/application/desktop/DesktopRemoteTaskReaders.test.ts @@ -81,9 +81,9 @@ it("waits for an interrupted reader before removing its folder", async () => { it("preserves a completed view when cancellation arrives during successful cleanup", async () => { const controller = new AbortController(); + const reason = { stage: "reader final cleanup" }; const owner = new DesktopRemoteTaskReaders(async (argv) => { - if (argv[3] === PYTHON_READER_CLEANUP) - controller.abort(new Error("cancelled")); + if (argv[3] === PYTHON_READER_CLEANUP) controller.abort(reason); return success; }); const failure = await owner @@ -91,6 +91,7 @@ it("preserves a completed view when cancellation arrives during successful clean .catch((error: unknown) => error); if (!(failure instanceof AnalysisCancelledError)) throw new Error("expected cancellation"); + expect(failure.cause).toBe(reason); expect(failure.diagnostics).toMatchObject({ completed_reader_views: { note: { view: { value: 7 } } }, }); @@ -99,12 +100,13 @@ it("preserves a completed view when cancellation arrives during successful clean it("keeps cancellation and a completed view when final cleanup also fails", async () => { const controller = new AbortController(); + const reason = { stage: "reader final cleanup" }; let fail = true; const owner = new DesktopRemoteTaskReaders(async (argv) => { if (argv[3] !== PYTHON_READER_CLEANUP) return success; if (fail) { fail = false; - controller.abort(new Error("cancelled")); + controller.abort(reason); return { ...success, exit_code: 1, stderr: "denied" }; } return success; @@ -115,6 +117,7 @@ it("keeps cancellation and a completed view when final cleanup also fails", asyn if (!(failure instanceof ProviderCleanupError)) throw new Error("expected cleanup failure"); expect(failure.cleanupPrimary).toBeInstanceOf(AnalysisCancelledError); + expect(failure.cleanupPrimary?.cause).toBe(reason); expect(failure.diagnostics).toMatchObject({ completed_reader_views: { note: { view: { value: 7 } } }, }); diff --git a/src/browser/CdpCaptureValues.test.ts b/src/browser/CdpCaptureValues.test.ts index 83d5b5adf4..ed95cb551b 100644 --- a/src/browser/CdpCaptureValues.test.ts +++ b/src/browser/CdpCaptureValues.test.ts @@ -37,7 +37,9 @@ describe("cancellable browser observation delays", () => { ).catch((error: unknown) => error); expect(beforeStart).toBeInstanceOf(AnalysisCancelledError); - expect(beforeStart).toMatchObject({ cause: preAbortedReason }); + expect( + beforeStart instanceof AnalysisCancelledError && beforeStart.cause, + ).toBe(preAbortedReason); vi.useFakeTimers(); const reason = new Error("capture wait cancelled"); @@ -51,7 +53,9 @@ describe("cancellable browser observation delays", () => { const duringWait = await waiting; expect(duringWait).toBeInstanceOf(AnalysisCancelledError); - expect(duringWait).toMatchObject({ cause: reason }); + expect( + duringWait instanceof AnalysisCancelledError && duringWait.cause, + ).toBe(reason); expect(vi.getTimerCount()).toBe(0); }); }); diff --git a/src/browser/CdpEndpoint.test.ts b/src/browser/CdpEndpoint.test.ts index 9daa4e4a4f..5f98fe9970 100644 --- a/src/browser/CdpEndpoint.test.ts +++ b/src/browser/CdpEndpoint.test.ts @@ -136,7 +136,9 @@ describe("CDP discovery cancellation ownership", () => { ).catch((error: unknown) => error); expect(failure).toBeInstanceOf(AnalysisCancelledError); - expect(failure).toMatchObject({ cause: reason }); + expect(failure instanceof AnalysisCancelledError && failure.cause).toBe( + reason, + ); expect(requests).toBe(0); }); @@ -164,7 +166,9 @@ describe("CDP discovery cancellation ownership", () => { expect(requestedPath).toBe("/json/version"); expect(failure).toBeInstanceOf(AnalysisCancelledError); - expect(failure).toMatchObject({ cause: reason }); + expect(failure instanceof AnalysisCancelledError && failure.cause).toBe( + reason, + ); }); it("does not replace a primary discovery failure when cancellation arrives later", async () => { @@ -259,7 +263,9 @@ describe("CDP discovery cancellation ownership", () => { ).catch((error: unknown) => error); expect(failure).toBeInstanceOf(AnalysisCancelledError); - expect(failure).toMatchObject({ cause: reason }); + expect(failure instanceof AnalysisCancelledError && failure.cause).toBe( + reason, + ); }); it("keeps a deadline rejection when the caller aborts only after timeout", async () => { diff --git a/src/browser/CdpPageCaptureScripts.test.ts b/src/browser/CdpPageCaptureScripts.test.ts index 19b7bfccc0..93fd023d1f 100644 --- a/src/browser/CdpPageCaptureScripts.test.ts +++ b/src/browser/CdpPageCaptureScripts.test.ts @@ -112,15 +112,17 @@ it("preserves the caller cancellation reason when a source read is pending", asy signal: controller.signal, } as unknown as CaptureContext; - await expect( - captureScripts({ - context, - events, - rawResources: [], - resources: [], - frameIds: new Set(), - }), - ).rejects.toMatchObject({ _tag: "AnalysisCancelledError", cause: reason }); + const failure = await captureScripts({ + context, + events, + rawResources: [], + resources: [], + frameIds: new Set(), + }).catch((error: unknown) => error); + expect(failure).toBeInstanceOf(AnalysisCancelledError); + expect(failure instanceof AnalysisCancelledError && failure.cause).toBe( + reason, + ); }); it.each([