spb/llmindex Public
The discriminative, contamination-resistant, fully transparent LLM ranking — updated live.
TypeScript 77.9%
TeX 15.2%
Python 3.7%
SQL 1.4%
JavaScript 1.1%
Shell 0.5%
1/**2 * llmindex.io — OpenRouter client unit tests (mocked fetch)3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * License: Proprietary — © Simon-Pierre Boucher, all rights reserved6 */7import { describe, expect, it, vi } from 'vitest';8import { OpenRouterClient, OpenRouterError } from './client';910const okBody = {11 id: 'gen-1',12 model: 'test/model',13 choices: [{ index: 0, message: { role: 'assistant', content: 'ANSWER: 42' }, finish_reason: 'stop' }],14 usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },15};1617function jsonResponse(body: unknown, status = 200): Response {18 return new Response(JSON.stringify(body), {19 status,20 headers: { 'Content-Type': 'application/json' },21 });22}2324describe('OpenRouterClient', () => {25 it('throws without an API key', () => {26 const prev = process.env.OPENROUTER_API_KEY;27 delete process.env.OPENROUTER_API_KEY;28 expect(() => new OpenRouterClient()).toThrow(OpenRouterError);29 if (prev) process.env.OPENROUTER_API_KEY = prev;30 });3132 it('sends required headers and computes cost from pricing', async () => {33 const fetchFn = vi.fn().mockResolvedValue(jsonResponse(okBody));34 const client = new OpenRouterClient({ apiKey: 'test-key', fetchFn, backoffBaseMs: 1 });35 const result = await client.chat(36 { model: 'test/model', messages: [{ role: 'user', content: 'hi' }], temperature: 0 },37 { promptPerM: 3, completionPerM: 15 },38 );39 const [url, init] = fetchFn.mock.calls[0]!;40 expect(url).toBe('https://openrouter.ai/api/v1/chat/completions');41 expect(init.headers.Authorization).toBe('Bearer test-key');42 expect(init.headers['HTTP-Referer']).toBe('https://www.llmindex.io');43 expect(init.headers['X-Title']).toBe('LLM Index');44 expect(result.text).toBe('ANSWER: 42');45 // 100 tok × $3/1M + 50 tok × $15/1M46 expect(result.costUsd).toBeCloseTo(0.00105, 8);47 expect(result.latencyMs).toBeGreaterThanOrEqual(0);48 expect(result.requestParams.temperature).toBe(0);49 });5051 it('retries on 429 then succeeds', async () => {52 const fetchFn = vi53 .fn()54 .mockResolvedValueOnce(jsonResponse({ error: 'rate limited' }, 429))55 .mockResolvedValueOnce(jsonResponse(okBody));56 const client = new OpenRouterClient({ apiKey: 'test-key', fetchFn, backoffBaseMs: 1 });57 const result = await client.chat({ model: 'test/model', messages: [] });58 expect(fetchFn).toHaveBeenCalledTimes(2);59 expect(result.text).toBe('ANSWER: 42');60 });6162 it('does not retry on 400 and surfaces the error', async () => {63 const fetchFn = vi.fn().mockResolvedValue(jsonResponse({ error: 'bad request' }, 400));64 const client = new OpenRouterClient({ apiKey: 'test-key', fetchFn, backoffBaseMs: 1 });65 await expect(client.chat({ model: 'test/model', messages: [] })).rejects.toThrow(66 OpenRouterError,67 );68 expect(fetchFn).toHaveBeenCalledTimes(1);69 });7071 it('gives up after 5 retries on 500s', async () => {72 const fetchFn = vi.fn().mockResolvedValue(jsonResponse({ error: 'boom' }, 500));73 const client = new OpenRouterClient({ apiKey: 'test-key', fetchFn, backoffBaseMs: 1 });74 await expect(client.chat({ model: 'test/model', messages: [] })).rejects.toThrow(75 /after 6 attempt/,76 );77 expect(fetchFn).toHaveBeenCalledTimes(6);78 });7980 it('lists models', async () => {81 const fetchFn = vi.fn().mockResolvedValue(82 jsonResponse({83 data: [84 {85 id: 'prov/model-x',86 name: 'Model X',87 context_length: 200000,88 pricing: { prompt: '0.000003', completion: '0.000015' },89 },90 ],91 }),92 );93 const client = new OpenRouterClient({ apiKey: 'test-key', fetchFn });94 const models = await client.listModels();95 expect(models[0]?.id).toBe('prov/model-x');96 });97});98