| import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
| import type { Express } from 'express';
|
| import { createApp } from '../../app.js';
|
| import { getDb, initDb } from '../../db/index.js';
|
| import { mintDashboardToken, isGatedApiPath } from '../helpers/auth.js';
|
|
|
| let dashToken = '';
|
|
|
| async function request(app: Express, path: 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, {
|
| headers: isGatedApiPath(path) ? { Authorization: `Bearer ${dashToken}` } : {},
|
| });
|
| const data = await res.json().catch(() => null);
|
| server.close();
|
|
|
| return { status: res.status, body: data };
|
| }
|
|
|
| function insertRequest(createdAt: string) {
|
| const db = getDb();
|
| db.prepare(`
|
| INSERT INTO requests (platform, model_id, status, input_tokens, output_tokens, latency_ms, error, created_at)
|
| VALUES ('test', 'test-model', 'success', 1, 2, 3, NULL, ?)
|
| `).run(createdAt);
|
| }
|
|
|
| function insertTokensRequest(
|
| platform: string,
|
| modelId: string,
|
| status: 'success' | 'error',
|
| inputTokens: number,
|
| outputTokens: number,
|
| createdAt: string,
|
| ) {
|
| const db = getDb();
|
| db.prepare(`
|
| INSERT INTO requests (platform, model_id, status, input_tokens, output_tokens, latency_ms, error, created_at)
|
| VALUES (?, ?, ?, ?, ?, 3, NULL, ?)
|
| `).run(platform, modelId, status, inputTokens, outputTokens, createdAt);
|
| }
|
|
|
| describe('Analytics API', () => {
|
| let app: Express;
|
|
|
| beforeAll(() => {
|
| process.env.ENCRYPTION_KEY = '0'.repeat(64);
|
| initDb(':memory:');
|
| app = createApp();
|
| dashToken = mintDashboardToken();
|
| });
|
|
|
| beforeEach(() => {
|
| getDb().prepare('DELETE FROM requests').run();
|
| vi.useFakeTimers();
|
| vi.setSystemTime(new Date('2026-05-29T12:00:00.000Z'));
|
| });
|
|
|
| afterEach(() => {
|
| vi.useRealTimers();
|
| });
|
|
|
| it('uses a rolling 24-hour window for summary analytics', async () => {
|
| insertRequest('2026-05-28 11:59:59');
|
| insertRequest('2026-05-28 12:00:00');
|
| insertRequest('2026-05-29 11:59:59');
|
|
|
| const { status, body } = await request(app, '/api/analytics/summary?range=24h');
|
|
|
| expect(status).toBe(200);
|
| expect(body.totalRequests).toBe(2);
|
| expect(body.totalInputTokens).toBe(2);
|
| expect(body.totalOutputTokens).toBe(4);
|
| });
|
|
|
| it.each([
|
| ['7d', '2026-05-22 11:59:59', '2026-05-22 12:00:00'],
|
| ['30d', '2026-04-29 11:59:59', '2026-04-29 12:00:00'],
|
| ])('uses a rolling %s window for summary analytics', async (range, outside, boundary) => {
|
| insertRequest(outside);
|
| insertRequest(boundary);
|
| insertRequest('2026-05-29 11:59:59');
|
|
|
| const { status, body } = await request(app, `/api/analytics/summary?range=${range}`);
|
|
|
| expect(status).toBe(200);
|
| expect(body.totalRequests).toBe(2);
|
| });
|
|
|
| it('prices savings at the served model paid-equivalent rate', async () => {
|
|
|
|
|
| insertTokensRequest('groq', 'llama-3.3-70b-versatile', 'success', 10_000_000, 5_000_000, '2026-05-29 11:00:00');
|
|
|
| const { status, body } = await request(app, '/api/analytics/summary?range=24h');
|
|
|
| expect(status).toBe(200);
|
| expect(body.estimatedCostSavings).toBe(2.6);
|
|
|
| expect(body.firstRequestAt).toBe('2026-05-29 11:00:00');
|
| });
|
|
|
| it('falls back to modest default pricing for unmapped models', async () => {
|
|
|
| insertTokensRequest('custom', 'mystery-model', 'success', 10_000_000, 5_000_000, '2026-05-29 11:00:00');
|
|
|
| const { status, body } = await request(app, '/api/analytics/summary?range=24h');
|
|
|
| expect(status).toBe(200);
|
| expect(body.estimatedCostSavings).toBe(6);
|
| });
|
|
|
| it('excludes failed requests from savings', async () => {
|
| insertTokensRequest('groq', 'llama-3.3-70b-versatile', 'error', 10_000_000, 0, '2026-05-29 11:00:00');
|
|
|
| const { status, body } = await request(app, '/api/analytics/summary?range=24h');
|
|
|
| expect(status).toBe(200);
|
| expect(body.estimatedCostSavings).toBe(0);
|
| });
|
|
|
| it('returns per-model estimated cost in the by-model breakdown', async () => {
|
| insertTokensRequest('groq', 'llama-3.3-70b-versatile', 'success', 10_000_000, 5_000_000, '2026-05-29 11:00:00');
|
|
|
| const { status, body } = await request(app, '/api/analytics/by-model?range=24h');
|
|
|
| expect(status).toBe(200);
|
| expect(body[0].estimatedCost).toBe(2.6);
|
| });
|
|
|
| describe('pinned vs auto tracking', () => {
|
| function insertPinnedRequest(modelId: string, requestedModel: string | null, createdAt: string) {
|
| getDb().prepare(`
|
| INSERT INTO requests (platform, model_id, requested_model, status, input_tokens, output_tokens, latency_ms, error, created_at)
|
| VALUES ('test', ?, ?, 'success', 1, 2, 3, NULL, ?)
|
| `).run(modelId, requestedModel, createdAt);
|
| }
|
|
|
| it('summary splits pinned, honored, and auto requests', async () => {
|
| insertPinnedRequest('model-a', 'model-a', '2026-05-29 11:00:00');
|
| insertPinnedRequest('model-b', 'model-a', '2026-05-29 11:01:00');
|
| insertPinnedRequest('model-b', null, '2026-05-29 11:02:00');
|
|
|
| const { status, body } = await request(app, '/api/analytics/summary?range=24h');
|
|
|
| expect(status).toBe(200);
|
| expect(body.totalRequests).toBe(3);
|
| expect(body.pinnedRequests).toBe(2);
|
| expect(body.pinHonoredRequests).toBe(1);
|
| });
|
|
|
| it('by-model counts only requests the model served because it was pinned', async () => {
|
| insertPinnedRequest('model-a', 'model-a', '2026-05-29 11:00:00');
|
| insertPinnedRequest('model-a', null, '2026-05-29 11:01:00');
|
| insertPinnedRequest('model-a', 'model-x', '2026-05-29 11:02:00');
|
|
|
| const { status, body } = await request(app, '/api/analytics/by-model?range=24h');
|
|
|
| expect(status).toBe(200);
|
| const row = body.find((r: any) => r.modelId === 'model-a');
|
| expect(row.requests).toBe(3);
|
| expect(row.pinnedRequests).toBe(1);
|
| });
|
| });
|
| });
|
|
|