import { describe, expect, it, vi } from 'vitest'; import { createRequestLogger, ensureSchema, insertRequest, removeNonChatCompletionRequests, selectTrainingRequests, } from '../src/db.js'; describe('request database', () => { it('creates the requests table, compatible columns, and recent-request index', async () => { const pool = { query: vi.fn().mockResolvedValue({}) }; await ensureSchema(pool); expect(pool.query).toHaveBeenCalledTimes(3); expect(pool.query.mock.calls[0][0]).toContain('CREATE TABLE IF NOT EXISTS requests'); expect(pool.query.mock.calls[1][0]).toContain('ADD COLUMN IF NOT EXISTS request_headers'); expect(pool.query.mock.calls[1][0]).toContain('ADD COLUMN IF NOT EXISTS duration_ms'); expect(pool.query.mock.calls[1][0]).toContain('ADD COLUMN IF NOT EXISTS training_data'); expect(pool.query.mock.calls[2][0]).toContain('created_at DESC, id DESC'); }); it('stores request and response data as one row', async () => { const pool = { query: vi.fn().mockResolvedValue({}) }; const entry = { method: 'POST', endpoint: '/oai/v1/responses', requestHeaders: { host: 'proxy.test' }, requestBody: { model: 'm', input: 'Hi' }, responseHeaders: { 'content-type': 'application/json' }, responseBody: '{"id":"r1"}', responseStatus: 200, durationMs: 123.5, model: 'm', clientIp: '198.51.100.2', stream: false, trainingData: { messages: [{ role: 'user', content: 'Hi' }, { role: 'assistant', content: 'Hello' }] }, }; await insertRequest(pool, entry); expect(pool.query).toHaveBeenCalledOnce(); expect(pool.query.mock.calls[0][0]).toContain('INSERT INTO requests'); expect(pool.query.mock.calls[0][1]).toEqual([ 'POST', '/oai/v1/responses', '{"host":"proxy.test"}', '{"model":"m","input":"Hi"}', '{"content-type":"application/json"}', '{"id":"r1"}', 200, 123.5, 'm', '198.51.100.2', false, '{"messages":[{"role":"user","content":"Hi"},{"role":"assistant","content":"Hello"}]}', ]); }); it('removes successful non-training requests but retains failures', async () => { const pool = { query: vi.fn().mockResolvedValue({ rowCount: 4 }) }; await expect(removeNonChatCompletionRequests(pool)).resolves.toBe(4); expect(pool.query).toHaveBeenCalledOnce(); expect(pool.query.mock.calls[0][0]).toContain('response_status BETWEEN 200 AND 299'); expect(pool.query.mock.calls[0][0]).toContain("split_part(endpoint, '?', 1) <> ALL($1::text[])"); expect(pool.query.mock.calls[0][1]).toEqual([[ '/oai/v1/chat/completions', '/oai/v1/responses', '/ant/v1/messages', '/ant/v1/v1/messages', ]]); }); it('selects only successful training requests for export', async () => { const pool = { query: vi.fn().mockResolvedValue({ rows: [] }) }; await selectTrainingRequests(pool, { limit: 250 }); const [sql, params] = pool.query.mock.calls[0]; expect(sql).toContain('response_status BETWEEN 200 AND 299'); expect(sql).toContain("split_part(endpoint, '?', 1) = ANY($1::text[])"); expect(params).toEqual([[ '/oai/v1/chat/completions', '/oai/v1/responses', '/ant/v1/messages', '/ant/v1/v1/messages', ], 250]); }); it('reports storage failures without rejecting request handling', async () => { const onError = vi.fn(); const logger = createRequestLogger({ query: vi.fn().mockRejectedValue(new Error('database offline')) }, { onError }); await expect(logger.log({ method: 'GET', endpoint: '/', responseStatus: 200 })).resolves.toBeUndefined(); expect(onError).toHaveBeenCalledWith('Failed to store request: database offline'); }); });