openclaw/extensions/anthropic/usage.test.ts
Peter Steinberger 827402243d
feat: add Anthropic and OpenAI cost history (#100672)
* feat(providers): add admin cost history

* fix(ui): sync cron raw-copy baseline

* fix(usage): harden provider cost credentials

* fix(ui): refresh raw-copy baseline

* docs: refresh documentation map

* fix(ci): sync provider usage baselines

* fix(ui): hide zero-cost history bars

* docs(changelog): defer provider cost note
2026-07-06 12:06:55 +01:00

135 lines
4.4 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { fetchAnthropicAdminUsage, resolveAnthropicUsageAuth } from "./usage.js";
function requestUrl(input: string | URL | Request): URL {
return new URL(input instanceof Request ? input.url : input);
}
describe("Anthropic provider usage", () => {
it("aggregates provider-reported costs, cache tokens, models, and categories", async () => {
const fetchFn = vi.fn(async (input: string | URL | Request, _init?: RequestInit) => {
const url = requestUrl(input);
if (url.pathname.endsWith("/organizations/cost_report")) {
return new Response(
JSON.stringify({
data: [
{
starting_at: "2026-07-06T00:00:00Z",
ending_at: "2026-07-07T00:00:00Z",
results: [{ amount: "1234", currency: "USD", description: "Claude API" }],
},
],
has_more: false,
}),
{ status: 200 },
);
}
return new Response(
JSON.stringify({
data: [
{
starting_at: "2026-07-06T00:00:00Z",
ending_at: "2026-07-07T00:00:00Z",
results: [
{
uncached_input_tokens: 1_000,
cache_creation: {
ephemeral_1h_input_tokens: 100,
ephemeral_5m_input_tokens: 50,
},
cache_read_input_tokens: 300,
output_tokens: 250,
model: "claude-opus-4-8",
},
],
},
],
has_more: false,
}),
{ status: 200 },
);
});
const result = await fetchAnthropicAdminUsage({
apiKey: "sk-ant-admin-test",
timeoutMs: 5_000,
fetchFn: fetchFn as typeof fetch,
now: Date.parse("2026-07-06T12:00:00Z"),
periodDays: 2,
});
expect(result).toMatchObject({
provider: "anthropic",
plan: "Admin API",
billing: [{ type: "spend", amount: 12.34, unit: "USD", period: "2d" }],
costHistory: {
unit: "USD",
periodDays: 2,
daily: [
{
date: "2026-07-06",
amount: 12.34,
inputTokens: 1_000,
cacheWriteTokens: 150,
cacheReadTokens: 300,
outputTokens: 250,
totalTokens: 1_700,
},
],
models: [{ name: "claude-opus-4-8", totalTokens: 1_700 }],
categories: [{ name: "Claude API", amount: 12.34 }],
},
});
for (const [input, init] of fetchFn.mock.calls) {
const url = requestUrl(input);
expect(url.searchParams.get("bucket_width")).toBe("1d");
expect((init as RequestInit).headers).toMatchObject({
"anthropic-version": "2023-06-01",
"x-api-key": "sk-ant-admin-test",
});
}
});
it("uses explicit Admin API credentials before Claude OAuth", async () => {
const result = await resolveAnthropicUsageAuth({
config: {},
env: { ANTHROPIC_ADMIN_API_KEY: "sk-ant-admin-explicit" },
provider: "anthropic",
resolveApiKeyFromConfigAndStore: () => "sk-ant-oat01-fallback",
resolveOAuthToken: async () => ({ token: "oauth-token" }),
});
expect(result).toEqual({
token: 'openclaw:anthropic-admin:v1:{"token":"sk-ant-admin-explicit"}',
});
});
it("auto-detects an Admin API key stored in the Anthropic provider profile", async () => {
const result = await resolveAnthropicUsageAuth({
config: {},
env: {},
provider: "anthropic",
resolveApiKeyFromConfigAndStore: () => "sk-ant-admin-profile",
resolveOAuthToken: async () => null,
});
expect(result).toEqual({
token: 'openclaw:anthropic-admin:v1:{"token":"sk-ant-admin-profile"}',
});
});
it("prefers a stored Admin API key when normal API and OAuth credentials coexist", async () => {
const result = await resolveAnthropicUsageAuth({
config: {},
env: {},
provider: "anthropic",
resolveApiKeyFromConfigAndStore: () => "sk-ant-api03-inference",
resolveApiKeyCandidatesFromConfigAndStore: async () => [
"sk-ant-api03-inference",
"sk-ant-admin-billing",
],
resolveOAuthToken: async () => ({ token: "oauth-token" }),
});
expect(result).toEqual({
token: 'openclaw:anthropic-admin:v1:{"token":"sk-ant-admin-billing"}',
});
});
});