mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
114 lines
3.9 KiB
TypeScript
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;
|
|
}
|
|
},
|
|
);
|