FreeLLMAPI / server /src /__tests__ /routes /proxy-auto-model.test.ts
Nryn215's picture
Upload folder using huggingface_hub
077865a verified
Raw
History Blame Contribute Delete
4.41 kB
import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest';
import type { Express } from 'express';
import { createApp } from '../../app.js';
import { initDb, getDb, getUnifiedApiKey } from '../../db/index.js';
import { mintDashboardToken, isGatedApiPath } from '../helpers/auth.js';
let dashToken = '';
async function request(app: Express, method: string, path: string, body?: any, headers: Record<string, string> = {}) {
const server = app.listen(0);
const addr = server.address() as any;
const url = `http://127.0.0.1:${addr.port}${path}`;
const res = await fetch(url, {
method,
headers: { ...(body ? { 'Content-Type': 'application/json' } : {}), ...(isGatedApiPath(path) && !('Authorization' in headers) ? { Authorization: `Bearer ${dashToken}` } : {}), ...headers },
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.text();
server.close();
let json: any = null;
try { json = JSON.parse(data); } catch {}
return { status: res.status, body: json, headers: res.headers, raw: data };
}
function authHeaders() {
return { Authorization: `Bearer ${getUnifiedApiKey()}` };
}
describe('Virtual "auto" model', () => {
let app: Express;
beforeAll(() => {
process.env.ENCRYPTION_KEY = '0'.repeat(64);
initDb(':memory:');
app = createApp();
dashToken = mintDashboardToken();
});
beforeEach(async () => {
const db = getDb();
db.prepare('DELETE FROM api_keys').run();
db.prepare('DELETE FROM requests').run();
const addKey = await request(app, 'POST', '/api/keys', {
platform: 'groq',
key: 'gsk_auto_model_test',
label: 'auto-model',
});
expect(addKey.status).toBe(201);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('lists "auto" as the first /v1/models entry', async () => {
const { status, body } = await request(app, 'GET', '/v1/models', undefined, authHeaders());
expect(status).toBe(200);
expect(body.object).toBe('list');
expect(body.data[0]).toMatchObject({
id: 'auto',
object: 'model',
owned_by: 'freellmapi',
});
// Real catalog models still follow.
expect(body.data.length).toBeGreaterThan(1);
});
it('fails when authentication is missing or wrong', async () => {
const { status: status1 } = await request(app, 'GET', '/v1/models');
expect(status1).toBe(401);
const { status: status2 } = await request(app, 'GET', '/v1/models', undefined, { Authorization: 'Bearer wrongkey' });
expect(status2).toBe(401);
});
it('returns unique model ids from /v1/models', async () => {
const { status, body } = await request(app, 'GET', '/v1/models', undefined, authHeaders());
expect(status).toBe(200);
const ids = body.data.map((model: { id: string }) => model.id);
expect(new Set(ids).size).toBe(ids.length);
});
it('treats model:"auto" as auto-route instead of a 400', async () => {
const origFetch = global.fetch;
vi.spyOn(global, 'fetch').mockImplementation(async (url, init) => {
const urlStr = typeof url === 'string' ? url : url.toString();
if (urlStr.includes('api.groq.com/openai/v1/chat/completions')) {
return {
ok: true,
json: () => Promise.resolve({
id: 'chatcmpl-auto',
object: 'chat.completion',
created: 123,
model: 'openai/gpt-oss-120b',
choices: [{
index: 0,
message: { role: 'assistant', content: 'routed via auto' },
finish_reason: 'stop',
}],
usage: { prompt_tokens: 5, completion_tokens: 3, total_tokens: 8 },
}),
} as any;
}
return origFetch(url, init);
});
const { status, body } = await request(app, 'POST', '/v1/chat/completions', {
model: 'auto',
messages: [{ role: 'user', content: 'hello' }],
}, authHeaders());
expect(status).toBe(200);
expect(body.choices[0].message.content).toBe('routed via auto');
});
it('still rejects an unknown model with model_not_found', async () => {
const { status, body } = await request(app, 'POST', '/v1/chat/completions', {
model: 'definitely-not-a-real-model',
messages: [{ role: 'user', content: 'hello' }],
}, authHeaders());
expect(status).toBe(400);
expect(body.error.code).toBe('model_not_found');
});
});