Files
OmniRoute/tests/unit/usage-analytics-route.test.ts
Antigravity Assistant 9d9d1774c7 feat(analytics): add cost-based usage insights and activity streaks
Expand usage analytics to report total cost, per-series cost totals,
API key counts, and current activity streaks using pricing-aware token
calculations.

Also make probe-failed database recovery choose the newest backup by
its embedded timestamp instead of filesystem mtime so auto-restore
selects the intended snapshot reliably.
2026-04-30 13:47:23 -03:00

210 lines
7.4 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-usage-analytics-route-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const localDb = await import("../../src/lib/localDb.ts");
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
const analyticsRoute = await import("../../src/app/api/usage/analytics/route.ts");
const clearPendingRequests = usageHistory.clearPendingRequests;
const EXPECTED_TOTAL_COST = 0.020925;
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
clearPendingRequests();
}
async function seedAnalyticsData() {
const db = core.getDbInstance();
const now = new Date();
for (let i = 0; i < 20; i++) {
const timestamp = new Date(now.getTime() - i * 60 * 60 * 1000).toISOString();
db.prepare(
`INSERT INTO usage_history (provider, model, connection_id, api_key_id, api_key_name, tokens_input, tokens_output, success, latency_ms, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run(
i % 2 === 0 ? "openai" : "anthropic",
i % 2 === 0 ? "gpt-4o" : "claude-sonnet",
"test-conn",
"test-key",
"Primary Key",
100 + i,
50 + i,
1,
200 + i * 10,
timestamp
);
}
db.prepare(
`INSERT INTO call_logs (provider, model, requested_model, connection_id, timestamp)
VALUES (?, ?, ?, ?, ?)`
).run("openai", "gpt-4o", "gpt-4o-mini", "test-conn", new Date().toISOString());
}
function makeRequest(url: string) {
return new Request(url, { method: "GET" });
}
function assertClose(actual: number, expected: number, epsilon = 0.000001) {
assert.ok(
Math.abs(actual - expected) <= epsilon,
`expected ${actual} to be within ${epsilon} of ${expected}`
);
}
test.beforeEach(async () => {
await resetStorage();
await localDb.updatePricing({
openai: { "gpt-4o": { input: 2.5, output: 10 } },
anthropic: { "claude-sonnet": { input: 3, output: 15 } },
});
});
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("GET /api/usage/analytics returns summary with aggregated metrics", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(body.summary.totalRequests, 20);
assert.equal(body.summary.uniqueModels, 2);
assert.equal(body.summary.uniqueAccounts, 1);
assert.equal(body.summary.uniqueApiKeys, 1);
assert.ok(body.summary.totalTokens > 0);
assert.ok(body.summary.avgLatencyMs > 0);
assertClose(body.summary.totalCost, EXPECTED_TOTAL_COST);
assert.ok(body.summary.streak > 0);
});
test("GET /api/usage/analytics includes dailyTrend array with cost data", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(Array.isArray(body.dailyTrend));
assert.ok(body.dailyTrend.length > 0);
assert.ok(body.dailyTrend.every((row) => typeof row.cost === "number"));
const dailyCostTotal = body.dailyTrend.reduce((sum, row) => sum + row.cost, 0);
assertClose(dailyCostTotal, body.summary.totalCost);
});
test("GET /api/usage/analytics includes byModel array with cost calculations", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(Array.isArray(body.byModel));
assert.ok(body.byModel.length > 0);
const gptEntry = body.byModel.find((m) => m.model === "4o" && m.provider === "openai");
assert.ok(gptEntry);
assert.ok(typeof gptEntry.cost === "number");
assert.ok(gptEntry.cost > 0);
});
test("GET /api/usage/analytics filters by range parameter", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(
makeRequest("http://localhost/api/usage/analytics?range=1d")
);
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(body.range, "1d");
});
test("GET /api/usage/analytics includes byProvider array with cost data", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(Array.isArray(body.byProvider));
assert.ok(body.byProvider.length > 0);
assert.ok(body.byProvider.every((row) => typeof row.cost === "number"));
const providerCostTotal = body.byProvider.reduce((sum, row) => sum + row.cost, 0);
assertClose(providerCostTotal, body.summary.totalCost);
});
test("GET /api/usage/analytics includes byAccount array with cost data", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(Array.isArray(body.byAccount));
assert.ok(body.byAccount.length > 0);
assert.equal(body.byAccount[0].account, "test-conn");
assert.equal(typeof body.byAccount[0].cost, "number");
assertClose(body.byAccount[0].cost, body.summary.totalCost);
});
test("GET /api/usage/analytics includes cost by API key", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(Array.isArray(body.byApiKey));
assert.equal(body.byApiKey.length, 1);
assert.equal(body.byApiKey[0].apiKeyId, "test-key");
assert.equal(body.byApiKey[0].apiKeyName, "Primary Key");
assertClose(body.byApiKey[0].cost, body.summary.totalCost);
});
test("GET /api/usage/analytics returns weeklyPattern for the costs dashboard", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(Array.isArray(body.weeklyPattern));
assert.equal(body.weeklyPattern.length, 7);
assert.deepEqual(
body.weeklyPattern.map((row) => row.day),
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
);
assert.ok(body.weeklyPattern.some((row) => row.totalTokens > 0 && row.avgTokens > 0));
});
test("GET /api/usage/analytics includes activityMap for heatmap", async () => {
await seedAnalyticsData();
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(typeof body.activityMap === "object");
assert.ok(Object.keys(body.activityMap).length > 0);
});
test("GET /api/usage/analytics returns 500 on database errors", async () => {
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assert.ok(body.summary.totalRequests === 0);
});