mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 08:17:02 +00:00
Inspect selected fields in existing captures through shared CLI/MCP Evidence. Page matching frames without changing dissection or two-pass read coverage; retain failed descriptor/process cleanup ownership and reject excess admission. Verified with real TShark 4.0.17 and 4.2.2, public Wireshark recordings, owned HTTP application traffic, real CLI/MCP workflows and schema checks. Co-authored-by: Olorinm <59321404+Olorinm@users.noreply.github.com> Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
108 lines
3.9 KiB
TypeScript
108 lines
3.9 KiB
TypeScript
import { expect, it, onTestFinished, vi } from "vitest";
|
|
import { writeFile } from "node:fs/promises";
|
|
import type { FileHandle } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { TsharkCaptureProvider } from "../../../src/captures/TsharkCaptureProvider.js";
|
|
import { PacketCaptureService } from "../../../src/application/captures/PacketCaptureService.js";
|
|
import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js";
|
|
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const cleanup = vi.hoisted(() => ({
|
|
path: "",
|
|
denied: false,
|
|
handles: [] as FileHandle[],
|
|
}));
|
|
vi.mock("node:fs/promises", async (importOriginal) => {
|
|
const filesystem = await importOriginal<typeof import("node:fs/promises")>();
|
|
return {
|
|
...filesystem,
|
|
open: async (...args: Parameters<typeof filesystem.open>) => {
|
|
const handle = await filesystem.open(...args);
|
|
if (args[0] === cleanup.path) {
|
|
cleanup.handles.push(handle);
|
|
const close = handle.close.bind(handle);
|
|
handle.close = async () => {
|
|
if (cleanup.denied && handle.fd >= 0)
|
|
throw new Error("Descriptor close deliberately denied");
|
|
await close();
|
|
};
|
|
}
|
|
return handle;
|
|
},
|
|
};
|
|
});
|
|
|
|
it
|
|
.runIf(process.platform === "linux")
|
|
.each(["capture", "tool", "limiter", "kernel"])(
|
|
"retains failed %s descriptors and retries them before further acquisition and shutdown",
|
|
async (member) => {
|
|
const directory = await createTestTempDirectory("rea-capture-descriptor-");
|
|
const path = join(directory, "recording.pcap");
|
|
const command = join(directory, "tshark.mjs");
|
|
const limiter = join(directory, "limiter");
|
|
await writeFile(path, "recording bytes");
|
|
await writeFile(limiter, "limiter", { mode: 0o700 });
|
|
await writeFile(
|
|
command,
|
|
`if(process.argv.includes('--version'))console.log('TShark (Wireshark) 4.0.17');else console.log('[{"_source":{"layers":{"frame.number":["1"]}}}]');`,
|
|
{ mode: 0o700 },
|
|
);
|
|
let launches = 0;
|
|
const provider = new TsharkCaptureProvider(
|
|
{
|
|
...process.env,
|
|
REA_TSHARK_COMMAND: command,
|
|
REA_TSHARK_PRLIMIT_COMMAND: limiter,
|
|
},
|
|
(input) => {
|
|
launches += 1;
|
|
return spawnOwnedProviderProcess({
|
|
...input,
|
|
command: process.execPath,
|
|
arguments: [command, ...input.arguments],
|
|
expectedCommand: process.execPath,
|
|
});
|
|
},
|
|
() => PrivateRuntimeRoot.create({ parent: directory }),
|
|
);
|
|
cleanup.path =
|
|
member === "capture"
|
|
? path
|
|
: member === "tool"
|
|
? command
|
|
: member === "limiter"
|
|
? limiter
|
|
: "/proc/self/limits";
|
|
cleanup.denied = true;
|
|
cleanup.handles = [];
|
|
onTestFinished(async () => {
|
|
cleanup.denied = false;
|
|
await provider.close();
|
|
for (const handle of cleanup.handles) await handle.close();
|
|
cleanup.path = "";
|
|
});
|
|
const service = new PacketCaptureService(provider);
|
|
const first = await service.inspect({ path, fields: ["frame.number"] });
|
|
expect(first).toMatchObject({
|
|
ok: false,
|
|
error: { cleanupIncomplete: true, cleanupResources: [cleanup.path] },
|
|
});
|
|
expect(cleanup.handles).toHaveLength(1);
|
|
expect(cleanup.handles[0]?.fd).toBeGreaterThanOrEqual(0);
|
|
const launchCount = launches;
|
|
expect(
|
|
await service.inspect({ path, fields: ["frame.number"] }),
|
|
).toMatchObject({ ok: false, error: { cleanupIncomplete: true } });
|
|
expect(cleanup.handles).toHaveLength(1);
|
|
expect(launches).toBe(launchCount);
|
|
await expect(provider.close()).rejects.toMatchObject({
|
|
cleanupIncomplete: true,
|
|
});
|
|
cleanup.denied = false;
|
|
await provider.close();
|
|
expect(cleanup.handles[0]?.fd).toBe(-1);
|
|
},
|
|
);
|