Assessor_2.0/tests/server/litellm-client.test.ts

59 lines
3.5 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { createLiteLlmClientFromEnvironment, liteLlmCompletionEndpoint, LiteLlmClient } from '../../server/ai/litellm-client';
describe('work LiteLLM client', () => {
it('requires a company HTTPS gateway and pins the completion path', () => {
expect(() => liteLlmCompletionEndpoint('http://work.example/v1')).toThrow(/HTTPS/);
expect(() => liteLlmCompletionEndpoint('https://127.0.0.1:11434/v1')).toThrow(/company-managed/);
expect(() => liteLlmCompletionEndpoint('https://user:secret@work.example/v1')).toThrow(/credentials/);
expect(liteLlmCompletionEndpoint('https://litellm.work.example/v1').href)
.toBe('https://litellm.work.example/v1/chat/completions');
expect(liteLlmCompletionEndpoint('https://litellm.work.example').href)
.toBe('https://litellm.work.example/v1/chat/completions');
});
it('stays disabled unless litellm mode is explicit', () => {
expect(createLiteLlmClientFromEnvironment({})).toBeUndefined();
expect(() => createLiteLlmClientFromEnvironment({ ASSESSOR_AI_MODE: 'local' })).toThrow(/must be litellm/);
});
it('uses bearer authentication, JSON mode, and refuses redirects', async () => {
const fetcher = vi.fn<typeof fetch>(async () => new Response(JSON.stringify({
choices: [{ message: { content: '{"schemaVersion":1}' } }],
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
const client = new LiteLlmClient({
endpoint: new URL('https://litellm.work.example/v1/chat/completions'), model: 'approved-model',
apiKey: 'synthetic-secret', timeoutMs: 1_000, maximumOutputBytes: 10_000, fetcher
});
await expect(client.complete('system', 'input')).resolves.toMatchObject({ value: { schemaVersion: 1 }, usage: { totalTokens: 15 } });
const init = fetcher.mock.calls[0][1]!;
expect(init.redirect).toBe('error');
expect(new Headers(init.headers).get('Authorization')).toBe('Bearer synthetic-secret');
expect(JSON.parse(String(init.body))).toMatchObject({ model: 'approved-model', response_format: { type: 'json_object' } });
});
it('does not persist or echo a provider response body in HTTP errors', async () => {
const client = new LiteLlmClient({
endpoint: new URL('https://litellm.work.example/v1/chat/completions'), model: 'approved-model',
apiKey: 'synthetic-secret', timeoutMs: 1_000, maximumOutputBytes: 10_000,
fetcher: async () => new Response('sensitive provider body', { status: 401 })
});
await expect(client.complete('system', 'input')).rejects.toMatchObject({ code: 'litellm_http_error', message: 'Work LiteLLM returned HTTP 401.' });
});
it('surfaces only structured error type and code, never free-text or echoed input', async () => {
const client = new LiteLlmClient({
endpoint: new URL('https://litellm.work.example/v1/chat/completions'), model: 'approved-model',
apiKey: 'synthetic-secret', timeoutMs: 1_000, maximumOutputBytes: 10_000,
fetcher: async () => new Response(JSON.stringify({
error: { message: 'Authentication Error for key sk-leak and env DC01', type: 'authentication_error', code: 'invalid_api_key', param: 'messages' }
}), { status: 403 })
});
await expect(client.complete('system', 'input')).rejects.toMatchObject({
code: 'litellm_http_error',
message: 'Work LiteLLM returned HTTP 403. (type=authentication_error, code=invalid_api_key)'
});
});
});