/** * Tests for gateway-proxy.ts — Go Gateway integration layer * * These tests verify the proxy functions that bridge Node.js tRPC * procedures to the Go Gateway service. */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; // ─── Mock fetch globally ─────────────────────────────────────────────────────── const mockFetch = vi.fn(); vi.stubGlobal("fetch", mockFetch); // Helper to create a mock Response function mockResponse(body: unknown, status = 200) { return { ok: status >= 200 && status < 300, status, json: async () => body, text: async () => JSON.stringify(body), } as Response; } // Import after mock setup import { checkGatewayHealth, isGatewayAvailable, getGatewayOrchestratorConfig, getGatewayTools, type GatewayToolDef, } from "./gateway-proxy"; describe("Go Gateway Proxy", () => { beforeEach(() => { mockFetch.mockReset(); // Default: gateway is available process.env.GATEWAY_URL = "http://localhost:18789"; }); afterEach(() => { vi.clearAllMocks(); }); // ─── checkGatewayHealth ────────────────────────────────────────────────────── describe("checkGatewayHealth", () => { it("returns connected=true when gateway responds with ok status", async () => { mockFetch.mockResolvedValueOnce( mockResponse({ status: "ok", service: "goclaw-gateway", version: "1.0.0", ollama: { connected: true, latencyMs: 5 }, }) ); const result = await checkGatewayHealth(); expect(result.connected).toBe(true); expect(result.latencyMs).toBeGreaterThanOrEqual(0); }); it("returns connected=false when gateway is unreachable", async () => { mockFetch.mockRejectedValueOnce(new Error("Connection refused")); const result = await checkGatewayHealth(); expect(result.connected).toBe(false); expect(result.latencyMs).toBe(0); }); it("returns connected=false when gateway returns non-ok status", async () => { mockFetch.mockResolvedValueOnce(mockResponse({ error: "Internal Server Error" }, 500)); const result = await checkGatewayHealth(); expect(result.connected).toBe(false); }); it("includes llm health info when gateway is healthy", async () => { mockFetch.mockResolvedValueOnce( mockResponse({ status: "ok", ollama: { connected: true, latencyMs: 12 }, }) ); const result = await checkGatewayHealth(); expect(result.connected).toBe(true); expect(result.llm).toBeDefined(); expect(result.llm?.connected).toBe(true); }); }); // ─── isGatewayAvailable ────────────────────────────────────────────────────── describe("isGatewayAvailable", () => { it("returns true when gateway health check succeeds", async () => { mockFetch.mockResolvedValueOnce(mockResponse({ status: "ok" })); const available = await isGatewayAvailable(); expect(available).toBe(true); }); it("returns false when gateway is unreachable", async () => { mockFetch.mockRejectedValueOnce(new Error("ECONNREFUSED")); const available = await isGatewayAvailable(); expect(available).toBe(false); }); }); // ─── getGatewayOrchestratorConfig ─────────────────────────────────────────── describe("getGatewayOrchestratorConfig", () => { it("returns orchestrator config when gateway responds", async () => { const mockConfig = { id: 1, name: "Orchestrator", model: "qwen2.5:7b", temperature: 0.5, maxTokens: 8192, allowedTools: ["shell_exec", "file_read", "file_list"], systemPromptPreview: "You are GoClaw Orchestrator.", }; mockFetch.mockResolvedValueOnce(mockResponse(mockConfig)); const config = await getGatewayOrchestratorConfig(); expect(config).not.toBeNull(); expect(config?.model).toBe("qwen2.5:7b"); expect(config?.allowedTools).toContain("shell_exec"); expect(config?.temperature).toBe(0.5); }); it("returns null when gateway is unreachable", async () => { mockFetch.mockRejectedValueOnce(new Error("Network error")); const config = await getGatewayOrchestratorConfig(); expect(config).toBeNull(); }); it("returns null when gateway returns non-ok status", async () => { mockFetch.mockResolvedValueOnce(mockResponse({ error: "not found" }, 404)); const config = await getGatewayOrchestratorConfig(); expect(config).toBeNull(); }); }); // ─── getGatewayTools ──────────────────────────────────────────────────────── describe("getGatewayTools", () => { it("maps OpenAI-format tools to GatewayToolDef", async () => { // Go Gateway returns OpenAI format: {type: "function", function: {...}} const openAITools = { count: 2, tools: [ { type: "function", function: { name: "shell_exec", description: "Execute a bash command on the host system.", parameters: { type: "object", properties: { command: { type: "string" } }, required: ["command"], }, }, }, { type: "function", function: { name: "file_read", description: "Read a file from the filesystem.", parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"], }, }, }, ], }; mockFetch.mockResolvedValueOnce(mockResponse(openAITools)); const tools = await getGatewayTools(); expect(tools).not.toBeNull(); expect(tools).toHaveLength(2); const shellTool = tools![0] as GatewayToolDef; expect(shellTool.name).toBe("shell_exec"); expect(shellTool.description).toContain("bash command"); expect(shellTool.parameters).toBeDefined(); }); it("also handles flat format tools (name, description, parameters)", async () => { const flatTools = { count: 1, tools: [ { name: "file_list", description: "List files at a path.", parameters: { type: "object", properties: { path: { type: "string" } } }, }, ], }; mockFetch.mockResolvedValueOnce(mockResponse(flatTools)); const tools = await getGatewayTools(); expect(tools).not.toBeNull(); expect(tools![0].name).toBe("file_list"); }); it("returns null when gateway is unreachable", async () => { mockFetch.mockRejectedValueOnce(new Error("Connection refused")); const tools = await getGatewayTools(); expect(tools).toBeNull(); }); it("returns null when response has no tools array", async () => { mockFetch.mockResolvedValueOnce(mockResponse({ error: "no tools" })); const tools = await getGatewayTools(); expect(tools).toBeNull(); }); }); });