From d077d879cce526dc75288010a229112296fcb46b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 20 Oct 2026 20:11:03 +0000 Subject: [PATCH] test(observation): share repeated observation fixtures The incomplete-observation tests repeated the same provider setup and an empty Electron coverage tail, which pushed duplication over 3.5%. One coverage fixture and shared assertions bring the tree back under the threshold. Co-authored-by: morluto --- .../BrowserObservationService.test.ts | 13 +-- .../BrowserScenarioCaptureService.test.ts | 80 ++++++++----------- .../ProviderOperationExecution.test.ts | 30 +++---- src/application/WebRuntimeService.test.ts | 18 ++--- .../ElectronObservationService.test.ts | 69 +++------------- ...avaScriptRuntimeObservationService.test.ts | 77 +++++++----------- .../ownerObservationExpectation.fixture.ts | 28 +++++++ .../electronPageInspection.fixture.ts | 34 ++++++++ tests/boundary/mcp/electronMcp.test.ts | 30 +------ 9 files changed, 165 insertions(+), 214 deletions(-) create mode 100644 src/domain/javascript/electronPageInspection.fixture.ts diff --git a/src/application/BrowserObservationService.test.ts b/src/application/BrowserObservationService.test.ts index 3627206855..b6e33a0e23 100644 --- a/src/application/BrowserObservationService.test.ts +++ b/src/application/BrowserObservationService.test.ts @@ -11,6 +11,7 @@ import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { expectDroppedPartial, expectKeptPartial, + expectOutputError, expectOwnerError, } from "./ownerObservationExpectation.fixture.js"; import { BrowserObservationError } from "../domain/browserObservationError.js"; @@ -80,11 +81,7 @@ it("does not let late cancellation hide invalid completed browser output", async provider(async () => ok(malformed)), input, ); - expect(output).toMatchObject({ - ok: false, - error: { _tag: "AnalysisOutputError" }, - }); - if (!output.ok) expect(output.error).toBeInstanceOf(AnalysisOutputError); + expectOutputError(output); const controller = new AbortController(); const late = await listBrowserTargets( @@ -95,11 +92,7 @@ it("does not let late cancellation hide invalid completed browser output", async input, { signal: controller.signal }, ); - expect(late).toMatchObject({ - ok: false, - error: { _tag: "AnalysisOutputError" }, - }); - if (!late.ok) expect(late.error).toBeInstanceOf(AnalysisOutputError); + expectOutputError(late); }); it("converts a raw page inspection retained after browser cleanup failure", async () => { diff --git a/src/application/BrowserScenarioCaptureService.test.ts b/src/application/BrowserScenarioCaptureService.test.ts index 9f48a2b983..34a3c588cb 100644 --- a/src/application/BrowserScenarioCaptureService.test.ts +++ b/src/application/BrowserScenarioCaptureService.test.ts @@ -1,5 +1,6 @@ import { expect, it } from "vitest"; +import type { AnalysisError } from "../domain/analysisErrorBase.js"; import { AnalysisCancelledError, AnalysisOutputError, @@ -27,6 +28,34 @@ const scenario = browserScenarioInputSchema.parse({ actions: [{ step_id: "settle", action: "wait_for_timeout", duration_ms: 1 }], }); const capture = scriptScenarioFixture(); +const scenarioIdentity = { + id: "scenario-fixture", + name: "Scenario fixture", + version: "1", +}; +const runScenario = ( + failure: AnalysisError, + id = { id: "fixture", name: "fixture", version: "1" }, +) => + captureBrowserScenario( + { + identity: () => id, + captureScenario: async () => err(failure), + }, + scenario, + ); +const expectRetainedCapture = ( + result: Awaited>, + failure: AnalysisError, +) => { + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Expected the retained capture"); + expect(result.error).toBe(failure); + expect(parseEvidence(result.error.partialObservation)).toMatchObject({ + operation: "capture_browser_scenario", + normalized_result: capture, + }); +}; it("converts a retained raw scenario capture into partial Evidence", async () => { const failure = new AnalysisCancelledError("capture_browser_scenario", { @@ -35,23 +64,7 @@ it("converts a retained raw scenario capture into partial Evidence", async () => capture, }, }); - const provider: BrowserScenarioCapturePort = { - identity: () => ({ - id: "scenario-fixture", - name: "Scenario fixture", - version: "1", - }), - captureScenario: async () => err(failure), - }; - const result = await captureBrowserScenario(provider, scenario); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe(failure); - expect(parseEvidence(result.error.partialObservation)).toMatchObject({ - operation: "capture_browser_scenario", - normalized_result: capture, - }); - } + expectRetainedCapture(await runScenario(failure, scenarioIdentity), failure); }); it("converts a raw scenario capture retained after cleanup failure", async () => { @@ -69,24 +82,9 @@ it("converts a raw scenario capture retained after cleanup failure", async () => }, }, ); - const provider: BrowserScenarioCapturePort = { - identity: () => ({ - id: "scenario-fixture", - name: "Scenario fixture", - version: "1", - }), - captureScenario: async () => err(failure), - }; - const result = await captureBrowserScenario(provider, scenario); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBe(failure); - expect(result.error.cleanupIncomplete).toBe(true); - expect(parseEvidence(result.error.partialObservation)).toMatchObject({ - operation: "capture_browser_scenario", - normalized_result: capture, - }); - } + const result = await runScenario(failure, scenarioIdentity); + expectRetainedCapture(result, failure); + if (!result.ok) expect(result.error.cleanupIncomplete).toBe(true); }); it.each([ @@ -137,11 +135,7 @@ it("keeps startup cancellation and its incomplete capture", async () => { cause: reason, partialObservation: partial, }); - const provider: BrowserScenarioCapturePort = { - identity: () => ({ id: "fixture", name: "fixture", version: "1" }), - captureScenario: async () => err(failure), - }; - const result = await captureBrowserScenario(provider, scenario); + const result = await runScenario(failure); expectKeptPartial(expectOwnerError(result, failure, reason), partial); }); @@ -157,11 +151,7 @@ it.each([{ steps: [undefined] }, { started_at: Number.NaN }])( cause: reason, partialObservation: partial, }); - const provider: BrowserScenarioCapturePort = { - identity: () => ({ id: "fixture", name: "fixture", version: "1" }), - captureScenario: async () => err(failure), - }; - const result = await captureBrowserScenario(provider, scenario); + const result = await runScenario(failure); expectDroppedPartial( expectOwnerError(result, failure, reason), "Browser scenario provider retained a non-JSON incomplete observation.", diff --git a/src/application/ProviderOperationExecution.test.ts b/src/application/ProviderOperationExecution.test.ts index e2d944761d..f3a36e1b5a 100644 --- a/src/application/ProviderOperationExecution.test.ts +++ b/src/application/ProviderOperationExecution.test.ts @@ -98,17 +98,23 @@ it("preserves operation correction outside the input envelope", async () => { expect(execute).not.toHaveBeenCalled(); }); -it("clears a non-JSON partial from the cleanup primary projection returns after retry", () => { - const partial = { steps: [] as unknown[] }; - const primary = new AnalysisCancelledError("capture_browser_scenario", { +const cancelledScenario = (partial: unknown) => + new AnalysisCancelledError("capture_browser_scenario", { cause: { requested: "stop during capture" }, partialObservation: partial as never, }); - const composed = analysisErrorWithCleanupFailure( + +const wrapScenarioCleanup = (primary: AnalysisCancelledError) => + analysisErrorWithCleanupFailure( primary, new ProviderCleanupError("fixture", ["browser"], {}), "capture_browser_scenario", ); + +it("clears a non-JSON partial from the cleanup primary projection returns after retry", () => { + const partial = { steps: [] as unknown[] }; + const primary = cancelledScenario(partial); + const composed = wrapScenarioCleanup(primary); partial.steps.push(undefined); const reason = "Browser scenario provider retained a non-JSON incomplete observation."; @@ -139,15 +145,8 @@ it("clears a non-JSON partial from the cleanup primary projection returns after it("freezes one shared snapshot for a cleanup wrapper and its primary", () => { const partial = { steps: ["kept"] }; - const primary = new AnalysisCancelledError("capture_browser_scenario", { - cause: { requested: "stop during capture" }, - partialObservation: partial as never, - }); - const composed = analysisErrorWithCleanupFailure( - primary, - new ProviderCleanupError("fixture", ["browser"], {}), - "capture_browser_scenario", - ); + const primary = cancelledScenario(partial); + const composed = wrapScenarioCleanup(primary); keepIncompleteObservation( composed, "capture_browser_scenario", @@ -163,10 +162,7 @@ it("freezes one shared snapshot for a cleanup wrapper and its primary", () => { it("freezes a retained partial so a later mutation cannot break projection", () => { const partial = { steps: [] as unknown[] }; - const primary = new AnalysisCancelledError("capture_browser_scenario", { - cause: { requested: "stop during capture" }, - partialObservation: partial as never, - }); + const primary = cancelledScenario(partial); keepIncompleteObservation( primary, "capture_browser_scenario", diff --git a/src/application/WebRuntimeService.test.ts b/src/application/WebRuntimeService.test.ts index 8e9d34afff..f28be157d9 100644 --- a/src/application/WebRuntimeService.test.ts +++ b/src/application/WebRuntimeService.test.ts @@ -93,11 +93,7 @@ it("converts raw execution retained by provider cleanup failure into partial Evi { operation: "observe_web_execution", partialObservation: execution }, ); const result = await new WebRuntimeService({ - identity: () => ({ - id: "web-runtime", - name: "Runtime fixture", - version: "1", - }), + identity: runtimeIdentity, observeExecution: async () => err(failure), inspectEventListeners: async () => err(failure), }).observe(args); @@ -172,16 +168,18 @@ it("retains completed execution Evidence on late abort and keeps invalid output if (!invalid.ok) expect(invalid.error).toBeInstanceOf(AnalysisOutputError); }); +const runtimeIdentity = () => ({ + id: "web-runtime", + name: "Runtime fixture", + version: "1", +}); + const callRuntime = ( operation: "observe" | "inspect", failure: AnalysisError, ) => { const service = new WebRuntimeService({ - identity: () => ({ - id: "web-runtime", - name: "Runtime fixture", - version: "1", - }), + identity: runtimeIdentity, observeExecution: async () => err(failure), inspectEventListeners: async () => err(failure), }); diff --git a/src/application/javascript/ElectronObservationService.test.ts b/src/application/javascript/ElectronObservationService.test.ts index 1d974b8304..01e9e8c0e2 100644 --- a/src/application/javascript/ElectronObservationService.test.ts +++ b/src/application/javascript/ElectronObservationService.test.ts @@ -1,6 +1,5 @@ import { expect, it } from "vitest"; -import { classifyBrowserCompleteness } from "../../domain/browserCompleteness.js"; import { AnalysisCancelledError, AnalysisOutputError, @@ -12,6 +11,8 @@ import { electronTargetListSchema, inspectElectronPageInputSchema, } from "../../domain/javascript/electronObservation.js"; +import { emptyElectronPageCoverage } from "../../domain/javascript/electronPageInspection.fixture.js"; +import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { err, ok } from "../../domain/result.js"; import type { ElectronObservationPort } from "./ElectronObservationPort.js"; import { @@ -25,6 +26,12 @@ import { } from "../ownerObservationExpectation.fixture.js"; const identity = { id: "electron-fixture", name: "Fixture", version: "1" }; +const pageProvider = (failure: AnalysisError): ElectronObservationPort => ({ + identity: () => identity, + listTargets: async () => + err(new AnalysisCancelledError("list_electron_targets")), + inspectPage: async () => err(failure), +}); const input = inspectElectronPageInputSchema.parse({ cdp_endpoint: "http://127.0.0.1:9222", target_id: "electron-page", @@ -45,33 +52,7 @@ const inspection = electronPageInspectionSchema.parse({ file_path: "/app/index.html", attached: false, }, - capture_window: { - started_at: "2026-01-01T00:00:00.000Z", - ended_at: "2026-01-01T00:00:00.000Z", - observation_ms: 0, - }, - completeness: classifyBrowserCompleteness({ - policyFilteredSections: new Set(), - attachLimitedSections: new Set(), - truncatedSections: new Set(), - unavailableSections: new Set(), - excluded: [], - droppedEvents: { - scripts: 0, - network_requests: 0, - console_events: 0, - websocket_connections: 0, - websocket_frames: 0, - webmcp_tools: 0, - timeline_events: 0, - }, - }), - frames: [], - dom: { total_nodes: 0, nodes: [] }, - scripts: { total: 0, items: [] }, - resources: [], - workers: [], - limitations: [], + ...emptyElectronPageCoverage(), }); it("converts a retained raw Electron inspection into partial Evidence", async () => { @@ -82,13 +63,7 @@ it("converts a retained raw Electron inspection into partial Evidence", async () cleanup: "transport-closed", }, }); - const provider: ElectronObservationPort = { - identity: () => identity, - listTargets: async () => - err(new AnalysisCancelledError("list_electron_targets")), - inspectPage: async () => err(failure), - }; - const result = await inspectElectronPage(provider, input); + const result = await inspectElectronPage(pageProvider(failure), input); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error).toBe(failure); @@ -121,13 +96,7 @@ it("converts a raw inspection retained by provider cleanup failure", async () => }, }, ); - const provider: ElectronObservationPort = { - identity: () => identity, - listTargets: async () => - err(new AnalysisCancelledError("list_electron_targets")), - inspectPage: async () => err(failure), - }; - const result = await inspectElectronPage(provider, input); + const result = await inspectElectronPage(pageProvider(failure), input); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error).toBe(failure); @@ -261,13 +230,7 @@ it("keeps cancellation when the retained Electron inspection is incomplete", asy cause: reason, partialObservation: partial as never, }); - const provider: ElectronObservationPort = { - identity: () => identity, - listTargets: async () => - err(new AnalysisCancelledError("list_electron_targets")), - inspectPage: async () => err(failure), - }; - const result = await inspectElectronPage(provider, input); + const result = await inspectElectronPage(pageProvider(failure), input); expectKeptPartial(expectOwnerError(result, failure, reason), partial); }); @@ -288,13 +251,7 @@ it.each([ ...unsafe, } as never, }); - const provider: ElectronObservationPort = { - identity: () => identity, - listTargets: async () => - err(new AnalysisCancelledError("list_electron_targets")), - inspectPage: async () => err(failure), - }; - const result = await inspectElectronPage(provider, input); + const result = await inspectElectronPage(pageProvider(failure), input); expectDroppedPartial( expectOwnerError(result, failure, reason), "Electron provider retained a non-JSON incomplete observation.", diff --git a/src/application/javascript/JavaScriptRuntimeObservationService.test.ts b/src/application/javascript/JavaScriptRuntimeObservationService.test.ts index 186161f88b..6ef97ff4c2 100644 --- a/src/application/javascript/JavaScriptRuntimeObservationService.test.ts +++ b/src/application/javascript/JavaScriptRuntimeObservationService.test.ts @@ -17,9 +17,33 @@ import { import { expectDroppedPartial, expectKeptPartial, + expectOutputError, expectOwnerError, + expectPreemptedCancellation, } from "../ownerObservationExpectation.fixture.js"; +const observeCancelled = async (partial: unknown) => { + const reason = { requested: "stop before runtime observation completed" }; + const failure = new AnalysisCancelledError("observe_javascript_runtime", { + cause: reason, + partialObservation: partial as never, + }); + const result = await observeJavaScriptRuntime( + { + identity: () => ({ id: "test", name: "Test", version: "1" }), + listTargets: async () => + err(new AnalysisCancelledError("list_javascript_runtime_targets")), + observe: async () => err(failure), + }, + { + inspector_endpoint: "http://127.0.0.1:9229", + target_id: "target", + observation_ms: 0, + }, + ); + return { result, failure, reason }; +}; + it("preserves failed Inspector cleanup when cancellation occurs during observation", async () => { const controller = new AbortController(); const failure = new BrowserObservationError( @@ -131,11 +155,7 @@ it("does not let late cancellation hide an invalid completed Inspector report", }, { inspector_endpoint: "http://127.0.0.1:9229" }, ); - expect(output).toMatchObject({ - ok: false, - error: { _tag: "AnalysisOutputError" }, - }); - if (!output.ok) expect(output.error).toBeInstanceOf(AnalysisOutputError); + expectOutputError(output); const controller = new AbortController(); const late = await listJavaScriptRuntimeTargets( @@ -152,11 +172,7 @@ it("does not let late cancellation hide an invalid completed Inspector report", { inspector_endpoint: "http://127.0.0.1:9229" }, { signal: controller.signal }, ); - expect(late).toMatchObject({ - ok: false, - error: { _tag: "AnalysisOutputError" }, - }); - if (!late.ok) expect(late.error).toBeInstanceOf(AnalysisOutputError); + expectOutputError(late); }); it("converts a raw runtime observation retained by cleanup failure", async () => { @@ -314,55 +330,20 @@ it.each(["list", "observe"] as const)( { signal: controller.signal }, ); - expect(result.ok).toBe(false); - if (!result.ok) { - expect(result.error).toBeInstanceOf(AnalysisCancelledError); - expect(result.error.cause).toBe(reason); - } - expect(providerCalls).toBe(0); + expectPreemptedCancellation(result, reason, providerCalls); }, ); it("keeps cancellation when the retained runtime observation is incomplete", async () => { - const reason = { requested: "stop before runtime observation completed" }; const partial = { runtime: "partial", scripts: [] }; - const failure = new AnalysisCancelledError("observe_javascript_runtime", { - cause: reason, - partialObservation: partial as never, - }); - const provider: JavaScriptRuntimeObservationPort = { - identity: () => ({ id: "test", name: "Test", version: "1" }), - listTargets: async () => - err(new AnalysisCancelledError("list_javascript_runtime_targets")), - observe: async () => err(failure), - }; - const result = await observeJavaScriptRuntime(provider, { - inspector_endpoint: "http://127.0.0.1:9229", - target_id: "target", - observation_ms: 0, - }); + const { result, failure, reason } = await observeCancelled(partial); expectKeptPartial(expectOwnerError(result, failure, reason), partial); }); it.each([{ scripts: [undefined] }, { elapsed_ms: Number.NaN }])( "drops a non-JSON runtime partial and keeps cancellation primary", async (partial) => { - const reason = { requested: "stop before runtime observation completed" }; - const failure = new AnalysisCancelledError("observe_javascript_runtime", { - cause: reason, - partialObservation: partial as never, - }); - const provider: JavaScriptRuntimeObservationPort = { - identity: () => ({ id: "test", name: "Test", version: "1" }), - listTargets: async () => - err(new AnalysisCancelledError("list_javascript_runtime_targets")), - observe: async () => err(failure), - }; - const result = await observeJavaScriptRuntime(provider, { - inspector_endpoint: "http://127.0.0.1:9229", - target_id: "target", - observation_ms: 0, - }); + const { result, failure, reason } = await observeCancelled(partial); expectDroppedPartial( expectOwnerError(result, failure, reason), "Inspector retained a non-JSON incomplete observation.", diff --git a/src/application/ownerObservationExpectation.fixture.ts b/src/application/ownerObservationExpectation.fixture.ts index f71a73e49c..609edb0f48 100644 --- a/src/application/ownerObservationExpectation.fixture.ts +++ b/src/application/ownerObservationExpectation.fixture.ts @@ -1,5 +1,9 @@ import { expect } from "vitest"; import type { AnalysisError } from "../domain/analysisErrorBase.js"; +import { + AnalysisCancelledError, + AnalysisOutputError, +} from "../domain/analysisErrorCore.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import type { Result } from "../domain/result.js"; @@ -30,6 +34,30 @@ export const expectKeptPartial = ( }); }; +/** A completed value failed output validation, including after a late abort. */ +export const expectOutputError = ( + result: Result, +): void => { + expect(result).toMatchObject({ + ok: false, + error: { _tag: "AnalysisOutputError" }, + }); + if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisOutputError); +}; + +/** Cancellation won before the provider ran. */ +export const expectPreemptedCancellation = ( + result: Result, + cause: unknown, + providerCalls: number, +): void => { + expect(result.ok).toBe(false); + if (result.ok) throw new Error("Expected cancellation"); + expect(result.error).toBeInstanceOf(AnalysisCancelledError); + expect(result.error.cause).toBe(cause); + expect(providerCalls).toBe(0); +}; + /** A non-JSON partial is cleared and recorded as a secondary output failure. */ export const expectDroppedPartial = ( error: AnalysisError, diff --git a/src/domain/javascript/electronPageInspection.fixture.ts b/src/domain/javascript/electronPageInspection.fixture.ts new file mode 100644 index 0000000000..e5fe9d0dcb --- /dev/null +++ b/src/domain/javascript/electronPageInspection.fixture.ts @@ -0,0 +1,34 @@ +import { classifyBrowserCompleteness } from "../browserCompleteness.js"; + +/** Empty coverage tail shared by Electron page fixtures. */ +export const emptyElectronPageCoverage = (observationMs = 0) => ({ + capture_window: { + started_at: "2026-01-01T00:00:00.000Z", + ended_at: new Date( + Date.parse("2026-01-01T00:00:00.000Z") + observationMs, + ).toISOString(), + observation_ms: observationMs, + }, + completeness: classifyBrowserCompleteness({ + policyFilteredSections: new Set(), + attachLimitedSections: new Set(), + truncatedSections: new Set(), + unavailableSections: new Set(), + excluded: [], + droppedEvents: { + scripts: 0, + network_requests: 0, + console_events: 0, + websocket_connections: 0, + websocket_frames: 0, + webmcp_tools: 0, + timeline_events: 0, + }, + }), + frames: [], + dom: { total_nodes: 0, nodes: [] }, + scripts: { total: 0, items: [] }, + resources: [], + workers: [], + limitations: [], +}); diff --git a/tests/boundary/mcp/electronMcp.test.ts b/tests/boundary/mcp/electronMcp.test.ts index 9842407fa0..1d6a8fb318 100644 --- a/tests/boundary/mcp/electronMcp.test.ts +++ b/tests/boundary/mcp/electronMcp.test.ts @@ -29,7 +29,7 @@ import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js"; import { toolAvailabilityInventory } from "../../../src/contracts/toolOutputSchemaPrimitives.js"; import { AnalysisCancelledError } from "../../../src/domain/analysisErrorCore.js"; -import { classifyBrowserCompleteness } from "../../../src/domain/browserCompleteness.js"; +import { emptyElectronPageCoverage } from "../../../src/domain/javascript/electronPageInspection.fixture.js"; import type { ElectronPageInspection } from "../../../src/domain/javascript/electronObservation.js"; import { err } from "../../../src/domain/result.js"; @@ -115,33 +115,7 @@ it("records completed Electron facts after final detach as server Evidence", asy file_path: "/tmp/index.html", attached: true, }, - capture_window: { - started_at: "2026-01-01T00:00:00.000Z", - ended_at: "2026-01-01T00:00:00.001Z", - observation_ms: 1, - }, - completeness: classifyBrowserCompleteness({ - policyFilteredSections: new Set(), - attachLimitedSections: new Set(), - truncatedSections: new Set(), - unavailableSections: new Set(), - excluded: [], - droppedEvents: { - scripts: 0, - network_requests: 0, - console_events: 0, - websocket_connections: 0, - websocket_frames: 0, - webmcp_tools: 0, - timeline_events: 0, - }, - }), - frames: [], - dom: { total_nodes: 0, nodes: [] }, - scripts: { total: 0, items: [] }, - resources: [], - workers: [], - limitations: [], + ...emptyElectronPageCoverage(1), }; const provider: ElectronObservationPort = { identity: () => ({