UNPKG

@access-mcp/shared

Version:

Shared utilities for ACCESS-CI MCP servers

119 lines (118 loc) 4.76 kB
import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { spawn } from "child_process"; import path from "path"; /** * Integration tests for inter-server HTTP communication * These tests start actual MCP servers and test tool execution via HTTP */ describe("Inter-server Communication Integration Tests", () => { let nsfServer; const NSF_PORT = 3199; const NSF_URL = `http://localhost:${NSF_PORT}`; beforeAll(async () => { // Start NSF Awards server in HTTP mode const serverPath = path.resolve(process.cwd(), "packages/nsf-awards/dist/index.js"); nsfServer = spawn("node", [serverPath], { env: { ...process.env, PORT: String(NSF_PORT), LOG_LEVEL: "info", // Enable info logging to see startup message }, stdio: ["ignore", "pipe", "pipe"], }); // Wait for server to be ready await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Server startup timeout")); }, 10000); // Logger writes to stderr, not stdout nsfServer.stderr?.on("data", (data) => { if (data.toString().includes("HTTP server running")) { clearTimeout(timeout); resolve(); } }); nsfServer.on("error", (err) => { clearTimeout(timeout); reject(err); }); }); }, 15000); afterAll(async () => { if (nsfServer) { nsfServer.kill("SIGTERM"); // Wait for process to exit await new Promise((resolve) => { nsfServer.on("exit", () => resolve()); setTimeout(resolve, 1000); // Fallback timeout }); } }); describe("NSF Awards Server HTTP API", () => { it("should respond to health check", async () => { const response = await fetch(`${NSF_URL}/health`); const data = await response.json(); expect(response.status).toBe(200); expect(data.status).toBe("healthy"); expect(data.server).toBe("access-mcp-nsf-awards"); }); it("should list available tools", async () => { const response = await fetch(`${NSF_URL}/tools`); const data = await response.json(); expect(response.status).toBe(200); expect(data.tools).toBeDefined(); expect(Array.isArray(data.tools)).toBe(true); const toolNames = data.tools.map((t) => t.name); expect(toolNames).toContain("search_nsf_awards"); }); it("should execute search_nsf_awards tool via HTTP", async () => { const response = await fetch(`${NSF_URL}/tools/search_nsf_awards`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ arguments: { keyword: "machine learning", limit: 5, }, }), }); const data = await response.json(); expect(response.status).toBe(200); expect(data.content).toBeDefined(); expect(data.content[0].type).toBe("text"); // Parse the result const result = JSON.parse(data.content[0].text); expect(result).toBeDefined(); }, 15000); it("should return 404 for unknown tool", async () => { const response = await fetch(`${NSF_URL}/tools/nonexistent_tool`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ arguments: {} }), }); expect(response.status).toBe(404); }); }); describe("SSE Endpoint", () => { it("should accept SSE connections on /sse", async () => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 2000); try { const response = await fetch(`${NSF_URL}/sse`, { signal: controller.signal, }); expect(response.status).toBe(200); expect(response.headers.get("content-type")).toContain("text/event-stream"); } catch (e) { // AbortError is expected if (e instanceof Error && e.name !== "AbortError") { throw e; } } finally { clearTimeout(timeout); } }); }); });