Restore test fixture runtime prerequisites and align assertions with current Evidence and uncertainty contracts. Separate actual CLI publisher refusal from explicitly admitted Hopper socket/CLI fixtures, retaining exact wire identity and cleanup coverage. Validate retained EVM Evidence against exact unchanged source-carrier bytes. Validation: 88 targeted local test passes across three completed selections, two platform skips, full check:ci; hosted Windows probes and complete current-head CI. No production authority, authentication or schema change. Controlled fixtures do not qualify actual disassemblers or workers. Fixes #4690.
132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
import { copyFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { afterEach, expect, it, onTestFinished, vi } from "vitest";
|
|
import type {
|
|
BridgeLauncher,
|
|
BridgeSession,
|
|
} from "../../src/hopper/BridgeLauncher.js";
|
|
import { analysisCliErrorEnvelopeSchema } from "../../src/contracts/errorSchemas.js";
|
|
import { parseEvidence } from "../../src/domain/evidence.js";
|
|
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
|
import { waitForExit } from "../support/process/processFixture.js";
|
|
import { HopperFixtureLauncher } from "../boundary/providers/hopper/hopperClient.fixture.js";
|
|
import { serveCli } from "../boundary/cli/cliServeHarness.js";
|
|
|
|
const originalExitCode = process.exitCode;
|
|
afterEach(() => {
|
|
process.exitCode = originalExitCode;
|
|
});
|
|
|
|
const fixture = vi.hoisted(() => ({
|
|
launcher: undefined as BridgeLauncher | undefined,
|
|
}));
|
|
|
|
// Substitute only process admission. CLI composition, provider, regex search,
|
|
// socket protocol, Evidence and error projection all use production code.
|
|
// The compiled CLI separately verifies refusal of an unauthenticated launcher.
|
|
vi.mock("../../src/hopper/BridgeLauncher.js", async (importOriginal) => ({
|
|
...(await importOriginal<
|
|
typeof import("../../src/hopper/BridgeLauncher.js")
|
|
>()),
|
|
HopperApplicationLauncher: class implements BridgeLauncher {
|
|
launch(session: BridgeSession) {
|
|
if (fixture.launcher === undefined)
|
|
throw new Error("Missing fixture launcher");
|
|
return fixture.launcher.launch(session);
|
|
}
|
|
},
|
|
}));
|
|
|
|
it
|
|
.skipIf(process.platform === "win32")
|
|
.each(
|
|
(["strings", "procedures"] as const).flatMap((kind) =>
|
|
(["capability_unavailable", "bridge_exception", "valid"] as const).map(
|
|
(mode) => ({ kind, mode }),
|
|
),
|
|
),
|
|
)(
|
|
"projects $kind regex $mode through CLI with exact wire identity",
|
|
async ({ kind, mode }) => {
|
|
process.exitCode = undefined;
|
|
const home = await createTestTempDirectory("rea-hopper-regex-cli-");
|
|
const target = join(home, "sample");
|
|
await copyFile("/usr/bin/true", target);
|
|
const launcher = new HopperFixtureLauncher(
|
|
undefined,
|
|
"quiet",
|
|
"ready",
|
|
"acknowledge",
|
|
mode === "valid" ? "none" : mode,
|
|
);
|
|
fixture.launcher = launcher;
|
|
onTestFinished(async () => {
|
|
fixture.launcher = undefined;
|
|
for (const child of launcher.processes) {
|
|
if (child.exitCode === null && child.signalCode === null)
|
|
child.kill("SIGTERM");
|
|
await waitForExit(child, 5_000);
|
|
}
|
|
});
|
|
const run = await serveCli(
|
|
[
|
|
"search",
|
|
target,
|
|
"fixture",
|
|
"--provider",
|
|
"hopper",
|
|
"--kind",
|
|
kind,
|
|
"--mode",
|
|
"regex",
|
|
"--json",
|
|
],
|
|
{
|
|
HOME: home,
|
|
HOPPER_LAUNCHER_PATH: process.execPath,
|
|
REA_LOG_LEVEL: "silent",
|
|
},
|
|
);
|
|
const output: unknown = JSON.parse(run.stdout);
|
|
if (mode === "valid") {
|
|
expect(process.exitCode ?? run.exitCode, run.stdout).toBe(0);
|
|
const evidence = parseEvidence(output);
|
|
expect(evidence.operation).toBe(`search_${kind}`);
|
|
expect(evidence.normalized_result).toEqual([
|
|
{ address: "0x1000", value: "fixture" },
|
|
]);
|
|
} else {
|
|
expect(process.exitCode ?? run.exitCode, run.stdout).toBe(1);
|
|
const error = analysisCliErrorEnvelopeSchema.parse(output);
|
|
const request = await launcher.waitForRequest(`list_${kind}`);
|
|
expect(error.code, run.stdout).toBe(
|
|
mode === "capability_unavailable"
|
|
? "capability_unavailable"
|
|
: "execution_failure",
|
|
);
|
|
expect(error.details).toMatchObject({
|
|
operation: `search_${kind}`,
|
|
diagnostics: {
|
|
provider_request: {
|
|
operation: request.method,
|
|
request_id: request.id,
|
|
provider_code: -32000,
|
|
diagnostic_type: mode,
|
|
},
|
|
},
|
|
});
|
|
if (mode === "bridge_exception")
|
|
expect(error.message).toContain(`search_${kind}`);
|
|
}
|
|
expect(launcher.requests.map(({ method }) => method)).toEqual([
|
|
"health",
|
|
`list_${kind}`,
|
|
"shutdown",
|
|
]);
|
|
expect(launcher.processes).toHaveLength(1);
|
|
const child = launcher.processes[0];
|
|
if (child === undefined) throw new Error("Missing fixture process");
|
|
expect(child.exitCode !== null || child.signalCode !== null).toBe(true);
|
|
},
|
|
);
|