fix(test): make host fixtures portable
This commit is contained in:
parent
b2d1f2affd
commit
f23f52903c
|
|
@ -40,16 +40,22 @@ export const isInitializedPtyRoot = (options: {
|
|||
readonly before: ProcessIdentitySnapshot;
|
||||
readonly observedRunId: string | undefined;
|
||||
readonly after: ProcessIdentitySnapshot;
|
||||
}): boolean =>
|
||||
options.before.pid === options.rootPid &&
|
||||
options.before.process_group_id === options.rootPid &&
|
||||
options.before.session_id === options.rootPid &&
|
||||
options.after.pid === options.rootPid &&
|
||||
options.after.parent_pid === options.before.parent_pid &&
|
||||
options.after.process_group_id === options.rootPid &&
|
||||
options.after.session_id === options.rootPid &&
|
||||
options.after.startTime === options.before.startTime &&
|
||||
options.observedRunId === options.expectedRunId;
|
||||
}): boolean => {
|
||||
const sessionIdentityMatches =
|
||||
(options.before.session_id === options.rootPid &&
|
||||
options.after.session_id === options.rootPid) ||
|
||||
(options.before.session_id === null && options.after.session_id === null);
|
||||
return (
|
||||
options.before.pid === options.rootPid &&
|
||||
options.before.process_group_id === options.rootPid &&
|
||||
sessionIdentityMatches &&
|
||||
options.after.pid === options.rootPid &&
|
||||
options.after.parent_pid === options.before.parent_pid &&
|
||||
options.after.process_group_id === options.rootPid &&
|
||||
options.after.startTime === options.before.startTime &&
|
||||
options.observedRunId === options.expectedRunId
|
||||
);
|
||||
};
|
||||
|
||||
const isExpectedProcReadFailure = (cause: unknown): boolean =>
|
||||
cause instanceof Error &&
|
||||
|
|
@ -315,7 +321,7 @@ const readPsRows = async (
|
|||
pid: Number(match[1]),
|
||||
parent_pid: Number(match[2]),
|
||||
process_group_id: Number(match[3]),
|
||||
session_id: Number(match[4]),
|
||||
session_id: Number(match[4]) || null,
|
||||
command: match[5] ?? "",
|
||||
startTime: undefined,
|
||||
}))
|
||||
|
|
@ -346,7 +352,7 @@ const inspectInitializedPtyRoot = async (
|
|||
if (
|
||||
before === undefined ||
|
||||
before.process_group_id !== rootPid ||
|
||||
before.session_id !== rootPid
|
||||
(before.session_id !== rootPid && before.session_id !== null)
|
||||
)
|
||||
return undefined;
|
||||
let observedRunId: string | undefined;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ export interface GhidraJavaObservation {
|
|||
|
||||
/** Narrow synchronous seam used by provider discovery without launching Ghidra. */
|
||||
export interface GhidraInstallationHost {
|
||||
readonly platform?: NodeJS.Platform;
|
||||
readonly architecture?: NodeJS.Architecture;
|
||||
readText(path: string): string | undefined;
|
||||
executable(path: string): boolean;
|
||||
probeJava(
|
||||
|
|
|
|||
|
|
@ -415,6 +415,12 @@ export class GhidraProvider implements AnalysisProviderCandidate {
|
|||
...(this.config.ghidraJavaHome === undefined
|
||||
? {}
|
||||
: { javaHome: this.config.ghidraJavaHome }),
|
||||
...(this.installationHost?.platform === undefined
|
||||
? {}
|
||||
: { platform: this.installationHost.platform }),
|
||||
...(this.installationHost?.architecture === undefined
|
||||
? {}
|
||||
: { architecture: this.installationHost.architecture }),
|
||||
};
|
||||
this.#installation ??=
|
||||
this.installationHost === undefined
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { EnhancedTools } from "../src/application/EnhancedTools.js";
|
|||
import { AnalysisProviderRegistry } from "../src/application/AnalysisProviderRegistry.js";
|
||||
import { SessionProviderRouter } from "../src/application/SessionProviderRouter.js";
|
||||
import { parseConfig } from "../src/config.js";
|
||||
import { parseEvidence } from "../src/domain/evidence.js";
|
||||
import type { JsonValue } from "../src/domain/jsonValue.js";
|
||||
import { ok } from "../src/domain/result.js";
|
||||
import type { GhidraInstallationHost } from "../src/ghidra/GhidraInstallation.js";
|
||||
|
|
@ -67,7 +66,8 @@ describe("Ghidra MCP and shared CLI composition", () => {
|
|||
});
|
||||
expect(opened.isError).not.toBe(true);
|
||||
|
||||
const listed = parseEvidence(
|
||||
const listed = sessionEvidence(
|
||||
session,
|
||||
(
|
||||
await mcp.callTool({
|
||||
name: "list_procedures",
|
||||
|
|
@ -104,7 +104,8 @@ describe("Ghidra MCP and shared CLI composition", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const mcpOverview = parseEvidence(
|
||||
const mcpOverview = sessionEvidence(
|
||||
session,
|
||||
(
|
||||
await mcp.callTool({
|
||||
name: "binary_overview",
|
||||
|
|
@ -131,7 +132,8 @@ describe("Ghidra MCP and shared CLI composition", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const pseudocode = parseEvidence(
|
||||
const pseudocode = sessionEvidence(
|
||||
session,
|
||||
(
|
||||
await mcp.callTool({
|
||||
name: "procedure_pseudo_code",
|
||||
|
|
@ -149,7 +151,8 @@ describe("Ghidra MCP and shared CLI composition", () => {
|
|||
]),
|
||||
});
|
||||
|
||||
const analyzed = parseEvidence(
|
||||
const analyzed = sessionEvidence(
|
||||
session,
|
||||
(
|
||||
await mcp.callTool({
|
||||
name: "analyze_function",
|
||||
|
|
@ -206,7 +209,7 @@ describe("Ghidra MCP and shared CLI composition", () => {
|
|||
});
|
||||
expect(batch.isError).not.toBe(true);
|
||||
expect(batch.structuredContent).toMatchObject({
|
||||
normalized_result: {
|
||||
result: {
|
||||
succeeded: 1,
|
||||
failed: 0,
|
||||
items: [{ status: "ok" }],
|
||||
|
|
@ -269,7 +272,24 @@ describe("Ghidra MCP and shared CLI composition", () => {
|
|||
});
|
||||
});
|
||||
|
||||
const sessionEvidence = (session: BinarySession, value: unknown) => {
|
||||
if (
|
||||
typeof value !== "object" ||
|
||||
value === null ||
|
||||
!("evidence_id" in value) ||
|
||||
typeof value.evidence_id !== "string"
|
||||
)
|
||||
throw new TypeError(
|
||||
`Missing compact Evidence ID: ${JSON.stringify(value)}`,
|
||||
);
|
||||
const evidence = session.evidenceById(value.evidence_id);
|
||||
if (evidence === undefined) throw new TypeError("Missing session Evidence");
|
||||
return evidence;
|
||||
};
|
||||
|
||||
const installationHost = (): GhidraInstallationHost => ({
|
||||
platform: "linux",
|
||||
architecture: "x64",
|
||||
readText: () => "application.version=12.1.2\n",
|
||||
executable: () => true,
|
||||
probeJava: () => ({
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import { silentLogger } from "../src/logger.js";
|
|||
|
||||
const INSTALL = "/opt/ghidra_12.1.2_PUBLIC";
|
||||
const installationHost = (): GhidraInstallationHost => ({
|
||||
platform: "linux",
|
||||
architecture: "x64",
|
||||
readText: () => "application.version=12.1.2\n",
|
||||
executable: () => true,
|
||||
probeJava: () => ({
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ afterEach(async () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe("curl installer scenarios", () => {
|
||||
describe("curl installer scenarios", { timeout: 20_000 }, () => {
|
||||
it("installs only a pinned REA CLI with closed stdin", async () => {
|
||||
const fixture = await createFixture();
|
||||
const result = await runInstaller(fixture, ["--version", "0.3.0"]);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ describe("native DMG artifact reader", () => {
|
|||
const provenance = reader.provenance();
|
||||
if (provenance[0] !== undefined)
|
||||
Reflect.set(provenance[0], "tool", "forged");
|
||||
expect(reader.provenance()[0]?.tool).toBe("hdiutil");
|
||||
expect(reader.provenance()[0]?.tool).toBe("/usr/bin/hdiutil");
|
||||
await reader.close();
|
||||
expect(calls).toContainEqual(["verify", "/tmp/image.dmg"]);
|
||||
expect(calls).toContainEqual(["detach", "/dev/disk-fixture"]);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ describe("native macOS provider", () => {
|
|||
return Promise.resolve(
|
||||
resolutions === 1
|
||||
? err(new NativeCommandFailure(tool, "unavailable"))
|
||||
: ok({ path: "/bin/true", sha256: "a".repeat(64) }),
|
||||
: ok({ path: "/usr/bin/true", sha256: "a".repeat(64) }),
|
||||
);
|
||||
});
|
||||
const options = { timeoutMs: 1_000, maxOutputBytes: 1_024 };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { realpath } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { AnalysisOperationPort } from "../src/application/AnalysisProvider.js";
|
||||
|
|
@ -8,6 +10,7 @@ import { createServer } from "../src/server/createServer.js";
|
|||
|
||||
describe("MCP permission preflight", () => {
|
||||
it("denies extraction before dispatch and returns exact typed remediation", async () => {
|
||||
const outputRoot = join(await realpath("/tmp"), "rea-permission-denied");
|
||||
let dispatches = 0;
|
||||
const analysis: AnalysisOperationPort = {
|
||||
execute: () => {
|
||||
|
|
@ -28,7 +31,7 @@ describe("MCP permission preflight", () => {
|
|||
name: "extract_artifact",
|
||||
arguments: {
|
||||
approved: true,
|
||||
output_root: "/tmp/rea-permission-denied",
|
||||
output_root: outputRoot,
|
||||
occurrence_ids: [`occ_${"0".repeat(64)}`],
|
||||
},
|
||||
});
|
||||
|
|
@ -41,7 +44,7 @@ describe("MCP permission preflight", () => {
|
|||
code: "permission_required",
|
||||
details: {
|
||||
capability: "artifact_extract",
|
||||
missing: { roots: ["/tmp/rea-permission-denied"] },
|
||||
missing: { roots: [outputRoot] },
|
||||
},
|
||||
remediation: {
|
||||
restart_required: false,
|
||||
|
|
|
|||
|
|
@ -249,6 +249,15 @@ describe("process capture domain", () => {
|
|||
after: initialized,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isInitializedPtyRoot({
|
||||
rootPid: 100,
|
||||
expectedRunId: "run-token",
|
||||
before: { ...initialized, session_id: null },
|
||||
observedRunId: "run-token",
|
||||
after: { ...initialized, session_id: null },
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps interaction and shim residual uncertainty in separate scopes", () => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { realpathSync } from "node:fs";
|
||||
import { availableParallelism, tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
|
|
@ -5,6 +6,7 @@ import { defineConfig } from "vitest/config";
|
|||
|
||||
const TEST_FILES = ["tests/**/*.test.ts"];
|
||||
const PROCESS_CAPTURE_TEST = "tests/processCapture.test.ts";
|
||||
const CANONICAL_TEMPORARY_DIRECTORY = realpathSync(tmpdir());
|
||||
const SERIAL_INTEGRATION_TESTS = [
|
||||
"tests/browserCli.test.ts",
|
||||
"tests/cliOutput.test.ts",
|
||||
|
|
@ -37,6 +39,7 @@ const dualCoreProjects =
|
|||
export default defineConfig({
|
||||
test: {
|
||||
include: TEST_FILES,
|
||||
env: { TMPDIR: CANONICAL_TEMPORARY_DIRECTORY },
|
||||
...dualCoreProjects,
|
||||
retry: 2,
|
||||
reporters: ["default", "verbose"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue