import { OpenAIBackend } from "../backends/openai.js"; import { ChatCompletionResponse } from "../backends/llm-base.js"; describe("OpenAIBackend", () => { const originalFetch = globalThis.fetch; let fetchCalls: Array<{ url: string; headers: Record; body: string }>; beforeEach(() => { fetchCalls = []; }); afterEach(() => { globalThis.fetch = originalFetch; delete process.env.TEST_OPENAI_KEY; delete process.env.TEST_OPENAI_KEY_EMPTY; }); function mockFetch(response: ChatCompletionResponse, status = 200): void { globalThis.fetch = ((url: string, init: RequestInit) => { fetchCalls.push({ url, headers: (init.headers as Record) || {}, body: init.body as string, }); return Promise.resolve({ ok: status >= 200 && status < 300, status, text: () => Promise.resolve(JSON.stringify(response)), json: () => Promise.resolve(response), } as Response); }) as typeof fetch; } describe("isAvailable", () => { it("returns true when API key is present", async () => { process.env.TEST_OPENAI_KEY = "sk-test-key-123"; const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "TEST_OPENAI_KEY", model: "gpt-4o", model_profile: "quality", }); expect(await backend.isAvailable()).toBe(true); }); it("returns false when API key is absent", async () => { const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "NONEXISTENT_OPENAI_KEY_VAR_99999", model: "gpt-4o", model_profile: "quality", }); expect(await backend.isAvailable()).toBe(false); }); it("returns false when API key is empty string", async () => { process.env.TEST_OPENAI_KEY_EMPTY = ""; const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "TEST_OPENAI_KEY_EMPTY", model: "gpt-4o", model_profile: "quality", }); expect(await backend.isAvailable()).toBe(false); }); }); describe("resolveModel", () => { it("returns config.model when set", async () => { process.env.TEST_OPENAI_KEY = "sk-test"; mockFetch({ choices: [{ message: { content: '{"success": true, "output": "done"}' } }], usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }, }); const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "TEST_OPENAI_KEY", model: "gpt-4o-mini", model_profile: "speed", }); const request = { persona: "executor" as const, workflow: "execute", task: "test", context: { project_path: "/tmp", phase: 1, stage: "execute" as const, specification: "", config_path: "", }, autonomy: "full" as const, }; await backend.execute(request); const body = JSON.parse(fetchCalls[0].body); expect(body.model).toBe("gpt-4o-mini"); }); it("defaults to gpt-4o when model not specified", async () => { process.env.TEST_OPENAI_KEY = "sk-test"; mockFetch({ choices: [{ message: { content: '{"success": true, "output": "done"}' } }], usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }, }); const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "TEST_OPENAI_KEY", model: "", model_profile: "quality", }); const request = { persona: "executor" as const, workflow: "execute", task: "test", context: { project_path: "/tmp", phase: 1, stage: "execute" as const, specification: "", config_path: "", }, autonomy: "full" as const, }; await backend.execute(request); const body = JSON.parse(fetchCalls[0].body); expect(body.model).toBe("gpt-4o"); }); }); describe("callModel request format", () => { it("sends correct URL, Authorization header, and body structure", async () => { process.env.TEST_OPENAI_KEY = "sk-test-key-abc"; const mockResponse: ChatCompletionResponse = { choices: [{ message: { content: '{"success": true, "output": "done"}' } }], usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 }, }; mockFetch(mockResponse); const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "TEST_OPENAI_KEY", model: "gpt-4o", model_profile: "quality", }); const request = { persona: "executor" as const, workflow: "execute", task: "Do the thing", context: { project_path: "/tmp", phase: 1, stage: "execute" as const, specification: "", config_path: "", }, autonomy: "full" as const, }; await backend.execute(request); expect(fetchCalls.length).toBe(1); expect(fetchCalls[0].url).toBe("https://api.openai.com/v1/chat/completions"); expect(fetchCalls[0].headers["Authorization"]).toBe("Bearer sk-test-key-abc"); expect(fetchCalls[0].headers["Content-Type"]).toBe("application/json"); const body = JSON.parse(fetchCalls[0].body); expect(body.model).toBe("gpt-4o"); expect(body.stream).toBe(false); expect(Array.isArray(body.messages)).toBe(true); expect(body.messages.length).toBeGreaterThanOrEqual(2); expect(body.messages[0].role).toBe("system"); expect(body.messages[1].role).toBe("user"); expect(body.messages[1].content).toBe("Do the thing"); expect(Array.isArray(body.tools)).toBe(true); }); }); describe("custom base_url override", () => { it("sends request to custom base_url", async () => { process.env.TEST_OPENAI_KEY = "sk-test"; mockFetch({ choices: [{ message: { content: '{"success": true, "output": "done"}' } }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, }); const backend = new OpenAIBackend({ base_url: "https://custom-proxy.example.com/api", api_key_env: "TEST_OPENAI_KEY", model: "gpt-4o", model_profile: "quality", }); const request = { persona: "executor" as const, workflow: "execute", task: "test", context: { project_path: "/tmp", phase: 1, stage: "execute" as const, specification: "", config_path: "", }, autonomy: "full" as const, }; await backend.execute(request); expect(fetchCalls[0].url).toBe("https://custom-proxy.example.com/api/chat/completions"); }); }); describe("organization header", () => { it("sends OpenAI-Organization header when config.organization is set", async () => { process.env.TEST_OPENAI_KEY = "sk-test"; mockFetch({ choices: [{ message: { content: '{"success": true, "output": "done"}' } }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, }); const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "TEST_OPENAI_KEY", model: "gpt-4o", model_profile: "quality", organization: "org-abc123", }); const request = { persona: "executor" as const, workflow: "execute", task: "test", context: { project_path: "/tmp", phase: 1, stage: "execute" as const, specification: "", config_path: "", }, autonomy: "full" as const, }; await backend.execute(request); expect(fetchCalls[0].headers["OpenAI-Organization"]).toBe("org-abc123"); }); it("does not send OpenAI-Organization header when config.organization is not set", async () => { process.env.TEST_OPENAI_KEY = "sk-test"; mockFetch({ choices: [{ message: { content: '{"success": true, "output": "done"}' } }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, }); const backend = new OpenAIBackend({ base_url: "https://api.openai.com/v1", api_key_env: "TEST_OPENAI_KEY", model: "gpt-4o", model_profile: "quality", }); const request = { persona: "executor" as const, workflow: "execute", task: "test", context: { project_path: "/tmp", phase: 1, stage: "execute" as const, specification: "", config_path: "", }, autonomy: "full" as const, }; await backend.execute(request); expect(fetchCalls[0].headers["OpenAI-Organization"]).toBeUndefined(); }); }); });