Files
waggle-os/packages/agent/tests/openai-compat.test.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

95 lines
3.3 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { openaiChat } from '../src/providers/openai-compat.js';
import type { ResolvedModel } from '../src/model-router.js';
const resolved: ResolvedModel = {
provider: 'openai',
model: 'gpt-4o-mini',
apiKey: 'test-key',
baseUrl: 'https://api.example.com/v1',
};
function okBody(content = 'hi') {
return new Response(
JSON.stringify({
choices: [{ message: { content } }],
model: 'gpt-4o-mini',
usage: { prompt_tokens: 12, completion_tokens: 5 },
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
);
}
const noSleep = async () => {};
describe('openaiChat', () => {
it('returns parsed content and usage on success', async () => {
const fetchImpl = vi.fn(async () => okBody('hello')) as unknown as typeof fetch;
const res = await openaiChat(resolved, [{ role: 'user', content: 'hey' }], undefined, { fetchImpl });
expect(res.content).toBe('hello');
expect(res.usage).toEqual({ input_tokens: 12, output_tokens: 5 });
});
it('passes an AbortSignal (timeout) to fetch', async () => {
const fetchImpl = vi.fn(async (_url: string | URL, init?: RequestInit) => {
expect(init?.signal).toBeInstanceOf(AbortSignal);
return okBody();
}) as unknown as typeof fetch;
await openaiChat(resolved, [{ role: 'user', content: 'x' }], undefined, { fetchImpl });
expect((fetchImpl as unknown as ReturnType<typeof vi.fn>)).toHaveBeenCalledTimes(1);
});
it('retries a transient 500 then succeeds', async () => {
let n = 0;
const fetchImpl = vi.fn(async () => {
n++;
if (n === 1) return new Response('upstream boom', { status: 500 });
return okBody('recovered');
}) as unknown as typeof fetch;
const res = await openaiChat(resolved, [{ role: 'user', content: 'x' }], undefined, {
fetchImpl,
sleepImpl: noSleep,
});
expect(res.content).toBe('recovered');
expect(n).toBe(2);
});
it('retries a network-level failure then succeeds', async () => {
let n = 0;
const fetchImpl = vi.fn(async () => {
n++;
if (n === 1) throw new Error('fetch failed');
return okBody('ok-after-network-retry');
}) as unknown as typeof fetch;
const res = await openaiChat(resolved, [{ role: 'user', content: 'x' }], undefined, {
fetchImpl,
sleepImpl: noSleep,
});
expect(res.content).toBe('ok-after-network-retry');
expect(n).toBe(2);
});
it('gives up after maxRetries on persistent 503', async () => {
const fetchImpl = vi.fn(async () => new Response('down', { status: 503 })) as unknown as typeof fetch;
await expect(
openaiChat(resolved, [{ role: 'user', content: 'x' }], undefined, {
fetchImpl,
maxRetries: 2,
sleepImpl: noSleep,
}),
).rejects.toThrow(/503/);
// 1 initial + 2 retries
expect((fetchImpl as unknown as ReturnType<typeof vi.fn>)).toHaveBeenCalledTimes(3);
});
it('does not retry a non-transient 400', async () => {
const fetchImpl = vi.fn(async () => new Response('bad request', { status: 400 })) as unknown as typeof fetch;
await expect(
openaiChat(resolved, [{ role: 'user', content: 'x' }], undefined, { fetchImpl, sleepImpl: noSleep }),
).rejects.toThrow(/400/);
expect((fetchImpl as unknown as ReturnType<typeof vi.fn>)).toHaveBeenCalledTimes(1);
});
});