Files
rea/tests/boundary/filesystem/clientConfigurationLayeredEnvironment.test.ts
T

377 lines
13 KiB
TypeScript

import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import {
clientRegistrationEntry,
parseClientConfiguration,
effectiveClientServer,
} from "../../../src/application/ClientConfigurationDocument.js";
import {
configureClientConfiguration,
inspectClientConfiguration,
clientConfigurationAligned,
} from "../../../src/application/SetupClientConfiguration.js";
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
import { npxRegistrationCommand } from "../../../src/application/ClientRegistrationIdentity.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import {
effectiveClientConfiguration,
readClientConfigurationFiles,
} from "../../../src/application/ClientConfigurationFiles.js";
import { clientRegistrationEnvironmentDetails } from "../../../src/application/ClientRegistrationEnvironment.js";
const command = npxRegistrationCommand(process.platform);
const language = "x86:LE:32:default";
const compiler = "borlandcpp";
it.each(["missing", "empty", "legacy", "native"] as const)(
"uses effective OpenCode native activation when the upper layer is %s",
async (shape) => {
const home = await createTestTempDirectory(
"rea-opencode-effective-dialect-",
);
const root = join(home, ".config", "opencode");
await mkdir(root, { recursive: true });
const lower = join(root, "opencode.json");
const upper = join(root, "opencode.jsonc");
const originalLower = JSON.stringify({
mcp: {
servers: {
rea: { type: "local", command, disabled: true },
other: { type: "local", command: ["other"], disabled: true },
},
},
});
await writeFile(lower, originalLower);
if (shape !== "missing")
await writeFile(
upper,
JSON.stringify({
theme: "caller-theme",
...(shape === "legacy"
? {
mcp: {
rea: {
type: "local",
command,
enabled: true,
future_policy: { retain: true },
},
},
}
: {}),
...(shape === "native" ? { mcp: { servers: {} } } : {}),
}),
);
const client = {
name: "opencode",
format: "opencode",
configPath: upper,
configPaths: [lower, upper],
} as const;
expect(
await configureClientConfiguration(client, {}, command),
).toMatchObject({ status: "configured" });
const files = await readClientConfigurationFiles(client);
expect(files.ok).toBe(true);
if (!files.ok) throw new Error(files.error.detail);
const effective = effectiveClientConfiguration(files.value);
expect(effective?.dialect).toBe("opencode_v2");
expect(
effective === undefined
? undefined
: effectiveClientServer(effective, "rea"),
).toMatchObject({ disabled: false });
const written = JSON.parse(await readFile(upper, "utf8"));
expect(written.mcp.servers.rea).toMatchObject({
type: "local",
command,
disabled: false,
});
if (shape === "legacy")
expect(written.mcp.servers.rea.future_policy).toEqual({ retain: true });
expect(written.mcp.rea).toBeUndefined();
expect(await inspectClientConfiguration(client, {}, command)).toEqual({
status: "already_current",
});
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "unchanged",
});
expect(await readFile(lower, "utf8")).toBe(originalLower);
},
);
const layered = async (lower: unknown, upper: unknown) => {
const home = await createTestTempDirectory("rea-layered-environment-");
const directory = join(home, ".config/opencode");
await mkdir(directory, { recursive: true });
const paths = [
join(directory, "opencode.json"),
join(directory, "opencode.jsonc"),
];
const sources = [lower, upper].map((environment) =>
JSON.stringify({
mcp: {
rea: {
...clientRegistrationEntry("opencode", command, {}),
environment,
},
},
}),
);
for (const [index, path] of paths.entries())
await writeFile(path, sources[index]!);
const client = {
name: "opencode",
format: "opencode",
configPath: paths[1]!,
configPaths: paths,
} as const;
const status = async () =>
(
await readClientRegistrationStatuses(home, "rea", {
environment: { XDG_CONFIG_HOME: join(home, ".config") },
})
).find(({ client }) => client === "opencode");
const unchanged = async () => {
for (const [index, path] of paths.entries()) {
expect(await readFile(path, "utf8")).toBe(sources[index]);
await expect(access(`${path}.rea.backup`)).rejects.toMatchObject({
code: "ENOENT",
});
}
};
return { client, paths, sources, status, unchanged };
};
it.skipIf(process.platform !== "win32").each([
{ REA_ANALYSIS_PROVIDER: "ghidra", rea_analysis_provider: "hopper" },
{ rea_analysis_provider: "hopper", REA_ANALYSIS_PROVIDER: "ghidra" },
])(
"keeps explicit overrides above inherited Windows aliases: %j",
async (lower) => {
const fixture = await layered(lower, {});
expect(
await configureClientConfiguration(
fixture.client,
{ REA_ANALYSIS_PROVIDER: "ghidra" },
command,
),
).toMatchObject({ status: "configured" });
const sources = await readClientConfigurationFiles(fixture.client);
expect(sources.ok).toBe(true);
if (!sources.ok) throw new Error(sources.error.detail);
const environment = clientRegistrationEnvironmentDetails(
effectiveClientConfiguration(sources.value),
);
expect(environment.ok).toBe(true);
if (!environment.ok) throw environment.error;
expect(environment.value.timeoutInputs?.analysisProvider).toBe("ghidra");
expect(environment.value.values).toHaveProperty(
"REA_ANALYSIS_PROVIDER",
"ghidra",
);
expect(environment.value.values).not.toHaveProperty(
"rea_analysis_provider",
);
expect(await readFile(fixture.paths[0]!, "utf8")).toBe(fixture.sources[0]);
expect(
await configureClientConfiguration(
fixture.client,
{ REA_ANALYSIS_PROVIDER: "ghidra" },
command,
),
).toEqual({ status: "unchanged" });
},
);
it("validates language/compiler settings in the effective OpenCode environment", async () => {
const fixture = await layered(
{ REA_GHIDRA_LANGUAGE_ID: language },
{ REA_GHIDRA_COMPILER_SPEC_ID: compiler },
);
expect(await inspectClientConfiguration(fixture.client, {}, command)).toEqual(
{ status: "already_current" },
);
expect(await clientConfigurationAligned(fixture.client, {}, command)).toBe(
true,
);
expect(await fixture.status()).toMatchObject({ state: "aligned" });
expect(
await configureClientConfiguration(fixture.client, {}, command),
).toEqual({ status: "unchanged" });
await fixture.unchanged();
});
it("rejects a companion absent from the complete layered environment", async () => {
const fixture = await layered(
{ CALLER_SETTING: "keep" },
{ REA_GHIDRA_COMPILER_SPEC_ID: compiler },
);
expect(
await inspectClientConfiguration(fixture.client, {}, command),
).toMatchObject({
status: "invalid",
remediation: expect.stringContaining("REA_GHIDRA_COMPILER_SPEC_ID"),
});
expect(await clientConfigurationAligned(fixture.client, {}, command)).toBe(
false,
);
expect(await fixture.status()).toMatchObject({
state: "invalid",
remediation: expect.stringContaining("REA_GHIDRA_COMPILER_SPEC_ID"),
});
expect(
await configureClientConfiguration(fixture.client, {}, command),
).toMatchObject({ status: "failed" });
await fixture.unchanged();
});
it("keeps higher-priority values and does not copy inherited settings into the write layer", async () => {
const fixture = await layered(
{
REA_GHIDRA_LANGUAGE_ID: language,
HOPPER_LOADER_ARGS_JSON: '["lower"]',
["__proto__"]: "lower caller value",
},
{
REA_GHIDRA_COMPILER_SPEC_ID: compiler,
HOPPER_LOADER_ARGS_JSON: '[""]',
["__proto__"]: "selected caller value",
},
);
expect(
await configureClientConfiguration(
fixture.client,
{ REA_LOG_LEVEL: "debug" },
command,
),
).toMatchObject({ status: "configured" });
expect(await readFile(fixture.paths[0]!, "utf8")).toBe(fixture.sources[0]);
const configured = parseClientConfiguration(
await readFile(fixture.client.configPath, "utf8"),
"opencode",
);
expect(effectiveClientServer(configured, "rea")).toHaveProperty(
"environment",
{
REA_GHIDRA_COMPILER_SPEC_ID: compiler,
HOPPER_LOADER_ARGS_JSON: '[""]',
REA_LOG_LEVEL: "debug",
["__proto__"]: "selected caller value",
},
);
expect(
await readFile(`${fixture.client.configPath}.rea.backup`, "utf8"),
).toBe(fixture.sources[1]);
expect(await fixture.status()).toMatchObject({ state: "aligned" });
expect(await clientConfigurationAligned(fixture.client, {}, command)).toBe(
true,
);
expect(
await configureClientConfiguration(fixture.client, {}, command),
).toEqual({ status: "unchanged" });
});
it("merges caller server names as own keys without inventing absent registrations", () => {
const lower = parseClientConfiguration(
'{"mcp":{"__proto__":{"type":"local","command":["peer"],"environment":{"LOWER":"retained"}}}}',
"opencode",
);
const upper = parseClientConfiguration(
'{"mcp":{"__proto__":{"environment":{"UPPER":"selected"}}}}',
"opencode",
);
expect(effectiveClientServer(lower, "constructor")).toBeUndefined();
const effective = effectiveClientConfiguration([
{ ...lower, sourcePath: "lower.json" },
{ ...upper, sourcePath: "upper.json" },
]);
expect(effective).toBeDefined();
if (effective === undefined) throw new Error("missing layered configuration");
expect(Object.keys(effective.servers)).toEqual(["__proto__"]);
expect(effectiveClientServer(effective, "__proto__")).toEqual({
type: "local",
command: ["peer"],
environment: { LOWER: "retained", UPPER: "selected" },
});
expect(effectiveClientServer(effective, "toString")).toBeUndefined();
});
it.each([
["invalid JSON", { HOPPER_LOADER_ARGS_JSON: "{" }],
["invalid value type", { HOPPER_LOADER_ARGS_JSON: 42 }],
["invalid environment map", []],
])(
"does not hide a lower-priority %s behind a valid override",
async (_name, lower) => {
const fixture = await layered(lower, { HOPPER_LOADER_ARGS_JSON: "[]" });
expect(
await inspectClientConfiguration(fixture.client, {}, command),
).toMatchObject({
status: "invalid",
remediation: expect.stringContaining(fixture.paths[0]!),
});
expect(await fixture.status()).toMatchObject({ state: "invalid" });
expect(
await configureClientConfiguration(fixture.client, {}, command),
).toMatchObject({ status: "failed" });
await fixture.unchanged();
},
);
it("uses inherited settings when creating a missing write layer", async () => {
const fixture = await layered({ REA_GHIDRA_LANGUAGE_ID: language }, {});
await rm(fixture.client.configPath);
const overrides = { REA_GHIDRA_COMPILER_SPEC_ID: compiler };
expect(
await inspectClientConfiguration(fixture.client, overrides, command),
).toEqual({ status: "create" });
expect(
await configureClientConfiguration(fixture.client, overrides, command),
).toMatchObject({ status: "configured" });
const parsed = parseClientConfiguration(
await readFile(fixture.client.configPath, "utf8"),
"opencode",
);
expect(effectiveClientServer(parsed, "rea")).toHaveProperty(
"environment",
overrides,
);
expect(await readFile(fixture.paths[0]!, "utf8")).toBe(fixture.sources[0]);
expect(await fixture.status()).toMatchObject({ state: "aligned" });
await expect(
access(`${fixture.client.configPath}.rea.backup`),
).rejects.toMatchObject({ code: "ENOENT" });
});
it("replaces the selected write layer rather than the last loaded layer", async () => {
const fixture = await layered(
{ REA_GHIDRA_LANGUAGE_ID: language },
{ REA_GHIDRA_COMPILER_SPEC_ID: compiler },
);
const client = { ...fixture.client, configPath: fixture.paths[0]! };
const overrides = { REA_LOG_LEVEL: "debug" };
expect(await inspectClientConfiguration(client, {}, command)).toEqual({
status: "already_current",
});
expect(
await configureClientConfiguration(client, overrides, command),
).toMatchObject({ status: "configured" });
const parsed = parseClientConfiguration(
await readFile(client.configPath, "utf8"),
"opencode",
);
expect(effectiveClientServer(parsed, "rea")).toHaveProperty("environment", {
REA_GHIDRA_LANGUAGE_ID: language,
...overrides,
});
expect(await readFile(fixture.paths[1]!, "utf8")).toBe(fixture.sources[1]);
expect(await readFile(`${client.configPath}.rea.backup`, "utf8")).toBe(
fixture.sources[0],
);
expect(await clientConfigurationAligned(client, {}, command)).toBe(true);
expect(await fixture.status()).toMatchObject({ state: "aligned" });
});