225 lines
7.5 KiB
TypeScript
225 lines
7.5 KiB
TypeScript
/**
|
|
* 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();
|
|
});
|
|
});
|
|
});
|