Files
reverse-engineering-anything/src/application/ClientConfigurationDocument.shape.test.ts
T
morluto 9ca6b41ee0 fix(setup): name client configuration syntax from one reader table (#10586)
* fix(setup): name client configuration syntax from one reader table

Each client format now maps to one reader description (syntax, strict or
JSONC, BOM and empty-file handling, strict-JSON repair). The document
parser, the strict-JSON rewrite decision and uninstall's diagnostic all
read it, so uninstall no longer re-derives a syntax name that called
Goose's YAML and JSONC-admitting clients "JSON".

* test(hopper): retry cleanup after confirmed application exit
2026-11-14 14:37:04 -05:00

96 lines
3.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
ClientConfigurationShapeError,
clientConfigurationSyntaxName,
parseClientConfiguration,
shadowedClientServers,
} from "./ClientConfigurationDocument.js";
describe("parseClientConfiguration shape diagnostics", () => {
it.each([
["null root", "null", "root"],
["array root", "[]", "root"],
["string root", '"x"', "root"],
["null server table", '{"mcpServers":null,"keep":1}', "mcpServers"],
["array server table", '{"mcpServers":[],"keep":1}', "mcpServers"],
["string server table", '{"mcpServers":"x","keep":1}', "mcpServers"],
])("reports a non-object %s", (_label, text, location) => {
let error: unknown;
try {
parseClientConfiguration(text, "json");
} catch (cause: unknown) {
error = cause;
}
expect(error).toBeInstanceOf(ClientConfigurationShapeError);
if (!(error instanceof Error)) throw new Error("Expected shape error");
expect(error.message).toContain(location);
expect(error.message).toContain("object");
});
// The diagnostic syntax name must describe what the reader actually admits.
it.each([
["json", "JSONC"],
["vscode", "JSONC"],
["opencode", "JSONC"],
["kilo", "JSONC"],
["zed", "JSONC"],
["amp", "JSONC"],
["copilot_cli", "JSONC"],
["fx", "JSONC"],
["claude_code", "JSON"],
["pi", "JSON"],
["kimi", "JSON"],
["lm_studio", "JSON"],
["zcode", "JSON"],
["toml", "TOML"],
["grok", "TOML"],
["goose", "YAML"],
["hermes", "YAML"],
] as const)("names %s configuration syntax %s", (format, name) => {
expect(clientConfigurationSyntaxName(format)).toBe(name);
const accepts = (text: string): boolean => {
try {
parseClientConfiguration(text, format);
return true;
} catch {
return false;
}
};
if (name === "TOML") expect(accepts("keep = 1\n")).toBe(true);
if (name === "YAML") expect(accepts("keep: 1\n")).toBe(true);
if (name === "JSON" || name === "JSONC") {
expect(accepts('{"keep":1}')).toBe(true);
expect(accepts('{"keep":1,}')).toBe(name === "JSONC");
expect(accepts('{"keep":1 /* comment */}')).toBe(name === "JSONC");
}
});
it("still rejects unparsable JSON with SyntaxError", () => {
expect(() => parseClientConfiguration("not-json", "json")).toThrow(
SyntaxError,
);
});
it("reports only duplicates in the effective server object", () => {
const owned = '{"command":"rea","args":["mcp"]}';
const activeTable = `{"mcpServers":{"rea":null,"rea":${owned}}}`;
const active = parseClientConfiguration(activeTable, "json");
expect(shadowedClientServers(activeTable, active, "rea")).toEqual({
servers: [null],
legacyServers: [],
});
// Removing from the effective parent leaves an earlier repeated parent
// inactive. Its members are not registrations the client would reveal.
const inactiveParent =
`{"mcpServers":{"rea":{"command":"/usr/bin/false"}},` +
`"mcpServers":{"rea":${owned}}}`;
const parsed = parseClientConfiguration(inactiveParent, "json");
expect(shadowedClientServers(inactiveParent, parsed, "rea")).toEqual({
servers: [],
legacyServers: [],
});
});
});