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(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)' }); }); });