diff --git a/src/application/binaryDiagnostics/AppleIpsService.test.ts b/src/application/binaryDiagnostics/AppleIpsService.test.ts index 894e045b52..1784b7dc70 100644 --- a/src/application/binaryDiagnostics/AppleIpsService.test.ts +++ b/src/application/binaryDiagnostics/AppleIpsService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { expect, it } from "vitest"; import { AppleIpsService } from "./AppleIpsService.js"; import { APPLE_IPS_PROVIDER } from "../../domain/investigationProviders.js"; @@ -232,12 +233,13 @@ it.each([ }, ); -it("discards an observation if the caller cancels while the provider completes", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late AppleIps cancellation" }; const service = new AppleIpsService({ identity, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report)); }, }); @@ -245,8 +247,13 @@ it("discards an observation if the caller cancels while the provider completes", { path: report.artifact.path }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_apple_ips", + normalized_result: report, + }); }); it.each([ diff --git a/src/application/binaryDiagnostics/DevicetreeService.test.ts b/src/application/binaryDiagnostics/DevicetreeService.test.ts index 51b9d11fde..6fe7da18eb 100644 --- a/src/application/binaryDiagnostics/DevicetreeService.test.ts +++ b/src/application/binaryDiagnostics/DevicetreeService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { expect, it } from "vitest"; import { DevicetreeService } from "./DevicetreeService.js"; import { DEVICETREE_PROVIDER } from "../../domain/investigationProviders.js"; @@ -72,12 +73,13 @@ it("validates caller paths before invoking the provider", async () => { if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisInputError); expect(calls).toBe(0); }); -it("rejects cancellation at provider completion", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late Devicetree cancellation" }; const service = new DevicetreeService({ identity: DEVICETREE_PROVIDER, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report())); }, }); @@ -85,8 +87,13 @@ it("rejects cancellation at provider completion", async () => { { path: "/board.dtb" }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_devicetree", + normalized_result: report(), + }); }); it("rejects provider coverage and source-range inconsistencies", async () => { for (const change of [ diff --git a/src/application/binaryDiagnostics/ElfNotesService.test.ts b/src/application/binaryDiagnostics/ElfNotesService.test.ts index 75547fc619..f71883719e 100644 --- a/src/application/binaryDiagnostics/ElfNotesService.test.ts +++ b/src/application/binaryDiagnostics/ElfNotesService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { expect, it } from "vitest"; import { ElfNotesService } from "./ElfNotesService.js"; import { ELF_NOTES_PROVIDER } from "../../domain/investigationProviders.js"; @@ -106,12 +107,13 @@ it("retains an examined empty table and rejects an uncounted table", async () => } }); -it("discards an observation if the caller cancels while the provider completes", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late ElfNotes cancellation" }; const service = new ElfNotesService({ identity, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report)); }, }); @@ -119,6 +121,11 @@ it("discards an observation if the caller cancels while the provider completes", { path: report.artifact.path }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_elf_notes", + normalized_result: report, + }); }); diff --git a/src/application/binaryDiagnostics/ElfUnwindRangesService.test.ts b/src/application/binaryDiagnostics/ElfUnwindRangesService.test.ts index 88ce45ca43..422233a1a4 100644 --- a/src/application/binaryDiagnostics/ElfUnwindRangesService.test.ts +++ b/src/application/binaryDiagnostics/ElfUnwindRangesService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { expect, it } from "vitest"; import { ElfUnwindRangesService } from "./ElfUnwindRangesService.js"; import { ELF_UNWIND_RANGES_PROVIDER } from "../../domain/investigationProviders.js"; @@ -406,12 +407,13 @@ it("rejects a report that fails the output schema", async () => { if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisOutputError); }); -it("discards an observation if the caller cancels while the provider completes", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late ElfUnwindRanges cancellation" }; const service = new ElfUnwindRangesService({ identity, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report)); }, }); @@ -419,6 +421,11 @@ it("discards an observation if the caller cancels while the provider completes", { path: report.artifact.path }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_elf_unwind_ranges", + normalized_result: report, + }); }); diff --git a/src/application/binaryDiagnostics/NativeInitializersService.test.ts b/src/application/binaryDiagnostics/NativeInitializersService.test.ts index e0d836b181..c01de60fb0 100644 --- a/src/application/binaryDiagnostics/NativeInitializersService.test.ts +++ b/src/application/binaryDiagnostics/NativeInitializersService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { expect, it } from "vitest"; import { NativeInitializersService } from "./NativeInitializersService.js"; import { NATIVE_INITIALIZERS_PROVIDER } from "../../domain/investigationProviders.js"; @@ -113,12 +114,13 @@ it("rejects a slot offset outside the artifact", async () => { if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisOutputError); }); -it("discards an observation if the caller cancels while the provider completes", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late NativeInitializers cancellation" }; const service = new NativeInitializersService({ identity, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report)); }, }); @@ -126,6 +128,11 @@ it("discards an observation if the caller cancels while the provider completes", { path: report.artifact.path }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_native_initializers", + normalized_result: report, + }); }); diff --git a/src/application/binaryDiagnostics/PeCertificatesService.test.ts b/src/application/binaryDiagnostics/PeCertificatesService.test.ts index 5fb1b34213..fa73b22f77 100644 --- a/src/application/binaryDiagnostics/PeCertificatesService.test.ts +++ b/src/application/binaryDiagnostics/PeCertificatesService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { createHash } from "node:crypto"; import { inspectPeCertificatesInputSchema } from "../../domain/native/peCertificates.js"; import { peCertificateFixture } from "../../native/pe/PeCertificates.fixture.js"; @@ -49,12 +50,13 @@ it("rejects a relative path and extra arguments before reading", async () => { expect(calls).toBe(0); }); -it("discards an observation if the caller cancels while the provider completes", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late PeCertificates cancellation" }; const service = new PeCertificatesService({ identity, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report)); }, }); @@ -62,8 +64,13 @@ it("discards an observation if the caller cancels while the provider completes", { path: report.artifact.path }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_pe_certificates", + normalized_result: report, + }); }); it("retains complete certificate provenance from the production parser", async () => { diff --git a/src/application/binaryDiagnostics/PeImportsService.test.ts b/src/application/binaryDiagnostics/PeImportsService.test.ts index 58a342bbd5..c641bf5385 100644 --- a/src/application/binaryDiagnostics/PeImportsService.test.ts +++ b/src/application/binaryDiagnostics/PeImportsService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { expect, it } from "vitest"; import { PeImportsService } from "./PeImportsService.js"; import { PE_IMPORTS_PROVIDER } from "../../domain/investigationProviders.js"; @@ -116,12 +117,13 @@ it.each(["import_directory", "delay_import_directory"] as const)( }, ); -it("discards an observation if the caller cancels while the provider completes", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late PeImports cancellation" }; const service = new PeImportsService({ identity, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report)); }, }); @@ -129,8 +131,13 @@ it("discards an observation if the caller cancels while the provider completes", { path: report.artifact.path }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_pe_imports", + normalized_result: report, + }); }); it("publishes every consumed directory span as its own Evidence location", async () => { diff --git a/src/application/binaryDiagnostics/WindowsShortcutService.test.ts b/src/application/binaryDiagnostics/WindowsShortcutService.test.ts index 8deb031b33..4aa8be43bd 100644 --- a/src/application/binaryDiagnostics/WindowsShortcutService.test.ts +++ b/src/application/binaryDiagnostics/WindowsShortcutService.test.ts @@ -1,3 +1,4 @@ +import { parseEvidence } from "../../domain/evidence.js"; import { expect, it } from "vitest"; import { WindowsShortcutService } from "./WindowsShortcutService.js"; import { WINDOWS_SHORTCUT_PROVIDER } from "../../domain/investigationProviders.js"; @@ -143,12 +144,13 @@ it("rejects complete coverage that still lists issues", async () => { if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisOutputError); }); -it("discards an observation if the caller cancels while the provider completes", async () => { +it("retains a completed observation when the caller cancels as the provider completes", async () => { const controller = new AbortController(); + const reason = { request: "late WindowsShortcut cancellation" }; const service = new WindowsShortcutService({ identity, inspect: () => { - controller.abort(); + controller.abort(reason); return Promise.resolve(ok(report)); }, }); @@ -156,6 +158,11 @@ it("discards an observation if the caller cancels while the provider completes", { path: report.artifact.path }, { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisCancelledError); + if (result.ok) throw new Error("Expected late cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(reason); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "inspect_windows_shortcut", + normalized_result: report, + }); });