db logs
This commit is contained in:
+23
-2
@@ -1,5 +1,11 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createRequestLogger, ensureSchema, insertRequest, removeNonChatCompletionRequests } from '../src/db.js';
|
||||
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 () => {
|
||||
@@ -37,12 +43,13 @@ describe('request database', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes requests that are not for the chat completions endpoint', async () => {
|
||||
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',
|
||||
@@ -50,6 +57,20 @@ describe('request database', () => {
|
||||
]]);
|
||||
});
|
||||
|
||||
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 });
|
||||
|
||||
+56
-3
@@ -1,6 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import request from 'supertest';
|
||||
import { allowedModelsFromEnv, createProxyApp, DEFAULT_ALLOWED_MODELS, unlimitedKeysFromEnv } from '../src/proxy.js';
|
||||
import {
|
||||
allowedModelsFromEnv,
|
||||
collectRequestData,
|
||||
createProxyApp,
|
||||
DEFAULT_ALLOWED_MODELS,
|
||||
unlimitedKeysFromEnv,
|
||||
} from '../src/proxy.js';
|
||||
|
||||
const response = (body, options = {}) => new Response(body, {
|
||||
status: options.status ?? 200,
|
||||
@@ -254,7 +261,7 @@ describe('proxy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not collect requests to non-chat-completion endpoints', async () => {
|
||||
it('collects failed requests outside training endpoints but skips successful ones', async () => {
|
||||
const fetchImpl = vi.fn().mockResolvedValue(response('{"object":"list","data":[]}'));
|
||||
const requestLogger = { log: vi.fn() };
|
||||
const app = createProxyApp({ keys: ['key'], fetchImpl, requestLogger });
|
||||
@@ -262,7 +269,53 @@ describe('proxy', () => {
|
||||
await request(app).get('/missing').expect(404);
|
||||
await request(app).get('/oai/v1/models').expect(200);
|
||||
|
||||
expect(requestLogger.log).not.toHaveBeenCalled();
|
||||
expect(requestLogger.log).toHaveBeenCalledOnce();
|
||||
expect(requestLogger.log.mock.calls[0][0]).toMatchObject({
|
||||
method: 'GET', endpoint: '/missing', requestBody: null, responseStatus: 404,
|
||||
trainingData: null,
|
||||
});
|
||||
expect(requestLogger.log.mock.calls[0][0].responseBody).toContain('Cannot GET /missing');
|
||||
});
|
||||
|
||||
it('collects failed training requests without creating export data', async () => {
|
||||
const fetchImpl = vi.fn().mockResolvedValue(response('{"error":"upstream failed"}', { status: 503 }));
|
||||
const requestLogger = { log: vi.fn() };
|
||||
const app = createProxyApp({ keys: ['key'], fetchImpl, requestLogger });
|
||||
|
||||
await request(app).post('/oai/v1/chat/completions').send({
|
||||
model: 'm', messages: [{ role: 'user', content: 'Hi' }],
|
||||
}).expect(503);
|
||||
|
||||
expect(requestLogger.log).toHaveBeenCalledOnce();
|
||||
expect(requestLogger.log.mock.calls[0][0]).toMatchObject({
|
||||
endpoint: '/oai/v1/chat/completions', responseStatus: 503,
|
||||
responseBody: '{"error":"upstream failed"}', trainingData: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('records an interrupted response as a failed request', () => {
|
||||
const requestLogger = { log: vi.fn() };
|
||||
const middleware = collectRequestData(requestLogger);
|
||||
const incoming = {
|
||||
method: 'GET', originalUrl: '/oai/v1/models', headers: {}, body: undefined,
|
||||
ip: '198.51.100.20', socket: {}, get: vi.fn(),
|
||||
};
|
||||
const outgoing = Object.assign(new EventEmitter(), {
|
||||
statusCode: 200,
|
||||
writableFinished: false,
|
||||
write: vi.fn(() => true),
|
||||
end: vi.fn(),
|
||||
getHeaders: vi.fn(() => ({})),
|
||||
});
|
||||
|
||||
middleware(incoming, outgoing, vi.fn());
|
||||
outgoing.emit('close');
|
||||
outgoing.emit('finish');
|
||||
|
||||
expect(requestLogger.log).toHaveBeenCalledOnce();
|
||||
expect(requestLogger.log.mock.calls[0][0]).toMatchObject({
|
||||
endpoint: '/oai/v1/models', responseStatus: 499, trainingData: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('collects OpenAI Responses and Anthropic Messages requests', async () => {
|
||||
|
||||
Reference in New Issue
Block a user