Retain process input and unexpected failure diagnostics through the shared error projector. Preserve filesystem causes and firmware validation issues, and mark unchanged invalid requests nonretryable. Use typed stat settlement and parse unknown command failures without losing null or undefined rejection causes. Refs #5397, #5401, #5402, #5642
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import { expect, it, onTestFinished } from "vitest";
|
|
import { AnalysisCleanupLease } from "../../../src/artifacts/AnalysisCleanupLease.js";
|
|
import { AnalysisInputError } from "../../../src/domain/analysisErrorCore.js";
|
|
import { analysisErrorWithCleanupFailure } from "../../../src/domain/analysisErrorCleanup.js";
|
|
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
|
|
import { err } from "../../../src/domain/result.js";
|
|
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
|
import { advertisedMcpToolSchemas } from "../../fixtures/advertisedMcpTool.js";
|
|
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
|
|
|
it("restores the primary error after exact cleanup retry through the same MCP connection", async () => {
|
|
const primary = new AnalysisInputError("inspect_adb_window", undefined, [
|
|
{
|
|
path: ["serial"],
|
|
reason: "invalid_value",
|
|
message: "fixture input refused",
|
|
},
|
|
]);
|
|
const cleanup = new ProviderCleanupError(
|
|
"fixture",
|
|
["selected-input"],
|
|
{ reason: "close denied" },
|
|
{ operation: "inspect_adb_window" },
|
|
);
|
|
let permitRelease = false;
|
|
const lease = new AnalysisCleanupLease(async () => {
|
|
if (!permitRelease) throw new Error("close denied");
|
|
}, ["selected-input"]);
|
|
lease.bindCleanupError(cleanup);
|
|
const composed = analysisErrorWithCleanupFailure(
|
|
primary,
|
|
cleanup,
|
|
"inspect_adb_window",
|
|
).bindCleanupDependencies([primary, cleanup]);
|
|
const harness = await createApplicationMcpHarness({
|
|
serverOptions: {
|
|
adbDeviceAnalysis: {
|
|
inspectAvailability: async () => ({
|
|
status: "available",
|
|
code: null,
|
|
reason: null,
|
|
diagnostics: {},
|
|
}),
|
|
execute: async () => err(composed),
|
|
close: async () => {
|
|
permitRelease = true;
|
|
await lease.release();
|
|
},
|
|
},
|
|
},
|
|
});
|
|
onTestFinished(() => harness.close());
|
|
const { name, ajv, inputSchema } = await advertisedMcpToolSchemas(
|
|
harness.client,
|
|
"inspect_adb_window",
|
|
);
|
|
const args = { serial: "fixture" };
|
|
expect(ajv.validate(inputSchema, args)).toBe(true);
|
|
const call = async () =>
|
|
parseMcpToolError(await harness.client.callTool({ name, arguments: args }))
|
|
.error;
|
|
expect((await call()).code).toBe("cleanup_incomplete");
|
|
await expect(lease.release()).rejects.toThrow("close denied");
|
|
expect((await call()).code).toBe("cleanup_incomplete");
|
|
permitRelease = true;
|
|
await lease.release();
|
|
expect(await call()).toMatchObject({
|
|
code: "invalid_request",
|
|
category: "invalid_input",
|
|
retryable: false,
|
|
details: {
|
|
issues: [{ path: ["serial"], reason: "invalid_value" }],
|
|
cleanup_history: {
|
|
primary_error: { code: "invalid_request" },
|
|
cleanup_error: { code: "cleanup_incomplete" },
|
|
},
|
|
},
|
|
});
|
|
await harness.client.ping();
|
|
});
|