59 lines
3.5 KiB
TypeScript
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)'
|
|
});
|
|
});
|
|
});
|