Files
OmniRoute/tests/unit/usage-account-analytics-route.test.ts
Xiangzhe 74204911c7 fix(usage): harden account identity reconciliation (#7843)
* fix(usage): harden account identity reconciliation

* fix(db): renumber codex strong-identity migration 128 -> 129

release/v3.8.49 tip took slot 128 via #7839 (auto_candidate_overrides) after
this branch forked; renumber the new migration and its test references.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-20 15:56:47 -03:00

391 lines
13 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-account-analytics-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = "test-account-analytics-secret";
const core = await import("../../src/lib/db/core.ts");
const localDb = await import("../../src/lib/localDb.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
const { resolveUsageAccountIdentity } = await import("../../src/lib/usage/accountIdentity.ts");
const analyticsRoute = await import("../../src/app/api/usage/analytics/route.ts");
function makeRequest() {
return new Request("http://localhost/api/usage/analytics?startDate=2026-01-01T00:00:00.000Z");
}
async function readAnalytics() {
const response = await analyticsRoute.GET(makeRequest());
assert.equal(response.status, 200);
return (await response.json()) as {
summary: Record<string, unknown>;
byAccount: Array<Record<string, unknown>>;
};
}
async function readAccounts() {
return (await readAnalytics()).byAccount;
}
test.beforeEach(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
usageHistory.clearPendingRequests();
});
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("Codex account grouping follows workspace and user identity, not email", async () => {
await localDb.updatePricing({ codex: { "gpt-5.5": { input: 1, output: 2 } } });
const specs = [
{ workspaceId: "workspace-a", chatgptUserId: "user-a", email: "old@example.com" },
{ workspaceId: "workspace-a", chatgptUserId: "user-b", email: "old@example.com" },
{ workspaceId: "workspace-b", chatgptUserId: "user-a", email: "old@example.com" },
];
for (const [index, spec] of specs.entries()) {
const connection = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: spec.email,
displayName: `Account ${index + 1}`,
providerSpecificData: spec,
});
await usageHistory.saveRequestUsage({
provider: "codex",
model: "gpt-5.5",
connectionId: connection.id as string,
tokens: { input: 1000, output: 500 },
timestamp: `2026-01-0${index + 1}T00:00:00.000Z`,
});
}
const sameUser = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "new@example.com",
displayName: "Account 1 renamed",
providerSpecificData: { workspaceId: "workspace-a", chatgptUserId: "user-a" },
});
await usageHistory.saveRequestUsage({
provider: "codex",
model: "gpt-5.5",
connectionId: sameUser.id as string,
tokens: { input: 1000, output: 500 },
timestamp: "2026-01-04T00:00:00.000Z",
});
const accounts = await readAccounts();
assert.equal(accounts.length, 3);
assert.deepEqual(accounts.map((account) => [account.account, account.requests]).sort(), [
["Account 1 renamed", 2],
["Account 2", 1],
["Account 3", 1],
]);
assert.equal(
accounts.reduce((sum, account) => sum + Number(account.cost), 0),
0.008
);
});
test("updating an established Codex user or workspace does not reattribute earlier usage", async () => {
const changes = [
{
name: "user boundary",
before: { workspaceId: "workspace-user", chatgptUserId: "user-a" },
after: { workspaceId: "workspace-user", chatgptUserId: "user-b" },
},
{
name: "workspace boundary",
before: { workspaceId: "workspace-a", chatgptUserId: "stable-user" },
after: { workspaceId: "workspace-b", chatgptUserId: "stable-user" },
},
];
for (const [index, change] of changes.entries()) {
const connection = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
displayName: `${change.name} before`,
providerSpecificData: change.before,
});
await usageHistory.saveRequestUsage({
provider: "codex",
model: "gpt-5.5",
connectionId: connection.id as string,
tokens: { input: 10, output: 5 },
timestamp: `2026-02-0${index + 1}T00:00:00.000Z`,
});
await providersDb.updateProviderConnection(connection.id as string, {
displayName: `${change.name} after`,
providerSpecificData: change.after,
});
await usageHistory.saveRequestUsage({
provider: "codex",
model: "gpt-5.5",
connectionId: connection.id as string,
tokens: { input: 20, output: 10 },
timestamp: `2026-02-1${index + 1}T00:00:00.000Z`,
});
}
const accounts = await readAccounts();
assert.deepEqual(
accounts.map((account) => [account.account, account.requests, account.totalTokens]).sort(),
[
["user boundary after", 1, 30],
["user boundary before", 1, 15],
["workspace boundary after", 1, 30],
["workspace boundary before", 1, 15],
]
);
});
test("deleting and recreating the same Codex account preserves one historical account", async () => {
await localDb.updatePricing({ codex: { "gpt-5.5": { input: 1, output: 2 } } });
const identity = {
workspaceId: "workspace-internal-recreated",
chatgptUserId: "user-internal-recreated",
};
const original = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "member@example.com",
displayName: "Production Codex <member@example.com>",
providerSpecificData: identity,
});
await usageHistory.saveRequestUsage({
provider: "codex",
model: "gpt-5.5",
connectionId: original.id as string,
tokens: { input: 1000, output: 500 },
timestamp: "2026-03-01T00:00:00.000Z",
});
assert.equal(await providersDb.deleteProviderConnection(original.id as string), true);
const recreated = await providersDb.createProviderConnection({
provider: "codex",
authType: "oauth",
email: "member@example.com",
providerSpecificData: identity,
});
assert.notEqual(recreated.id, original.id);
await usageHistory.saveRequestUsage({
provider: "codex",
model: "gpt-5.5",
connectionId: recreated.id as string,
tokens: { input: 1000, output: 500 },
timestamp: "2026-03-02T00:00:00.000Z",
});
const analytics = await readAnalytics();
assert.deepEqual(
analytics.byAccount.map((account) => ({
account: account.account,
requests: account.requests,
promptTokens: account.promptTokens,
completionTokens: account.completionTokens,
totalTokens: account.totalTokens,
cost: account.cost,
})),
[
{
account: "Production Codex <member@example.com>",
requests: 2,
promptTokens: 2000,
completionTokens: 1000,
totalTokens: 3000,
cost: 0.004,
},
]
);
const serialized = JSON.stringify(analytics);
const { accountKey } = resolveUsageAccountIdentity({
id: recreated.id,
provider: "codex",
authType: "oauth",
email: "member@example.com",
providerSpecificData: identity,
});
for (const privateValue of [
accountKey,
identity.workspaceId,
identity.chatgptUserId,
recreated.id as string,
]) {
assert.equal(serialized.includes(privateValue), false);
}
});
test("account cost and usage share the same trimmed legacy connection fallback", async () => {
await localDb.updatePricing({ codex: { "gpt-5.5": { input: 1, output: 2 } } });
const timestamp = new Date().toISOString();
core
.getDbInstance()
.prepare(
`INSERT INTO usage_history
(provider, model, connection_id, account_label, account_label_priority,
tokens_input, tokens_output, success, timestamp)
VALUES ('codex', 'gpt-5.5', ' legacy-id ', 'Legacy Codex', 4,
1000, 500, 1, ?)`
)
.run(timestamp);
const analytics = await readAnalytics();
assert.equal(analytics.byAccount.length, 1);
assert.deepEqual(
{
account: analytics.byAccount[0].account,
requests: analytics.byAccount[0].requests,
totalTokens: analytics.byAccount[0].totalTokens,
cost: analytics.byAccount[0].cost,
},
{ account: "Legacy Codex", requests: 1, totalTokens: 1500, cost: 0.002 }
);
assert.equal(analytics.summary.totalCost, analytics.byAccount[0].cost);
});
test("analytics prefers the highest-priority historical label and reports blank orphan IDs as unknown", async () => {
await localDb.updatePricing({ codex: { "gpt-5.5": { input: 1, output: 2 } } });
const db = core.getDbInstance();
const insert = db.prepare(`INSERT INTO usage_history
(provider, model, connection_id, account_key, account_label, account_label_priority,
tokens_input, tokens_output, success, timestamp)
VALUES (?, 'gpt-5.5', ?, ?, ?, ?, 10, 5, 1, ?)`);
insert.run(
"codex",
"old-uuid",
"stable-account",
" Production Codex ",
4,
"2026-01-01T00:00:00.000Z"
);
insert.run("codex", "new-uuid", "stable-account", " ", 5, "2026-01-02T00:00:00.000Z");
insert.run("codex", " ", null, null, 0, "2026-01-03T00:00:00.000Z");
const analytics = await readAnalytics();
assert.deepEqual(
analytics.byAccount.map((account) => [
account.account,
account.requests,
account.totalTokens,
account.cost,
]),
[
["Production Codex", 2, 30, 0.00004],
["unknown", 1, 15, 0.00002],
]
);
});
test("generic provider and Codex workspace identities stay distinct under a shared email", async () => {
const accountSpecs = [
{ provider: "codex", providerSpecificData: { workspaceId: "workspace-a" } },
{ provider: "codex", providerSpecificData: { workspaceId: "workspace-b" } },
{ provider: "openai", providerSpecificData: {} },
];
for (const [index, spec] of accountSpecs.entries()) {
const connection = await providersDb.createProviderConnection({
provider: spec.provider,
authType: "oauth",
email: "shared@example.com",
accessToken: `secret-${index}`,
providerSpecificData: spec.providerSpecificData,
});
await usageHistory.saveRequestUsage({
provider: spec.provider,
model: spec.provider === "codex" ? "gpt-5.5" : "gpt-4o",
connectionId: connection.id as string,
tokens: { input: 100 + index, output: 50 + index },
timestamp: `2026-01-0${index + 1}T00:00:00.000Z`,
});
}
const accounts = await readAccounts();
// Three separate accounts share one email label; grouping must not collapse
// them because workspace and provider identities differ.
assert.equal(accounts.length, 3);
assert.deepEqual(accounts.map((account) => account.account).sort(), [
"shared@example.com",
"shared@example.com",
"shared@example.com",
]);
assert.deepEqual(accounts.map((account) => account.requests).sort(), [1, 1, 1]);
});
test("a nonblank orphaned connection ID stays a truthful fallback label", async () => {
core
.getDbInstance()
.prepare(
`INSERT INTO usage_history (provider, model, connection_id, tokens_input, tokens_output, success, latency_ms, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
)
.run("codex", "gpt-5.5", "deleted-legacy-uuid", 10, 5, 1, 25, "2026-01-01T00:00:00.000Z");
const accounts = await readAccounts();
assert.equal(accounts.length, 1);
assert.equal(accounts[0].account, "deleted-legacy-uuid");
});
test("the newest label wins for one account at equal priority", async () => {
const db = core.getDbInstance();
const accountKey = resolveUsageAccountIdentity({
id: "new-uuid",
provider: "codex",
authType: "oauth",
email: "member@example.com",
providerSpecificData: { chatgptUserId: "user-a" },
}).accountKey;
const now = Date.now();
const insert = db.prepare(
`INSERT INTO usage_history
(provider, model, connection_id, account_key, account_label, account_label_priority,
tokens_input, tokens_output, success, latency_ms, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
);
insert.run(
"codex",
"gpt-5.5",
"old-uuid",
accountKey,
"Zulu before rename",
4,
10,
5,
1,
20,
new Date(now - 60_000).toISOString()
);
insert.run(
"codex",
"gpt-5.5",
"new-uuid",
accountKey,
"Alpha after rename",
4,
20,
10,
1,
30,
new Date(now).toISOString()
);
const analytics = await readAnalytics();
assert.equal(analytics.summary.uniqueAccounts, 1);
assert.equal(analytics.byAccount.length, 1);
assert.equal(analytics.byAccount[0].account, "Alpha after rename");
assert.equal(analytics.byAccount[0].requests, 2);
});