test(observation): share incomplete-observation assertions

The new non-JSON cases repeated the same owner-error checks and pushed
duplication over the 3.5% limit. One expectation helper keeps those
checks without a second copy.

Co-authored-by: morluto <morluto@users.noreply.github.com>
This commit is contained in:
Cursor Agentandmorluto committed 2026-10-20 18:55:18 +00:00
1 parent b7ec2f6d6e
commit 00785c0401
6 files changed
+153 -211

No files matched your search

@@ -8,6 +8,11 @@ import {
AnalysisOutputError,
} from "../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import {
expectDroppedPartial,
expectKeptPartial,
expectOwnerError,
} from "./ownerObservationExpectation.js";
import { BrowserObservationError } from "../domain/browserObservationError.js";
import { err, ok } from "../domain/result.js";
import { passiveNetworkCapture } from "../../tests/fixtures/passiveNetworkCapture.js";
@@ -243,16 +248,7 @@ it("keeps page cancellation when the retained inspection is incomplete", async (
include_script_sources: false,
});
const result = await inspectWebPage(browser, pageInput);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBe(partial);
expect(result.error.secondaryFailures).toHaveLength(0);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
details: { partial_observation: partial },
});
expectKeptPartial(expectOwnerError(result, failure, reason), partial);
});
it("keeps bundle cancellation and an earlier failure when retained facts are incomplete", async () => {
@@ -278,16 +274,13 @@ it("keeps bundle cancellation and an earlier failure when retained facts are inc
analyzeBundle: async () => err(failure),
} as unknown as BrowserObservationPort;
const result = await analyzeWebBundle(browser, selected);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected the owner failure");
expect(result.error).toBe(failure);
expect(result.error.partialObservation).toBe(partial);
expect(result.error.secondaryFailures).toHaveLength(0);
expectKeptPartial(
expectOwnerError(result, failure, failure.cause),
partial,
failure === timedOut ? "provider_timeout" : "cancelled",
);
}
expect(projectAnalysisError(cancelled)).toMatchObject({
code: "cancelled",
details: { partial_observation: partial },
});
expect(projectAnalysisError(cancelled).code).toBe("cancelled");
});
const pageInput = () =>
@@ -347,22 +340,12 @@ it.each([
operation === "inspect_web_page"
? await inspectWebPage(browserReturning(failure), pageInput())
: await analyzeWebBundle(browserReturning(failure), bundleInput());
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBeUndefined();
expect(result.error.secondaryFailures).toMatchObject([
{
_tag: "AnalysisOutputError",
operation,
reason: "Browser provider retained a non-JSON incomplete observation.",
},
]);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
});
expect(projectAnalysisError(result.error).details).not.toHaveProperty(
const error = expectOwnerError(result, failure, reason);
expectDroppedPartial(
error,
"Browser provider retained a non-JSON incomplete observation.",
);
expect(projectAnalysisError(error).details).not.toHaveProperty(
"partial_observation",
);
},
@@ -387,17 +370,16 @@ it.each([
const failure = new BrowserObservationError("inspect_web_page", "timeout", {
partialObservation: partial as never,
});
const result = await inspectWebPage(browserReturning(failure), pageInput());
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected timeout");
expect(result.error).toBe(failure);
expect(result.error.partialObservation).toBeUndefined();
expect(result.error.secondaryFailures[0]).toBeInstanceOf(
AnalysisOutputError,
const error = expectOwnerError(
await inspectWebPage(browserReturning(failure), pageInput()),
failure,
undefined,
);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "provider_timeout",
category: "timeout",
});
expectDroppedPartial(
error,
"Browser provider retained a non-JSON incomplete observation.",
"provider_timeout",
);
expect(projectAnalysisError(error).category).toBe("timeout");
},
);
@@ -4,7 +4,6 @@ import {
AnalysisCancelledError,
AnalysisOutputError,
} from "../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { BrowserObservationError } from "../domain/browserObservationError.js";
import { parseEvidence } from "../domain/evidence.js";
import { browserScenarioInputSchema } from "../domain/browserScenario.js";
@@ -16,6 +15,11 @@ import { err, ok } from "../domain/result.js";
import { scriptScenarioFixture } from "../../tests/fixtures/webScriptCapture.js";
import type { BrowserScenarioCapturePort } from "./BrowserScenarioCapturePort.js";
import { captureBrowserScenario } from "./BrowserScenarioCaptureService.js";
import {
expectDroppedPartial,
expectKeptPartial,
expectOwnerError,
} from "./ownerObservationExpectation.js";
const scenario = browserScenarioInputSchema.parse({
browser: { mode: "launch", executable_path: "/fixture/chrome" },
@@ -138,16 +142,7 @@ it("keeps startup cancellation and its incomplete capture", async () => {
captureScenario: async () => err(failure),
};
const result = await captureBrowserScenario(provider, scenario);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBe(partial);
expect(result.error.secondaryFailures).toHaveLength(0);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
details: { partial_observation: partial },
});
expectKeptPartial(expectOwnerError(result, failure, reason), partial);
});
it.each([{ steps: [undefined] }, { started_at: Number.NaN }])(
@@ -167,22 +162,10 @@ it.each([{ steps: [undefined] }, { started_at: Number.NaN }])(
captureScenario: async () => err(failure),
};
const result = await captureBrowserScenario(provider, scenario);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBeUndefined();
expect(result.error.secondaryFailures).toMatchObject([
{
_tag: "AnalysisOutputError",
operation: "capture_browser_scenario",
reason:
"Browser scenario provider retained a non-JSON incomplete observation.",
},
]);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
});
expectDroppedPartial(
expectOwnerError(result, failure, reason),
"Browser scenario provider retained a non-JSON incomplete observation.",
);
},
);
+51 -86
View File
@@ -13,6 +13,12 @@ import {
} from "../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import {
expectDroppedPartial,
expectKeptPartial,
expectOwnerError,
} from "./ownerObservationExpectation.js";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import { parseEvidence } from "../domain/evidence.js";
it("rejects malformed input and prior cancellation without calling a provider", async () => {
@@ -166,6 +172,32 @@ it("retains completed execution Evidence on late abort and keeps invalid output
if (!invalid.ok) expect(invalid.error).toBeInstanceOf(AnalysisOutputError);
});
const callRuntime = (
operation: "observe" | "inspect",
failure: AnalysisError,
) => {
const service = new WebRuntimeService({
identity: () => ({
id: "web-runtime",
name: "Runtime fixture",
version: "1",
}),
observeExecution: async () => err(failure),
inspectEventListeners: async () => err(failure),
});
return operation === "observe"
? service.observe({
cdp_endpoint: "http://127.0.0.1:9222",
target_id: "selected-page",
observation_ms: 5,
})
: service.inspect({
cdp_endpoint: "http://127.0.0.1:9222",
target_id: "selected-page",
selector: "#selected",
});
};
it.each([
{
operation: "observe" as const,
@@ -191,37 +223,10 @@ it.each([
: "inspect_web_event_listeners",
{ cause: reason, partialObservation: partial as never },
);
const service = new WebRuntimeService({
identity: () => ({
id: "web-runtime",
name: "Runtime fixture",
version: "1",
}),
observeExecution: async () => err(failure),
inspectEventListeners: async () => err(failure),
});
const result =
operation === "observe"
? await service.observe({
cdp_endpoint: "http://127.0.0.1:9222",
target_id: "selected-page",
observation_ms: 5,
})
: await service.inspect({
cdp_endpoint: "http://127.0.0.1:9222",
target_id: "selected-page",
selector: "#selected",
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBe(partial);
expect(result.error.secondaryFailures).toHaveLength(0);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
details: { partial_observation: partial },
});
expectKeptPartial(
expectOwnerError(await callRuntime(operation, failure), failure, reason),
partial,
);
},
);
@@ -253,38 +258,10 @@ it.each([
: "inspect_web_event_listeners",
{ cause: reason, partialObservation: partial as never },
);
const service = new WebRuntimeService({
identity: () => ({
id: "web-runtime",
name: "Runtime fixture",
version: "1",
}),
observeExecution: async () => err(failure),
inspectEventListeners: async () => err(failure),
});
const result =
operation === "observe"
? await service.observe({
cdp_endpoint: "http://127.0.0.1:9222",
target_id: "selected-page",
observation_ms: 5,
})
: await service.inspect({
cdp_endpoint: "http://127.0.0.1:9222",
target_id: "selected-page",
selector: "#selected",
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBeUndefined();
expect(result.error.secondaryFailures).toMatchObject([
{ _tag: "AnalysisOutputError", reason: outputReason },
]);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
});
expectDroppedPartial(
expectOwnerError(await callRuntime(operation, failure), failure, reason),
outputReason,
);
},
);
@@ -295,27 +272,15 @@ it("drops a non-JSON execution partial and keeps timeout primary", async () => {
window: { requested_ms: 5 },
} as never,
});
const service = new WebRuntimeService({
identity: () => ({
id: "web-runtime",
name: "Runtime fixture",
version: "1",
}),
observeExecution: async () => err(failure),
inspectEventListeners: async () => err(failure),
});
const result = await service.observe({
cdp_endpoint: "http://127.0.0.1:9222",
target_id: "selected-page",
observation_ms: 5,
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected timeout");
expect(result.error).toBe(failure);
expect(result.error.partialObservation).toBeUndefined();
expect(result.error.secondaryFailures[0]).toBeInstanceOf(AnalysisOutputError);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "provider_timeout",
category: "timeout",
});
const error = expectOwnerError(
await callRuntime("observe", failure),
failure,
undefined,
);
expectDroppedPartial(
error,
"Provider retained a non-JSON incomplete execution observation.",
"provider_timeout",
);
expect(projectAnalysisError(error).category).toBe("timeout");
});
@@ -5,7 +5,6 @@ import {
AnalysisCancelledError,
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
import { parseEvidence } from "../../domain/evidence.js";
import {
@@ -19,6 +18,11 @@ import {
inspectElectronPage,
listElectronTargets,
} from "./ElectronObservationService.js";
import {
expectDroppedPartial,
expectKeptPartial,
expectOwnerError,
} from "../ownerObservationExpectation.js";
const identity = { id: "electron-fixture", name: "Fixture", version: "1" };
const input = inspectElectronPageInputSchema.parse({
@@ -264,16 +268,7 @@ it("keeps cancellation when the retained Electron inspection is incomplete", asy
inspectPage: async () => err(failure),
};
const result = await inspectElectronPage(provider, input);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBe(partial);
expect(result.error.secondaryFailures).toHaveLength(0);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
details: { partial_observation: partial },
});
expectKeptPartial(expectOwnerError(result, failure, reason), partial);
});
it.each([
@@ -300,20 +295,9 @@ it.each([
inspectPage: async () => err(failure),
};
const result = await inspectElectronPage(provider, input);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBeUndefined();
expect(result.error.secondaryFailures).toMatchObject([
{
_tag: "AnalysisOutputError",
operation: "inspect_electron_page",
reason: "Electron provider retained a non-JSON incomplete observation.",
},
]);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
});
expectDroppedPartial(
expectOwnerError(result, failure, reason),
"Electron provider retained a non-JSON incomplete observation.",
);
},
);
@@ -5,7 +5,6 @@ import {
AnalysisCancelledError,
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { err, ok } from "../../domain/result.js";
import { parseEvidence } from "../../domain/evidence.js";
import type { JavaScriptRuntimeTargetList } from "../../domain/javascript/javascriptRuntimeObservation.js";
@@ -15,6 +14,11 @@ import {
listJavaScriptRuntimeTargets,
observeJavaScriptRuntime,
} from "./JavaScriptRuntimeObservationService.js";
import {
expectDroppedPartial,
expectKeptPartial,
expectOwnerError,
} from "../ownerObservationExpectation.js";
it("preserves failed Inspector cleanup when cancellation occurs during observation", async () => {
const controller = new AbortController();
@@ -337,16 +341,7 @@ it("keeps cancellation when the retained runtime observation is incomplete", asy
target_id: "target",
observation_ms: 0,
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBe(partial);
expect(result.error.secondaryFailures).toHaveLength(0);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
details: { partial_observation: partial },
});
expectKeptPartial(expectOwnerError(result, failure, reason), partial);
});
it.each([{ scripts: [undefined] }, { elapsed_ms: Number.NaN }])(
@@ -368,20 +363,9 @@ it.each([{ scripts: [undefined] }, { elapsed_ms: Number.NaN }])(
target_id: "target",
observation_ms: 0,
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBeUndefined();
expect(result.error.secondaryFailures).toMatchObject([
{
_tag: "AnalysisOutputError",
operation: "observe_javascript_runtime",
reason: "Inspector retained a non-JSON incomplete observation.",
},
]);
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cancelled",
});
expectDroppedPartial(
expectOwnerError(result, failure, reason),
"Inspector retained a non-JSON incomplete observation.",
);
},
);
@@ -0,0 +1,44 @@
import { expect } from "vitest";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import type { Result } from "../domain/result.js";
/** Assert the service returned the same owner error and its cause. */
export const expectOwnerError = <T>(
result: Result<T, AnalysisError>,
failure: AnalysisError,
cause: unknown,
): AnalysisError => {
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected the owner failure");
expect(result.error).toBe(failure);
expect(result.error.cause).toBe(cause);
return result.error;
};
/** A JSON-safe incomplete observation stays on the owner error. */
export const expectKeptPartial = (
error: AnalysisError,
partial: unknown,
code: "cancelled" | "provider_timeout" = "cancelled",
): void => {
expect(error.partialObservation).toBe(partial);
expect(error.secondaryFailures).toHaveLength(0);
expect(projectAnalysisError(error)).toMatchObject({
code,
details: { partial_observation: partial },
});
};
/** A non-JSON partial is cleared and recorded as a secondary output failure. */
export const expectDroppedPartial = (
error: AnalysisError,
reason: string,
code: "cancelled" | "provider_timeout" = "cancelled",
): void => {
expect(error.partialObservation).toBeUndefined();
expect(error.secondaryFailures).toMatchObject([
{ _tag: "AnalysisOutputError", reason },
]);
expect(projectAnalysisError(error).code).toBe(code);
};