Files
ci/src/backends/openai.test.ts
T
Jon Chery a8b50f5109
CI / build-and-test (push) Has been cancelled
Publish to npm / publish (push) Has been cancelled
feat(ci): v0.9.0 — Distribution & Expansion milestone complete
---ci---
project: ci
phase: 6
milestone: v0.9
status: complete
artifacts:
  tags: [v0.9.0]
decisions:
  - id: D-047
    decision: v0.9 theme = Distribution & Expansion
    rationale: npm publish + OpenAI/Anthropic backends + agent flesh + parallel execution
    confidence: 0.92
  - id: D-049
    decision: Feature milestone — patch tags v0.8.1-v0.8.6 then v0.9.0
    rationale: OpenAI backend, agent flesh, npm publish all feat
    confidence: 0.95
  - id: D-059
    decision: Rename OllamaBaseBackend to LLMBaseBackend + thin OllamaBaseBackend subclass
    rationale: 15 of 17 methods backend-agnostic
    confidence: 0.92
  - id: D-060
    decision: OpenAI/Anthropic backends use native fetch() not SDK packages
    rationale: No dependency bloat; fetch native in Node 18+
    confidence: 0.85
  - id: D-066
    decision: Concurrency limiter internal (no p-limit dependency)
    rationale: 15 lines; avoids dependency for trivial feature
    confidence: 0.90
  - id: D-067
    decision: Promise.allSettled for review agents at orchestrator lines 373-400
    rationale: Current sequential loop replaced with parallel execution
    confidence: 0.88
requirements:
  covered: [PUBLISH-01, PUBLISH-02, PUBLISH-03, PUBLISH-04, OPENAI-01, OPENAI-02, OPENAI-03, OPENAI-04, OPENAI-05, FLESH-01, FLESH-02, FLESH-03, FLESH-04, FLESH-05, ANTHROPIC-01, ANTHROPIC-02, FLESH-06, FLESH-07, NPM-01, NPM-02, PARALLEL-01, PARALLEL-02, PARALLEL-03, INTEG-01, INTEG-02, INTEG-03, INTEG-04, INTEG-05]
---/ci---

6 phases, 28 tasks, 4077 net lines added, 57 test suites, 527 tests, zero stub agents
2026-05-30 02:19:44 +00:00

279 lines
8.8 KiB
TypeScript

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<string, string>; 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<string, string>) || {},
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();
});
});
});