NODEDC_DESIGN_GUIDELINE/server/foundry-agent-gateway.test.mjs

156 lines
7.0 KiB
JavaScript

import assert from "node:assert/strict";
import { createServer } from "node:http";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { createFoundryAgentGateway } from "./foundry-agent-gateway.mjs";
import { createFoundryAgentStore } from "./foundry-agent-store.mjs";
import { handleFoundryMcpRequest } from "./foundry-mcp.mjs";
async function listen(server) {
await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address();
return `http://127.0.0.1:${address.port}`;
}
async function close(server) {
await new Promise((resolve) => server.close(resolve));
}
async function json(response) {
return await response.json();
}
test("one setup command provisions independent Foundry and Ontology MCP transports", async () => {
const dataRoot = await mkdtemp(path.join(os.tmpdir(), "nodedc-foundry-agent-gateway-"));
const installerPath = path.join(dataRoot, "installer.tgz");
await writeFile(installerPath, "installer", "utf8");
let ontologyAuthorization = "";
const ontologyServer = createServer(async (request, response) => {
ontologyAuthorization = String(request.headers.authorization || "");
const chunks = [];
for await (const chunk of request) chunks.push(chunk);
const message = JSON.parse(Buffer.concat(chunks).toString("utf8"));
const result = message.method === "tools/list"
? { tools: [{ name: "ontology_status" }, { name: "ontology_search" }] }
: {
protocolVersion: "2025-06-18",
capabilities: { tools: { listChanged: false } },
serverInfo: { name: "nodedc-ontology-core", version: "test" },
};
response.writeHead(200, { "content-type": "application/json", "mcp-protocol-version": "2025-06-18" });
response.end(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }));
});
const ontologyBase = await listen(ontologyServer);
const store = createFoundryAgentStore({ dataRoot: path.join(dataRoot, "store") });
const gateway = createFoundryAgentGateway({
store,
installerPackagePath: installerPath,
ontologyCoreUrl: ontologyBase,
ontologyCoreAccessToken: "platform-internal-token",
});
const operations = {
status: async () => ({ ok: true }),
listApplications: async () => ({ applications: [] }),
getApplication: async () => ({ application: null }),
};
const foundryServer = createServer(async (request, response) => {
const url = new URL(request.url || "/", `http://${request.headers.host}`);
if (await gateway.handlePublicRequest(request, response, url)) return;
if (await gateway.handleOntologyMcp(request, response, url)) return;
if (url.pathname === "/api/mcp") {
await handleFoundryMcpRequest(request, response, {
getConfig: () => ({ capabilitySecret: "", mcpAllowedOrigins: [] }),
operations,
authenticateAgentToken: gateway.authenticateFoundryToken,
});
return;
}
if (await gateway.handleManagementRequest(request, response, url, {
id: "user_root",
email: "dcctouch@gmail.com",
displayName: "DC SUDO",
})) return;
response.writeHead(404);
response.end();
});
const foundryBase = await listen(foundryServer);
try {
const createdResponse = await fetch(`${foundryBase}/api/foundry-agent-api/agents`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "Foundry Codex" }),
});
assert.equal(createdResponse.status, 201);
const created = await json(createdResponse);
const setupResponse = await fetch(`${foundryBase}/api/foundry-agent-api/agents/${created.agent.id}/setup-code`, { method: "POST" });
assert.equal(setupResponse.status, 201);
const setup = await json(setupResponse);
assert.match(setup.install.command, /nodedc-foundry-codex-agent/);
const code = setup.install.command.match(/--code\s+(fnd_setup_[A-Za-z0-9_-]+)/)?.[1];
assert.ok(code);
const redeemResponse = await fetch(`${foundryBase}/api/foundry-agent/v1/setup/redeem`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ code, deviceName: "Test Codex" }),
});
assert.equal(redeemResponse.status, 200);
const redeemed = await json(redeemResponse);
assert.equal(redeemed.foundry.serverName, "nodedc_module_foundry");
assert.equal(redeemed.ontology.serverName, "nodedc_ontology");
assert.equal(redeemed.ontology.readOnly, true);
assert.notEqual(redeemed.foundry.token, redeemed.ontology.token);
const foundryListResponse = await fetch(redeemed.foundry.mcpUrl, {
method: "POST",
headers: { authorization: `Bearer ${redeemed.foundry.token}`, "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
});
assert.equal(foundryListResponse.status, 200);
const foundryList = await json(foundryListResponse);
assert.ok(foundryList.result.tools.some((tool) => tool.name === "foundry_create_application"));
const crossTokenResponse = await fetch(redeemed.ontology.mcpUrl, {
method: "POST",
headers: { authorization: `Bearer ${redeemed.foundry.token}`, "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }),
});
assert.equal(crossTokenResponse.status, 401);
const ontologyListResponse = await fetch(redeemed.ontology.mcpUrl, {
method: "POST",
headers: { authorization: `Bearer ${redeemed.ontology.token}`, "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 3, method: "tools/list", params: {} }),
});
assert.equal(ontologyListResponse.status, 200);
const ontologyList = await json(ontologyListResponse);
assert.deepEqual(ontologyList.result.tools.map((tool) => tool.name), ["ontology_status", "ontology_search"]);
assert.equal(ontologyAuthorization, "Bearer platform-internal-token");
const revokeResponse = await fetch(`${foundryBase}/api/foundry-agent-api/agents/${created.agent.id}/revoke`, { method: "POST" });
assert.equal(revokeResponse.status, 200);
const revokedFoundryResponse = await fetch(redeemed.foundry.mcpUrl, {
method: "POST",
headers: { authorization: `Bearer ${redeemed.foundry.token}`, "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 4, method: "tools/list", params: {} }),
});
const revokedOntologyResponse = await fetch(redeemed.ontology.mcpUrl, {
method: "POST",
headers: { authorization: `Bearer ${redeemed.ontology.token}`, "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 5, method: "tools/list", params: {} }),
});
assert.equal(revokedFoundryResponse.status, 401);
assert.equal(revokedOntologyResponse.status, 401);
} finally {
await close(foundryServer);
await close(ontologyServer);
await rm(dataRoot, { recursive: true, force: true });
}
});