Files
musistudio--claude-code-router/tests/main/usage-store.test.mjs
T
wehub-resource-sync 9f48d4a18c
Docs / Build (push) Has been cancelled
Docs / Deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:37:25 +08:00

185 lines
5.8 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { RequestLogStore } from "../../packages/core/src/observability/request-log-store.ts";
import { UsageStore } from "../../packages/core/src/usage/store.ts";
test("UsageStore aggregates stats in SQLite without loading all events", async () => {
const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-test-"));
try {
const store = new UsageStore(path.join(dir, "usage.sqlite"));
const now = new Date();
const earlier = new Date(now.getTime() - 60_000);
await store.record({
createdAt: earlier.toISOString(),
durationMs: 120,
method: "POST",
model: "alpha-model",
path: "/v1/messages",
provider: "alpha",
requestId: "req-1",
statusCode: 200,
usage: {
cacheReadTokens: 2,
inputTokens: 10,
outputTokens: 5,
totalTokens: 17
}
});
await store.record({
createdAt: now.toISOString(),
durationMs: 80,
method: "POST",
model: "beta-model",
path: "/v1/messages",
provider: "beta",
requestId: "req-2",
statusCode: 500,
usage: {
inputTokens: 4,
outputTokens: 6
}
});
const stats = await store.getStats("30d", { includeProxy: true });
assert.equal(stats.totals.requestCount, 2);
assert.equal(stats.totals.errorCount, 1);
assert.equal(stats.totals.totalTokens, 27);
assert.equal(stats.totals.inputTokens, 14);
assert.equal(stats.totals.outputTokens, 11);
assert.equal(stats.recentRequests.length, 2);
assert.equal(stats.models[0]?.requestCount, 1);
} finally {
rmSync(dir, { force: true, recursive: true });
}
});
test("UsageStore excludes proxy rows by default and includes them on request", async () => {
const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-proxy-test-"));
try {
const store = new UsageStore(path.join(dir, "usage.sqlite"));
const createdAt = new Date().toISOString();
await store.record({
createdAt,
durationMs: 10,
method: "POST",
model: "direct/model-a",
path: "/v1/messages",
requestId: "direct-1",
statusCode: 200,
usage: {
inputTokens: 5,
outputTokens: 7
}
});
await store.record({
createdAt,
durationMs: 10,
method: "POST",
model: "proxy-model",
path: "/v1/messages",
provider: "proxy",
requestId: "proxy-1",
statusCode: 200,
usage: {
inputTokens: 100,
outputTokens: 200
}
});
const defaultStats = await store.getStats("30d");
assert.equal(defaultStats.totals.requestCount, 1);
assert.equal(defaultStats.totals.totalTokens, 12);
assert.equal(defaultStats.providerModels[0]?.provider, "direct");
assert.equal(defaultStats.providerModels[0]?.model, "model-a");
const withProxy = await store.getStats("30d", { includeProxy: true });
assert.equal(withProxy.totals.requestCount, 2);
assert.equal(withProxy.totals.totalTokens, 312);
} finally {
rmSync(dir, { force: true, recursive: true });
}
});
test("UsageStore treats null web RPC usage filters as empty filters", async () => {
const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-null-filter-test-"));
try {
const store = new UsageStore(path.join(dir, "usage.sqlite"));
await store.record({
createdAt: new Date().toISOString(),
durationMs: 10,
method: "POST",
model: "alpha-model",
path: "/v1/messages",
provider: "alpha",
requestId: "req-null-filter",
statusCode: 200,
usage: {
inputTokens: 3,
outputTokens: 4
}
});
const stats = await store.getStats("7d", null);
assert.equal(stats.range, "7d");
assert.equal(stats.totals.requestCount, 1);
const defaultRangeStats = await store.getStats(null, null);
assert.equal(defaultRangeStats.range, "7d");
assert.equal(defaultRangeStats.totals.requestCount, 1);
} finally {
rmSync(dir, { force: true, recursive: true });
}
});
test("UsageStore backfills missing events from request logs", async () => {
const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-request-log-backfill-test-"));
try {
const requestLogDbFile = path.join(dir, "request-logs.sqlite");
const requestLogStore = new RequestLogStore(requestLogDbFile);
const usageStore = new UsageStore(path.join(dir, "usage.sqlite"), { requestLogDbFile });
const createdAt = new Date().toISOString();
await requestLogStore.record({
client: "Claude Code",
completedAt: createdAt,
durationMs: 25,
method: "POST",
path: "/v1/messages",
providerName: "alpha",
requestBody: Buffer.from(JSON.stringify({ model: "alpha-model" })),
requestHeaders: { "content-type": "application/json" },
requestId: "req-backfill-1",
responseBodyText: JSON.stringify({
model: "alpha-model",
usage: {
input_tokens: 12,
output_tokens: 5,
total_tokens: 17
}
}),
responseHeaders: new Headers({ "content-type": "application/json" }),
startedAt: createdAt,
statusCode: 200,
url: "http://127.0.0.1:3456/v1/messages"
});
const stats = await usageStore.getStats("today", { includeProxy: true });
assert.equal(stats.totals.requestCount, 1);
assert.equal(stats.totals.totalTokens, 17);
assert.equal(stats.providerModels[0]?.provider, "alpha");
assert.equal(stats.providerModels[0]?.model, "alpha-model");
const reread = await usageStore.getStats("today", { includeProxy: true });
assert.equal(reread.totals.requestCount, 1);
assert.equal(reread.totals.totalTokens, 17);
} finally {
rmSync(dir, { force: true, recursive: true });
}
});