Files
Ra3ModXmlExt/test/agentSetup.test.mjs
T
2026-09-10 21:39:32 +02:00

139 lines
5.0 KiB
JavaScript

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 });
}
});