import { test } from "node:test"; import assert from "node:assert/strict"; import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { addMcpServerToConfigFile, installMcpServerConfigToFile, launcherPath, mcpConfigJson, mcpServerConfig, readMcpInstallRecord, removeLauncher, removeMcpServerFromConfigFile, uninstallMcpServerConfigs, } from "../out/agent/setup.js"; test("mcpServerConfig and mcpConfigJson use the stable launcher", () => { const config = mcpServerConfig("C:/Users/me/.ra3modxml/ra3-mod-xml-mcp.cmd", "D:/Mods/Example"); assert.deepEqual(config.mcpServers["ra3-mod-xml"].args, ["--project", "D:/Mods/Example"]); const json = mcpConfigJson("C:/launcher.cmd", "D:/Proj"); assert.ok(json.includes("C:/launcher.cmd")); assert.ok(json.includes("D:/Proj")); }); test("addMcpServerToConfigFile creates and merges config", async () => { const dir = mkdtempSync(join(tmpdir(), "ra3-setup-test-")); try { const file = join(dir, "mcp.json"); await addMcpServerToConfigFile(file, "C:/launcher.cmd", "D:/Proj"); const first = JSON.parse(readFileSync(file, "utf8")); assert.ok(first.mcpServers["ra3-mod-xml"]); writeFileSync(file, JSON.stringify({ mcpServers: { other: { command: "x" } } }, null, 2)); await addMcpServerToConfigFile(file, "C:/launcher.cmd", "D:/Proj"); const merged = JSON.parse(readFileSync(file, "utf8")); assert.ok(merged.mcpServers.other); assert.ok(merged.mcpServers["ra3-mod-xml"]); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("removeMcpServerFromConfigFile removes only our entry and preserves the rest", async () => { const dir = mkdtempSync(join(tmpdir(), "ra3-setup-remove-")); try { const file = join(dir, "mcp.json"); writeFileSync( file, JSON.stringify( { mcpServers: { other: { command: "x" }, "ra3-mod-xml": { command: "launcher", args: ["--project", "D:/Mods/A"] }, }, someOtherKey: 1, }, null, 2, ), ); assert.equal(await removeMcpServerFromConfigFile(file), true); const parsed = JSON.parse(readFileSync(file, "utf8")); assert.ok(parsed.mcpServers.other); assert.equal(parsed.mcpServers["ra3-mod-xml"], undefined); assert.equal(parsed.someOtherKey, 1); // Nothing left to remove: second call is a no-op. assert.equal(await removeMcpServerFromConfigFile(file), false); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("removeMcpServerFromConfigFile handles VS Code's servers key and drops empty containers", async () => { const dir = mkdtempSync(join(tmpdir(), "ra3-setup-vscode-")); try { const file = join(dir, "mcp.json"); writeFileSync( file, JSON.stringify({ servers: { "ra3-mod-xml": { command: "x" } }, inputs: [] }, null, 2), ); assert.equal(await removeMcpServerFromConfigFile(file), true); const parsed = JSON.parse(readFileSync(file, "utf8")); assert.equal(parsed.servers, undefined); assert.deepEqual(parsed.inputs, []); // A missing file is "nothing to remove", not an error. assert.equal(await removeMcpServerFromConfigFile(join(dir, "nope.json")), false); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("installed MCP configs can be uninstalled through the install record", async () => { const home = mkdtempSync(join(tmpdir(), "ra3-setup-home-")); const dir = mkdtempSync(join(tmpdir(), "ra3-setup-record-")); try { const file = join(dir, "mcp.json"); await installMcpServerConfigToFile({ filePath: file, launcher: "C:/launcher.cmd", projectDir: "D:/Mods/A", label: "Test client", sourceVersion: "1.2.3", agentHome: home, }); const record = await readMcpInstallRecord(home); assert.equal(record.length, 1); assert.equal(record[0].label, "Test client"); assert.equal(record[0].serverKey, "ra3-mod-xml"); assert.equal(record[0].sourceVersion, "1.2.3"); const removed = await uninstallMcpServerConfigs({ agentHome: home }); assert.deepEqual(removed, [file]); assert.equal((await readMcpInstallRecord(home)).length, 0); // Our entry was the only server: the empty container is dropped. assert.deepEqual(JSON.parse(readFileSync(file, "utf8")), {}); } finally { rmSync(home, { recursive: true, force: true }); rmSync(dir, { recursive: true, force: true }); } }); test("removeLauncher deletes the stable launcher and tolerates its absence", async () => { const home = mkdtempSync(join(tmpdir(), "ra3-setup-launcher-")); try { writeFileSync(launcherPath(home), "dummy", "utf8"); assert.equal(existsSync(launcherPath(home)), true); assert.equal(await removeLauncher(home), true); assert.equal(existsSync(launcherPath(home)), false); // force: true, so removing it again is still a success. assert.equal(await removeLauncher(home), true); } finally { rmSync(home, { recursive: true, force: true }); } });