refactor(host): inject platform and environment instead of reading them ambiently (#549)
This commit is contained in:
@@ -7,9 +7,20 @@ import {
|
||||
} from "./domain/errors.js";
|
||||
import type { JsonValue } from "./domain/jsonValue.js";
|
||||
|
||||
/** Load the configured passive browser authority and provider for one CLI operation. */
|
||||
export const browserContext = async (operation: string) => {
|
||||
const config = parseConfig(process.env);
|
||||
/**
|
||||
* Load the configured passive browser authority and provider for one CLI
|
||||
* operation.
|
||||
*
|
||||
* `environment` defaults to the process environment so CLI callers stay
|
||||
* unchanged, but every caller may supply the environment explicitly. That is
|
||||
* what makes configuration-driven behaviour testable without mutating
|
||||
* `process.env` for the duration of a test.
|
||||
*/
|
||||
export const browserContext = async (
|
||||
operation: string,
|
||||
environment: Readonly<Record<string, string | undefined>> = process.env,
|
||||
) => {
|
||||
const config = parseConfig(environment);
|
||||
if (!config.ok)
|
||||
return { ok: false as const, error: browserCliError(config.error) };
|
||||
const policy = config.value.browserObservationPolicy;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { browserContext } from "./cliBrowserContext.js";
|
||||
import { HopperProvider } from "./hopper/HopperProvider.js";
|
||||
import { silentLogger } from "./logger.js";
|
||||
import { parseConfig } from "./config.js";
|
||||
|
||||
const config = (env: Record<string, string | undefined> = {}) => {
|
||||
const parsed = parseConfig(env);
|
||||
if (!parsed.ok) throw new Error("expected valid configuration");
|
||||
return parsed.value;
|
||||
};
|
||||
|
||||
describe("host platform is injected, not read from the ambient process", () => {
|
||||
it("reports Hopper as unsupported for a platform it cannot run on", () => {
|
||||
const provider = new HopperProvider(config(), silentLogger, "win32");
|
||||
const availability = provider.inspectAvailability();
|
||||
expect(availability).toMatchObject({
|
||||
status: "unavailable",
|
||||
code: "unsupported_host",
|
||||
});
|
||||
expect(JSON.stringify(availability)).toContain("win32");
|
||||
});
|
||||
|
||||
it.each(["darwin", "linux"] as const)(
|
||||
"does not gate Hopper as unsupported on %s",
|
||||
(platform) => {
|
||||
const provider = new HopperProvider(
|
||||
config({ hopper_launcher_path: "/nonexistent/hopper-launcher" }),
|
||||
silentLogger,
|
||||
platform,
|
||||
);
|
||||
expect(provider.inspectAvailability().code).not.toBe("unsupported_host");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("configuration is read from an injected environment", () => {
|
||||
it("lets the supplied environment decide whether the operation is available", async () => {
|
||||
// The error projection deliberately redacts specifics, so the meaningful
|
||||
// signal is the outcome: the same helper fails or succeeds purely as a
|
||||
// function of the environment it was handed.
|
||||
const disabled = await browserContext("inspect_web_page", {});
|
||||
expect(disabled.ok).toBe(false);
|
||||
|
||||
const enabled = await browserContext("inspect_web_page", {
|
||||
REA_BROWSER_OBSERVE_ENABLED: "true",
|
||||
REA_BROWSER_CDP_ENDPOINTS_JSON: '["http://127.0.0.1:9222"]',
|
||||
REA_BROWSER_ALLOWED_ORIGINS_JSON: '["https://app.example.test"]',
|
||||
});
|
||||
expect(enabled.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("reports a configuration error from the supplied environment", async () => {
|
||||
const invalid = await browserContext("inspect_web_page", {
|
||||
REA_BROWSER_ALLOWED_ORIGINS: "https://example.test",
|
||||
REA_BROWSER_MAX_BYTES: "not-a-number",
|
||||
});
|
||||
expect(invalid.ok).toBe(false);
|
||||
if (invalid.ok) return;
|
||||
// Projected through the shared analysis-error contract, like every other
|
||||
// CLI failure path.
|
||||
expect(JSON.stringify(invalid.error)).toContain("code");
|
||||
});
|
||||
});
|
||||
@@ -84,9 +84,16 @@ const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
|
||||
|
||||
/** Concrete analysis provider backed by REA's private Hopper bridge. */
|
||||
export class HopperProvider implements AnalysisProviderCandidate {
|
||||
/**
|
||||
* Host platform, injected so availability and launch-mode decisions can be
|
||||
* exercised for any host rather than only the machine running the suite.
|
||||
* Mirrors `GhidraProvider`, which already threads its installation host's
|
||||
* platform the same way.
|
||||
*/
|
||||
constructor(
|
||||
private readonly config: AppConfig,
|
||||
private readonly logger: Logger,
|
||||
private readonly platform: NodeJS.Platform = process.platform,
|
||||
) {}
|
||||
|
||||
identity(): ProviderIdentity {
|
||||
@@ -100,13 +107,13 @@ export class HopperProvider implements AnalysisProviderCandidate {
|
||||
inspectAvailability(): ProviderAvailability {
|
||||
const diagnostics = {
|
||||
launcher_path: this.config.hopperLauncherPath,
|
||||
platform: process.platform,
|
||||
platform: this.platform,
|
||||
};
|
||||
if (process.platform !== "darwin" && process.platform !== "linux")
|
||||
if (this.platform !== "darwin" && this.platform !== "linux")
|
||||
return {
|
||||
status: "unavailable",
|
||||
code: "unsupported_host",
|
||||
reason: `Hopper integration is not supported on ${process.platform}.`,
|
||||
reason: `Hopper integration is not supported on ${this.platform}.`,
|
||||
diagnostics,
|
||||
};
|
||||
try {
|
||||
@@ -205,7 +212,7 @@ export class HopperProvider implements AnalysisProviderCandidate {
|
||||
bridgeScriptPath: fileURLToPath(
|
||||
new URL("../../bridge/hopper_bridge.py", import.meta.url),
|
||||
),
|
||||
...(process.platform === "linux"
|
||||
...(this.platform === "linux"
|
||||
? {
|
||||
launchMode: "verified_linux_demo" as const,
|
||||
demoHelperPath: fileURLToPath(
|
||||
|
||||
Reference in New Issue
Block a user