NODEDC_DESIGN_GUIDELINE/scripts/foundry-agent-installer.tes...

97 lines
4.3 KiB
JavaScript

import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createServer } from "node:http";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import test from "node:test";
const execFileAsync = promisify(execFile);
const installer = new URL("../server/assets/foundry-agent-npm/bin/nodedc-foundry-codex-agent.mjs", import.meta.url);
async function listen(server) {
await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
return `http://127.0.0.1:${server.address().port}`;
}
test("Foundry installer preserves unrelated MCP entries and installs two independent servers", async () => {
let baseUrl = "";
const server = createServer(async (request, response) => {
if (request.url === "/api/foundry-agent/v1/setup/redeem") {
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({
ok: true,
foundry: { serverName: "nodedc_module_foundry", mcpUrl: `${baseUrl}/foundry-mcp`, token: "foundry-device-token" },
ontology: { serverName: "nodedc_ontology", mcpUrl: `${baseUrl}/ontology-mcp`, token: "ontology-device-token", readOnly: true },
}));
return;
}
if (request.url === "/foundry-mcp" || request.url === "/ontology-mcp") {
const chunks = [];
for await (const chunk of request) chunks.push(chunk);
const message = JSON.parse(Buffer.concat(chunks).toString("utf8"));
const tools = request.url === "/foundry-mcp"
? [{ name: "foundry_status" }, { name: "foundry_create_application" }]
: [{ name: "ontology_status" }, { name: "ontology_search" }];
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify({
jsonrpc: "2.0",
id: message.id,
result: message.method === "tools/list"
? { tools }
: { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "test", version: "1" } },
}));
return;
}
response.writeHead(404);
response.end();
});
baseUrl = await listen(server);
const codexHome = await mkdtemp(path.join(os.tmpdir(), "nodedc-foundry-installer-"));
await mkdir(codexHome, { recursive: true });
const original = [
"model = \"gpt-5\"",
"",
"[mcp_servers.nodedc_engine_agent]",
"url = \"https://engine.example/mcp\"",
"",
"[mcp_servers.nodedc_module_foundry]",
"url = \"https://stale.example/foundry\"",
"",
"[mcp_servers.nodedc_ontology]",
"url = \"https://stale.example/ontology\"",
"",
].join("\n");
await writeFile(path.join(codexHome, "config.toml"), original, "utf8");
try {
const result = await execFileAsync(process.execPath, [installer.pathname, "--server", baseUrl, "--code", "test-code", "--codex-home", codexHome]);
assert.match(result.stdout, /Foundry MCP smoke tools: 2/);
assert.match(result.stdout, /Ontology MCP smoke tools: 2/);
const config = await readFile(path.join(codexHome, "config.toml"), "utf8");
assert.match(config, /\[mcp_servers\.nodedc_engine_agent\]/);
assert.equal((config.match(/\[mcp_servers\.nodedc_module_foundry\]/g) || []).length, 1);
assert.equal((config.match(/\[mcp_servers\.nodedc_ontology\]/g) || []).length, 1);
assert.match(config, new RegExp(`${baseUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/foundry-mcp`));
assert.match(config, /Bearer foundry-device-token/);
assert.match(config, /Bearer ontology-device-token/);
assert.equal(config.includes("stale.example"), false);
const backup = await readFile(path.join(codexHome, "config.toml.nodedc-foundry-agent.bak"), "utf8");
assert.equal(backup, original);
const skill = await readFile(path.join(codexHome, "skills", "foundry-context", "SKILL.md"), "utf8");
assert.match(skill, /nodedc_module_foundry/);
assert.match(skill, /nodedc_ontology/);
const doctor = await execFileAsync(process.execPath, [installer.pathname, "doctor", "--codex-home", codexHome]);
assert.match(doctor.stdout, /install is present/);
} finally {
await new Promise((resolve) => server.close(resolve));
await rm(codexHome, { recursive: true, force: true });
}
});