Files

114 lines
3.9 KiB
TypeScript

import { expect, it } from "vitest";
import { createCli } from "../../src/cli.js";
import { FridaInstrumentationManager } from "../../src/frida/FridaInstrumentationManager.js";
import { ProviderCleanupError } from "../../src/domain/providerCleanupError.js";
import { ok } from "../../src/domain/result.js";
import { toolContract } from "../../src/contracts/toolContracts.js";
import { jsonObjectSchema } from "../../src/domain/jsonValue.js";
it.each([
["list-frida-devices", "SIGINT", false],
["list-frida-processes", "SIGTERM", true],
["instrument-with-frida", "SIGINT", true],
] as const)(
"retains completed %s output when %s arrives during final close (failure=%s)",
async (command, interrupt, cleanupFails) => {
const initialListeners = new Set(process.listeners(interrupt));
const originalExitCode = process.exitCode;
const provider = new FridaInstrumentationManager();
provider.listDevices = async () => ok({ devices: [], cleanupError: null });
provider.listProcesses = async () =>
ok({
deviceId: "local",
processes: [{ pid: 71, name: "observed", identifier: null }],
cleanupError: null,
});
provider.startSession = async () =>
ok({
sessionId: "00000000-0000-4000-8000-000000000001",
deviceId: "local",
target: "observed",
pid: 71,
mode: "attach",
state: "running",
});
provider.loadScript = async () =>
ok({
scriptId: "00000000-0000-4000-8000-000000000002",
sourceKind: "inline",
sourcePath: null,
sourceSha256: "a".repeat(64),
messages: [{ type: "send", payload: "completed" }],
messagesTruncated: false,
});
provider.closeSession = async () => ok({ targetResumeObserved: false });
const cleanup = new ProviderCleanupError("frida", ["retained connection"], {
reason: "final close denied",
});
let closes = 0;
provider.closeAll = async () => {
closes += 1;
const listeners = process
.listeners(interrupt)
.filter((listener) => !initialListeners.has(listener));
expect(listeners).toHaveLength(1);
listeners[0]?.call(process, interrupt);
if (cleanupFails) throw cleanup;
};
const args =
command === "instrument-with-frida"
? [
command,
"attach",
"71",
"--script",
"send('completed')",
"--duration-ms",
"0",
"--json",
]
: [command, "--json"];
let stdout = "";
try {
await createCli({}, undefined, provider).serve(args, {
env: {},
exit: () => {},
stdout: (text) => {
stdout += text;
},
});
const output: unknown = JSON.parse(stdout);
expect(output).toMatchObject({
code: cleanupFails ? "cleanup_incomplete" : "cancelled",
details: cleanupFails
? {
resources: ["retained connection"],
diagnostics: { primary_error: { code: "cancelled" } },
}
: {},
});
const observation = jsonObjectSchema.parse(
jsonObjectSchema.parse(output).details,
).partial_observation;
const name =
command === "list-frida-devices"
? "list_frida_devices"
: command === "list-frida-processes"
? "list_frida_processes"
: "instrument_with_frida";
expect(() =>
toolContract(name).outputSchema.parse(observation),
).not.toThrow();
if (command === "instrument-with-frida")
expect(observation).toMatchObject({
normalized_result: { messages: [{ payload: "completed" }] },
});
expect(closes).toBe(1);
expect(process.exitCode).toBe(interrupt === "SIGINT" ? 130 : 143);
expect(process.listeners(interrupt)).toEqual([...initialListeners]);
} finally {
process.exitCode = originalExitCode;
}
},
);