Files
reverse-engineering-anything/tests/boundary/providers/hopper/hopperClient.protocol.test.ts
morluto 4fbc501121 feat(native): add inspection primitives, dispatch traces and approved UI observation (#500)
* fix(hopper): normalize string-backed addresses and update verification

* feat(native): add inspection primitives, dispatch traces and approved UI observation

* test: prioritize real artifact E2E and captured golden regressions

* fix(process): queue observed output before reactive rendering actions

* test: prune redundant cases and strengthen boundary assertions

* fix(cli): route native value tracing through shared workflows

* test: replace synthetic success with real workflows and captured data

* fix(catalog): separate portable metadata from native host availability
2026-10-04 16:02:36 +08:00

188 lines
6.0 KiB
TypeScript

import { spawn, type ChildProcess } from "node:child_process";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { HOPPER_PROVIDER_TOOL_CONTRACTS } from "../../../../src/hopper/HopperProvider.js";
import { ok } from "../../../../src/domain/result.js";
import type {
BridgeLauncher,
BridgeSession,
} from "../../../../src/hopper/BridgeLauncher.js";
import { HopperClient } from "../../../../src/hopper/HopperClient.js";
import type { HopperDiagnostic } from "../../../../src/hopper/HopperDiagnostics.js";
import { providerCleanupFailure } from "../../../../src/hopper/HopperDiagnostics.js";
const fixturePath = fileURLToPath(
new URL("../../../fixtures/fakeHopper.mjs", import.meta.url),
);
class FixtureLauncher implements BridgeLauncher {
socketPaths: string[] = [];
directories: string[] = [];
runIds: string[] = [];
processes: ChildProcess[] = [];
constructor(readonly tokenOverride?: string) {}
launch(session: BridgeSession) {
this.socketPaths.push(session.socketPath);
this.directories.push(session.directory);
this.runIds.push(session.runId);
const child = spawn(
process.execPath,
[
fixturePath,
session.socketPath,
this.tokenOverride ?? session.token,
session.runId,
],
{
stdio: ["ignore", "ignore", "pipe"],
},
);
this.processes.push(child);
return Promise.resolve(
ok({
process: child,
ownsProcessLifetime: true as const,
shutdownMode: "bridge-request" as const,
}),
);
}
}
const clients: HopperClient[] = [];
const startClient = async () => {
const client = new HopperClient({
launcher: new FixtureLauncher(),
startupTimeoutMs: 1_000,
});
clients.push(client);
await expect(client.start()).resolves.toEqual({
ok: true,
value: { name: "REA Hopper bridge", version: "1.0.0" },
});
return client;
};
afterEach(async () => {
await Promise.all(clients.splice(0).map((client) => client.close()));
});
describe("HopperClient protocol", () => {
it("sanitizes unexpected owned-cleanup exceptions", () => {
expect(
providerCleanupFailure(
new Error("secret-run-token 22222222-2222-4222-8222-222222222222"),
),
).toEqual({
cleaned: false,
reason: "owned provider cleanup failed",
});
});
it("uses the caller-assigned provider run identity", async () => {
const launcher = new FixtureLauncher();
const client = new HopperClient({
launcher,
runId: "11111111-1111-4111-8111-111111111111",
startupTimeoutMs: 1_000,
});
clients.push(client);
expect((await client.start()).ok).toBe(true);
expect(launcher.runIds).toEqual(["11111111-1111-4111-8111-111111111111"]);
});
it("keeps the native socket path below macOS sockaddr_un limits", async () => {
const launcher = new FixtureLauncher();
const client = new HopperClient({ launcher, startupTimeoutMs: 1_000 });
clients.push(client);
const started = await client.start();
expect(started.ok).toBe(true);
expect(Buffer.byteLength(launcher.socketPaths[0] ?? "")).toBeLessThan(104);
});
it("serializes concurrent calls until the active Hopper reply arrives", async () => {
const client = await startClient();
const slow = client.callTool("echo", { label: "slow", delay: 30 });
const fast = client.callTool("echo", { label: "fast", delay: 1 });
let fastSettled = false;
void fast.then(() => {
fastSettled = true;
});
await new Promise((resolve) => setTimeout(resolve, 10));
expect(fastSettled).toBe(false);
await expect(Promise.all([slow, fast])).resolves.toEqual([
{ ok: true, value: { label: "slow", delay: 30 } },
{ ok: true, value: { label: "fast", delay: 1 } },
]);
});
it("routes every established operation through the authenticated bridge", async () => {
const client = await startClient();
const results = await Promise.all(
HOPPER_PROVIDER_TOOL_CONTRACTS.map(({ name }) =>
client.callTool(name, {}),
),
);
expect(results).toHaveLength(HOPPER_PROVIDER_TOOL_CONTRACTS.length);
expect(results.every((result) => result.ok)).toBe(true);
});
it("rejects a bridge session with the wrong capability token", async () => {
const client = new HopperClient({
launcher: new FixtureLauncher("wrong-token"),
startupTimeoutMs: 1_000,
});
clients.push(client);
const result = await client.start();
expect(result.ok).toBe(false);
if (!result.ok) expect(result.error._tag).toBe("HopperRemoteError");
});
it.each([
["malformed", "HopperProtocolError"],
["wrong_id", "HopperProtocolError"],
["wrong_event_id", "HopperProtocolError"],
["malformed_event", "HopperProtocolError"],
["remote_error", "HopperRemoteError"],
["exit", "HopperProcessError"],
])("projects %s as %s", async (method, expectedTag) => {
const client = await startClient();
const result = await client.callTool(method);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe(expectedTag);
if (result.error._tag === "HopperRemoteError")
expect(result.error).toMatchObject({ operation: method, requestId: 2 });
}
});
it("preserves a sanitized bridge exception diagnostic", async () => {
const diagnostics: HopperDiagnostic[] = [];
const client = new HopperClient({
launcher: new FixtureLauncher(),
startupTimeoutMs: 1_000,
onDiagnostic: (event) => diagnostics.push(event),
});
clients.push(client);
await expect(client.start()).resolves.toMatchObject({ ok: true });
const result = await client.callTool("remote_error");
expect(result.ok).toBe(false);
if (!result.ok)
expect(result.error).toMatchObject({
_tag: "HopperRemoteError",
diagnosticType: "bridge_exception",
});
expect(diagnostics).toContainEqual({
type: "bridge-diagnostic",
request_id: 2,
code: -32001,
category: "bridge_exception",
message: "safe fake failure",
});
});
});