rea/tests/acceptance/setup/clientConfigurationDiagnost...

168 lines
4.6 KiB
TypeScript

import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { expect } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
const cases = [
{
client: "cursor",
path: ".cursor/mcp.json",
text: "malformed UTF-8",
bytes: Buffer.concat([
Buffer.from('{"label":"'),
Buffer.from([0xff]),
Buffer.from('"}'),
]),
diagnostic: "not valid UTF-8",
},
{
client: "cursor",
path: ".cursor/mcp.json",
text: "[]",
diagnostic: "The configuration root must be an object",
},
{
client: "cursor",
path: ".cursor/mcp.json",
text: "null",
diagnostic: "The configuration root must be an object",
},
{
client: "cursor",
path: ".cursor/mcp.json",
text: '{"mcpServers":null,"keep":1}',
diagnostic: '"mcpServers" must be an object',
},
{
client: "cursor",
path: ".cursor/mcp.json",
text: '{"mcpServers":"x","keep":1}',
diagnostic: '"mcpServers" must be an object',
},
{
client: "pi",
path: ".pi/agent/mcp.json",
text: '{"mcpServers":[]}',
diagnostic: '"mcpServers" must be an object',
},
{
client: "codex",
path: ".codex/config.toml",
text: 'mcp_servers = "x"\n',
diagnostic: '"mcp_servers" must be an object',
},
{
client: "opencode",
path: ".config/opencode/opencode.json",
text: '{"mcp":{"servers":null}}',
diagnostic: '"mcp.servers" must be an object',
},
{
client: "hermes",
path: ".hermes/config.yaml",
text: "- item\n",
diagnostic: "The configuration root must be an object",
},
{
client: "hermes",
path: ".hermes/config.yaml",
text: "mcp_servers: null\nkeep: 1\n",
diagnostic: '"mcp_servers" must be an object',
},
{
client: "cursor",
path: ".cursor/mcp.json",
text: "not-json",
diagnostic: "Configuration is not valid JSON",
syntax: "JSON",
},
{
client: "hermes",
path: ".hermes/config.yaml",
text: "mcp_servers: [\n",
diagnostic: "Configuration is not valid YAML",
syntax: "YAML",
},
{
client: "dsh",
path: ".dsh/cordis.patch.yml",
text: "- insert: [\n",
diagnostic: "Configuration is not valid YAML",
syntax: "YAML",
},
] as const;
for (const scenario of cases)
cliTest(
`setup and uninstall explain ${scenario.client} configuration ${scenario.text}`,
async ({ cli }) => {
const home = await createTestTempDirectory(
"rea-client-config-diagnostic-",
);
const config = join(home, scenario.path);
await mkdir(dirname(config), { recursive: true });
const original =
"bytes" in scenario ? scenario.bytes : Buffer.from(scenario.text);
await writeFile(config, original);
const environment = {
HOME: home,
USERPROFILE: home,
XDG_CONFIG_HOME: join(home, ".config"),
HERMES_HOME: join(home, ".hermes"),
DSH_HOME: join(home, ".dsh"),
};
const setup = await cli.run({
arguments: [
"setup",
"--client",
scenario.client,
"--skill=false",
"--yes",
"--json",
],
cwd: home,
environment,
});
expect(setup.exitCode).toBe(1);
expect(setup.json).toMatchObject({
status: "needs_human",
plannedActions: [],
appliedActions: [],
});
if ("syntax" in scenario) {
expect(setup.json).toMatchObject({ remediation: expect.any(String) });
if (scenario.syntax === "JSON")
expect(setup.stdout).toContain("Invalid JSON/JSONC");
} else {
expect(setup.json).toMatchObject({
remediation: expect.stringContaining(scenario.diagnostic),
});
expect(setup.stdout).not.toContain("invalid_type");
}
const uninstall = await cli.run({
arguments: ["uninstall", "--json"],
cwd: home,
environment,
});
expect(uninstall.exitCode).toBe(1);
expect(uninstall.json).toMatchObject({
status: "failed",
items: expect.arrayContaining([
expect.objectContaining({
name: scenario.client,
status: "failed",
detail: expect.stringContaining(scenario.diagnostic),
}),
]),
});
if (!("syntax" in scenario))
expect(uninstall.stdout).not.toContain("not valid JSON");
expect(await readFile(config)).toEqual(original);
expect(await readdir(dirname(config))).toEqual([
scenario.path.split("/").at(-1),
]);
},
);