Files
reverse-engineering-anything/tests/boundary/filesystem/binaryTargetCleanup.test.ts
T

257 lines
10 KiB
TypeScript

import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
import type { AnalysisOperation } from "../../../src/application/AnalysisProvider.js";
import { RetainedRegularFileCleanupFailure } from "../../../src/application/RegularFileRead.js";
import { ArtifactResourceScope } from "../../../src/artifacts/ArtifactResourceScope.js";
import { OwnedFileHandle } from "../../../src/filesystem/OwnedFileHandle.js";
import type { AnalysisError } from "../../../src/domain/analysisErrorBase.js";
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
import { ok, err } from "../../../src/domain/result.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
import {
createCacheProvider,
createTestBinarySession,
} from "../../fixtures/binarySession.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { observed } from "../../fixtures/analysisExecution.js";
it.each(
[true, false].flatMap((supported) =>
[true, false].map((cancel) => ({ supported, cancel })),
),
)(
"retains target admission and one descriptor through failed close (supported=$supported, cancelled=$cancel)",
async ({ supported, cancel }) => {
const directory = await createTestTempDirectory("rea-target-close-");
const path = join(directory, supported ? "target.hop" : "target.bin");
const bytes = Buffer.from("target source bytes");
await writeFile(path, bytes);
const resources = new ArtifactResourceScope();
const controller = new AbortController();
const reason = { caller: "cancel during target close" };
const nativeClose = OwnedFileHandle.prototype.close;
let denied = true;
const owners: OwnedFileHandle[] = [];
const close = vi
.spyOn(OwnedFileHandle.prototype, "close")
.mockImplementation(async function (this: OwnedFileHandle) {
owners.push(this);
if (cancel) controller.abort(reason);
if (denied) throw new Error("controlled target close denial");
await nativeClose.call(this);
});
try {
const result = await parseBinaryTarget(path, {
resources,
signal: controller.signal,
});
if (result.ok) throw new Error("Expected retained target cleanup");
expect(result.error.cleanupIncomplete).toBe(true);
expect(result.error.cause).toBeInstanceOf(
RetainedRegularFileCleanupFailure,
);
const failure = result.error.cause as RetainedRegularFileCleanupFailure;
expect(failure.owner).toBe(owners[0]);
if (supported) {
expect(result.error.diagnostics).toMatchObject({
completed_target: {
path,
format: "analysis-database",
sha256: createHash("sha256").update(bytes).digest("hex"),
},
});
if (cancel) {
expect(result.error.cleanupPrimary).toMatchObject({
_tag: "AnalysisCancelledError",
});
expect(result.error.cleanupPrimary?.cause).toBe(reason);
}
} else {
expect(result.error.cleanupPrimary).toMatchObject({
_tag: "BinaryTargetError",
constraint: "file_format_rejected",
});
}
const blocked = await parseBinaryTarget(path, { resources });
if (blocked.ok)
throw new Error(
"Expected unresolved cleanup to block another admission",
);
expect(owners).toHaveLength(3);
expect(owners.every((owner) => owner === failure.owner)).toBe(true);
denied = false;
await resources.retryCleanup();
expect(result.error.cleanupIncomplete).toBe(false);
expect(blocked.error.cleanupIncomplete).toBe(false);
expect(failure.owner.handle.fd).toBe(-1);
if (supported && cancel)
expect(result.error.cleanupPrimary?.diagnostics).toMatchObject({
completed_target: { path },
});
expect((await parseBinaryTarget(path, { resources })).ok).toBe(supported);
} finally {
denied = false;
await resources.close();
close.mockRestore();
}
},
);
it.each([true, false])(
"keeps target completion or refusal when cancellation arrives during confirmed close (supported=%s)",
async (supported) => {
const directory = await createTestTempDirectory("rea-target-final-cancel-");
const path = join(directory, supported ? "target.hop" : "target.bin");
await writeFile(path, "target source bytes");
const controller = new AbortController();
const reason = "caller stopped after reading";
const nativeClose = OwnedFileHandle.prototype.close;
const close = vi
.spyOn(OwnedFileHandle.prototype, "close")
.mockImplementation(async function (this: OwnedFileHandle) {
await nativeClose.call(this);
controller.abort(reason);
});
try {
const result = await parseBinaryTarget(path, {
signal: controller.signal,
});
if (result.ok) throw new Error("Expected refusal or cancellation");
expect(result.error.cleanupIncomplete).toBe(false);
if (supported) {
expect(result.error).toMatchObject({
_tag: "AnalysisCancelledError",
cause: reason,
diagnostics: { completed_target: { path } },
});
} else {
expect(result.error).toMatchObject({
_tag: "BinaryTargetError",
constraint: "file_format_rejected",
});
}
} finally {
close.mockRestore();
}
},
);
it("retries target and provider owners independently through the same MCP connection", async () => {
const directory = await createTestTempDirectory("rea-target-mcp-close-");
const first = join(directory, "first.hop");
const second = join(directory, "second.hop");
await writeFile(first, "first database bytes");
await writeFile(second, "second database bytes");
const provider = createCacheProvider([]);
const providerFailure = new ProviderCleanupError(
"fixture",
["provider-owned-resource"],
{ reason: "controlled provider close denial" },
);
let providerAllowed = false;
const execute = vi.fn(async (operation: AnalysisOperation) => {
if (operation === "health") return observed(null);
if (operation === "address_name") return observed("fixture-name");
throw new Error("Unexpected fixture operation");
});
provider.createClient = vi.fn(() => ({
execute,
close: async () => {
if (!providerAllowed) return err(providerFailure);
providerFailure.resolveCleanup();
return ok(null);
},
}));
const session = createTestBinarySession(provider);
const opened = await session.open(first);
if (!opened.ok) throw opened.error;
const harness = await createApplicationMcpHarness({ session });
const closeErrors: AnalysisError[] = [];
const sessionClose = session.close.bind(session);
vi.spyOn(session, "close").mockImplementation(async (...args) => {
const result = await sessionClose(...args);
if (!result.ok) closeErrors.push(result.error);
return result;
});
const nativeClose = OwnedFileHandle.prototype.close;
let fileAllowed = false;
const owners: OwnedFileHandle[] = [];
const close = vi
.spyOn(OwnedFileHandle.prototype, "close")
.mockImplementation(async function (this: OwnedFileHandle) {
owners.push(this);
if (!fileAllowed) throw new Error("controlled target close denial");
await nativeClose.call(this);
});
try {
// Opening a composed session checks only already-instantiated children.
// Invoke the selected operation to acquire the provider owner before the
// next target admission creates its independently retained descriptor.
const acquired = await harness.client.callTool({
name: "address_name",
arguments: { address: "0x1000", document: "fixture" },
});
expect(acquired.isError).not.toBe(true);
expect(provider.createClient).toHaveBeenCalledTimes(1);
expect(execute).toHaveBeenCalledTimes(1);
expect(execute.mock.calls[0]?.[0]).toBe("address_name");
const refused = await harness.client.callTool({
name: "open_binary",
arguments: { path: second },
});
expect(parseMcpToolError(refused).error).toMatchObject({
code: "cleanup_incomplete",
details: { diagnostics: { completed_target: { path: second } } },
});
expect(provider.createClient).toHaveBeenCalledTimes(1);
expect(execute).toHaveBeenCalledTimes(1);
expect(execute.mock.calls[0]?.[0]).toBe("address_name");
expect(await session.status()).toMatchObject({ cleanup_pending: true });
await harness.client.ping();
const firstClose = await harness.client.callTool({
name: "close_binary",
arguments: {},
});
expect(parseMcpToolError(firstClose).error.code).toBe("cleanup_incomplete");
const aggregate = closeErrors[0];
if (aggregate === undefined)
throw new Error("Expected mixed cleanup aggregate");
expect(aggregate.cleanupResources).toEqual(
expect.arrayContaining([second, "provider-owned-resource"]),
);
fileAllowed = true;
const secondClose = await harness.client.callTool({
name: "close_binary",
arguments: {},
});
expect(parseMcpToolError(secondClose).error.code).toBe(
"cleanup_incomplete",
);
expect(aggregate.cleanupIncomplete).toBe(true);
expect(aggregate.cleanupResources).toEqual(["provider-owned-resource"]);
const completedFileCalls = owners.length;
providerAllowed = true;
const released = await harness.client.callTool({
name: "close_binary",
arguments: {},
});
expect(released.isError).not.toBe(true);
expect(owners).toHaveLength(completedFileCalls);
expect(owners.every((owner) => owner === owners[0])).toBe(true);
expect(owners[0]?.handle.fd).toBe(-1);
expect(aggregate.cleanupIncomplete).toBe(false);
expect(await session.status()).toMatchObject({ cleanup_pending: false });
} finally {
fileAllowed = true;
providerAllowed = true;
close.mockRestore();
await harness.close();
vi.restoreAllMocks();
}
});