Files
reverse-engineering-anything/tests/boundary/filesystem/clientRegistrationProviderSettings.test.ts
Kaoru0822andClaude Opus 5.5 6a0ccf7eeb fix(doctor): report provider settings a client registration does not record (#8645)
* fix(doctor): report provider settings a client registration does not record

Provider checks read the terminal running doctor, so a registration with no
env block looked ready while its server reported not_configured. Compare what
an available provider relied on with the registration's own file and report the
setting names in a non-blocking registration_environment check. The client's
effective environment stays unknown, so health and the exit status are unchanged.

Fixes #8202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(doctor): write the Cordis fixture at its literal path

The test resolved the path through supportedClients, whose default environment
argument CodeQL traces to the fixture's writes even though the test passes {}.
It only needed the path, so spell it like the other fixtures in this file and
drop a leftover directory it never used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 05:14:50 -08:00

291 lines
8.9 KiB
TypeScript

import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { recordedProviderSettings } from "../../../src/application/ClientRegistrationEnvironment.js";
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
import { CURRENT_ENTRYPOINT_PATH } from "../../../src/process/CurrentEntrypointPath.js";
import { clearClientLocationEnvironment } from "../../fixtures/clientEnvironment.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
beforeEach(clearClientLocationEnvironment);
afterEach(() => {
vi.unstubAllEnvs();
});
const selected = {
GHIDRA_INSTALL_DIR: "/opt/ghidra",
JAVA_HOME: "/opt/jdk",
};
const entry = (env: unknown) =>
JSON.stringify({
mcpServers: {
rea: {
command: process.execPath,
args: [CURRENT_ENTRYPOINT_PATH, "mcp"],
...(env === undefined ? {} : { env }),
},
},
});
const readClaudeCode = async (env: unknown) => {
const home = await createTestTempDirectory("rea-recorded-settings-");
await writeFile(join(home, ".claude.json"), entry(env));
const [status] = await readClientRegistrationStatuses(
home,
CURRENT_ENTRYPOINT_PATH,
{ environment: {}, providerSettings: selected },
);
return status;
};
it.each([
[
"a registration with no environment map",
undefined,
{
unrecorded: ["GHIDRA_INSTALL_DIR", "JAVA_HOME"],
differing: [],
unresolved: [],
},
],
[
"a registration recording only part of the selection",
{ GHIDRA_INSTALL_DIR: "/opt/ghidra" },
{ unrecorded: ["JAVA_HOME"], differing: [], unresolved: [] },
],
[
"a registration recording another value",
{ GHIDRA_INSTALL_DIR: "/opt/other", JAVA_HOME: "/opt/jdk" },
{ unrecorded: [], differing: ["GHIDRA_INSTALL_DIR"], unresolved: [] },
],
])("reports names only for %s", async (_label, env, provider_settings) => {
expect(await readClaudeCode(env)).toMatchObject({
client: "claude_code",
provider_settings,
});
});
it.each([
["a non-string value", { GHIDRA_INSTALL_DIR: 1 }],
["a peer setting the runtime parser rejects", { REA_LOG_LEVEL: "shout" }],
])(
"leaves a malformed map with %s to the invalid registration result",
async (_label, env) => {
const status = await readClaudeCode(env);
expect(status?.state).toBe("invalid");
expect(status).not.toHaveProperty("provider_settings");
},
);
it("omits the comparison when the registration records the selection", async () => {
expect(await readClaudeCode(selected)).not.toHaveProperty(
"provider_settings",
);
});
it("keeps recorded values out of the status", async () => {
const status = await readClaudeCode({
GHIDRA_INSTALL_DIR: "/recorded/secret-path",
});
expect(status?.provider_settings?.differing).toEqual(["GHIDRA_INSTALL_DIR"]);
expect(JSON.stringify(status)).not.toContain("/recorded/secret-path");
});
it("does not read the comparison for callers that did not ask", async () => {
const home = await createTestTempDirectory("rea-recorded-settings-off-");
await writeFile(join(home, ".claude.json"), entry(undefined));
const [status] = await readClientRegistrationStatuses(
home,
CURRENT_ENTRYPOINT_PATH,
{ environment: {} },
);
expect(status).not.toHaveProperty("provider_settings");
});
it("retains the comparison on a stale launcher, which setup can still repair", async () => {
const home = await createTestTempDirectory("rea-recorded-settings-stale-");
await writeFile(
join(home, ".claude.json"),
JSON.stringify({
mcpServers: {
rea: {
command: "npx",
args: ["-y", PRODUCT_IDENTITY.registrationPackageSpecifier, "mcp"],
},
},
}),
);
const [status] = await readClientRegistrationStatuses(home, "/current/rea", {
environment: {},
providerSettings: selected,
});
expect(status).toMatchObject({
state: "stale",
provider_settings: { unrecorded: ["GHIDRA_INSTALL_DIR", "JAVA_HOME"] },
});
});
it("reads Codex's environment table", async () => {
const home = await createTestTempDirectory("rea-recorded-settings-codex-");
await mkdir(join(home, ".codex"));
await writeFile(
join(home, ".codex/config.toml"),
`[mcp_servers.rea]\ncommand = ${JSON.stringify(process.execPath)}\nargs = [${JSON.stringify(CURRENT_ENTRYPOINT_PATH)}, "mcp"]\nstartup_timeout_sec = 30\ntool_timeout_sec = 3600\n[mcp_servers.rea.env]\nGHIDRA_INSTALL_DIR = "/opt/ghidra"\n`,
);
const [status] = await readClientRegistrationStatuses(
home,
CURRENT_ENTRYPOINT_PATH,
{ environment: {}, providerSettings: selected },
);
expect(status).toMatchObject({
client: "codex",
provider_settings: {
unrecorded: ["JAVA_HOME"],
differing: [],
unresolved: [],
},
});
});
it("reads OpenCode's differently named environment member", async () => {
const home = await createTestTempDirectory("rea-recorded-settings-open-");
await mkdir(join(home, ".config/opencode"), { recursive: true });
await writeFile(
join(home, ".config/opencode/opencode.json"),
JSON.stringify({
mcp: {
rea: {
type: "local",
command: [process.execPath, CURRENT_ENTRYPOINT_PATH, "mcp"],
environment: { JAVA_HOME: "/opt/jdk" },
},
},
}),
);
const [status] = await readClientRegistrationStatuses(
home,
CURRENT_ENTRYPOINT_PATH,
{ environment: {}, providerSettings: selected },
);
expect(status).toMatchObject({
client: "opencode",
provider_settings: {
unrecorded: ["GHIDRA_INSTALL_DIR"],
differing: [],
unresolved: [],
},
});
});
it("reads the effective view of layered OpenCode files", async () => {
const home = await createTestTempDirectory("rea-recorded-settings-layer-");
await mkdir(join(home, ".config/opencode"), { recursive: true });
const layer = (environment: Readonly<Record<string, string>>) =>
JSON.stringify({
mcp: {
rea: {
type: "local",
command: [process.execPath, CURRENT_ENTRYPOINT_PATH, "mcp"],
environment,
},
},
});
const override = join(home, "override.json");
await writeFile(
join(home, ".config/opencode/opencode.json"),
layer({ JAVA_HOME: "/opt/jdk" }),
);
await writeFile(override, layer({ GHIDRA_INSTALL_DIR: "/opt/ghidra" }));
const statuses = await readClientRegistrationStatuses(
home,
CURRENT_ENTRYPOINT_PATH,
{
environment: { OPENCODE_CONFIG: override },
providerSettings: selected,
},
);
expect(
statuses.find(({ client }) => client === "opencode"),
).not.toHaveProperty("provider_settings");
});
it("keeps a Cordis expression unresolved instead of absent or different", async () => {
const home = await createTestTempDirectory("rea-recorded-settings-dsh-");
await mkdir(join(home, ".dsh"));
await writeFile(
join(home, ".dsh/cordis.patch.yml"),
`- insert:
- id: rea-mcp
name: "@deepseek-ai/dsh-mcp-client"
config:
serverName: rea
transport: stdio
command: ${JSON.stringify(process.execPath)}
args: [${JSON.stringify(CURRENT_ENTRYPOINT_PATH)}, mcp]
env:
GHIDRA_INSTALL_DIR: !!js process.env.GHIDRA_INSTALL_DIR
JAVA_HOME: /opt/other
`,
);
const statuses = await readClientRegistrationStatuses(
home,
CURRENT_ENTRYPOINT_PATH,
{ environment: {}, platform: "linux", providerSettings: selected },
);
expect(statuses.find((status) => status.client === "dsh")).toMatchObject({
provider_settings: {
unrecorded: [],
differing: ["JAVA_HOME"],
unresolved: ["GHIDRA_INSTALL_DIR"],
},
});
});
it("folds recorded environment names on Windows", () => {
const servers = {
rea: { env: { java_home: "/opt/jdk" } },
};
const parsed = {
document: { mcpServers: servers },
servers,
serversPath: ["mcpServers"],
dialect: "json",
environmentKey: "env",
legacyServers: {},
} as const;
expect(recordedProviderSettings(parsed, selected, "win32")).toEqual({
unrecorded: ["GHIDRA_INSTALL_DIR"],
differing: [],
unresolved: [],
});
expect(recordedProviderSettings(parsed, selected, "linux")).toEqual({
unrecorded: ["GHIDRA_INSTALL_DIR", "JAVA_HOME"],
differing: [],
unresolved: [],
});
expect(recordedProviderSettings(parsed, {}, "linux")).toBeUndefined();
});
it("does not treat a map it could not read as absent", () => {
const servers = { rea: { env: { GHIDRA_INSTALL_DIR: 1 } } };
expect(
recordedProviderSettings(
{
document: { mcpServers: servers },
servers,
serversPath: ["mcpServers"],
dialect: "json",
environmentKey: "env",
legacyServers: {},
},
selected,
"linux",
),
).toEqual({
unrecorded: [],
differing: [],
unresolved: ["GHIDRA_INSTALL_DIR", "JAVA_HOME"],
});
});