import { USAGE_CONTRACT_VERSION, type EnvironmentId, type UsageBucket, type UsageDay, type UsageProviderKind, type UsageSummary, } from "@t3tools/contracts"; import { describe, expect, it } from "vite-plus/test"; import { isModelCostUnknown, mergeUsage, type EnvironmentUsage } from "./usageMerge.ts"; function bucket(overrides: Partial = {}): UsageBucket { return { day: "2026-08-07" as UsageDay, provider: "claude", model: "claude-fable-5", totals: { uncachedInputTokens: 100, cachedInputTokens: 1000, cacheCreationTokens: 10, outputTokens: 50, reasoningTokens: 0, }, costUsd: 10, cacheSavingsUsd: 2, costSource: "modelPriced", records: 5, unpricedRecords: 0, sessions: 1, ...overrides, }; } function summary( buckets: readonly UsageBucket[], sources: readonly { provider: UsageProviderKind; hostId: string; homePath: string; volumeId?: string; distinctSessions?: number; }[], contractVersion: number = USAGE_CONTRACT_VERSION, ): UsageSummary { return { contractVersion, readAt: "2026-08-07T00:00:00.000Z", timeZone: "UTC", sinceDay: "2026-08-01" as UsageDay, untilDay: "2026-08-31" as UsageDay, buckets, sources: sources.map((source) => ({ fingerprint: { hostId: source.hostId, provider: source.provider, resolvedHomePath: source.homePath, volumeId: source.volumeId ?? `vol-${source.hostId}`, }, status: "ok" as const, scannedFiles: 1, skippedFiles: 0, malformedRecords: 0, distinctSessions: source.distinctSessions ?? 1, message: null, })), pricing: { status: "fresh", source: "litellm", fetchedAt: null, knownModels: 10 }, scanDurationMs: 1, }; } function environment(id: string, usageSummary: UsageSummary): EnvironmentUsage { return { environmentId: id as EnvironmentId, label: id, summary: usageSummary }; } describe("mergeUsage", () => { it("sums environments that read different transcript directories", () => { const merged = mergeUsage( [ environment( "env-a", summary([bucket()], [{ provider: "claude", hostId: "mac", homePath: "/a/.claude" }]), ), environment( "env-b", summary([bucket()], [{ provider: "claude", hostId: "linux", homePath: "/b/.claude" }]), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.costUsd).toBe(20); expect(merged.records).toBe(10); expect(merged.duplicateSources).toHaveLength(0); }); it("counts a shared transcript directory once", () => { // Two worktree servers on one machine resolve the same provider home. const shared = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" }; const merged = mergeUsage( [ environment("env-a", summary([bucket()], [shared])), environment("env-b", summary([bucket()], [shared])), ], USAGE_CONTRACT_VERSION, ); expect(merged.costUsd).toBe(10); expect(merged.records).toBe(5); expect(merged.sessions).toBe(1); expect(merged.duplicateSources).toHaveLength(1); expect(merged.contributingEnvironments).toEqual(["env-a"]); }); it("drops only the duplicated provider, keeping the environment's other one", () => { const sharedClaude = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude", }; const merged = mergeUsage( [ environment("env-a", summary([bucket()], [sharedClaude])), environment( "env-b", summary( [bucket(), bucket({ provider: "codex", model: "gpt-5.6-sol", costUsd: 4 })], [sharedClaude, { provider: "codex", hostId: "mac", homePath: "/home/theo/.codex" }], ), ), ], USAGE_CONTRACT_VERSION, ); // env-b's claude bucket is dropped, its codex bucket survives. expect(merged.costUsd).toBe(14); expect(merged.providers.map((provider) => provider.provider).sort()).toEqual([ "claude", "codex", ]); expect(merged.sessions).toBe(2); expect( Object.fromEntries( merged.providers.map((provider) => [provider.provider, provider.sessions]), ), ).toEqual({ claude: 1, codex: 1 }); }); it("excludes an environment reporting an older contract version", () => { const merged = mergeUsage( [ environment( "env-a", summary([bucket()], [{ provider: "claude", hostId: "mac", homePath: "/a" }]), ), environment( "env-b", summary( [bucket()], [{ provider: "claude", hostId: "linux", homePath: "/b" }], USAGE_CONTRACT_VERSION - 2, ), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.costUsd).toBe(10); expect(merged.staleEnvironments).toEqual(["env-b"]); }); it("keeps the previous compatible contract version so additive provider expansions still merge", () => { const merged = mergeUsage( [ environment( "env-a", summary( [bucket({ costUsd: 10 })], [{ provider: "claude", hostId: "mac", homePath: "/a" }], ), ), environment( "env-b", summary( [bucket({ costUsd: 4, provider: "codex", model: "gpt-5.6-sol" })], [{ provider: "codex", hostId: "linux", homePath: "/b" }], USAGE_CONTRACT_VERSION - 1, ), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.costUsd).toBe(14); expect(merged.staleEnvironments).toEqual([]); }); it("derives provider shares and cost quality", () => { const merged = mergeUsage( [ environment( "env-a", summary( [ bucket({ costUsd: 75 }), bucket({ provider: "codex", model: "gpt-5.6-sol", costUsd: 25, unpricedRecords: 5 }), ], [ { provider: "claude", hostId: "mac", homePath: "/a/.claude" }, { provider: "codex", hostId: "mac", homePath: "/a/.codex" }, ], ), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.providers[0]?.provider).toBe("claude"); expect(merged.providers[0]?.costShare).toBeCloseTo(0.75, 5); expect(merged.costQuality.unpricedShare).toBeCloseTo(0.5, 5); expect(merged.costQuality.cacheSavingsUsd).toBe(4); }); it("marks a model with no known rates as unpriced rather than free", () => { const merged = mergeUsage( [ environment( "env-a", summary( [ bucket({ costUsd: 75 }), bucket({ provider: "codex", model: "unknown-model", costUsd: 0, costSource: "unpriced", unpricedRecords: 5, }), ], [ { provider: "claude", hostId: "mac", homePath: "/a/.claude" }, { provider: "codex", hostId: "mac", homePath: "/a/.codex" }, ], ), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.models.find((model) => model.model === "unknown-model")?.unpricedRecords).toBe(5); expect(merged.models.filter(isModelCostUnknown).map((model) => model.model)).toEqual([ "unknown-model", ]); }); it("keeps two machines apart when hostname and home path collide", () => { // Every Mac resolves /Users/theo/.claude, so a hostname clash used to make // one machine's usage vanish. Filesystem identity separates them. const shape = { provider: "claude" as const, hostId: "mac", homePath: "/Users/theo/.claude" }; const merged = mergeUsage( [ environment("env-a", summary([bucket()], [{ ...shape, volumeId: "16777220:1234" }])), environment("env-b", summary([bucket()], [{ ...shape, volumeId: "16777221:9999" }])), ], USAGE_CONTRACT_VERSION, ); expect(merged.costUsd).toBe(20); expect(merged.duplicateSources).toHaveLength(0); }); it("still collapses two servers reading the same directory", () => { const same = { provider: "claude" as const, hostId: "mac", homePath: "/Users/theo/.claude", volumeId: "16777220:1234", }; const merged = mergeUsage( [ environment("env-a", summary([bucket()], [same])), environment("env-b", summary([bucket()], [same])), ], USAGE_CONTRACT_VERSION, ); expect(merged.costUsd).toBe(10); expect(merged.duplicateSources).toHaveLength(1); }); it("totals sessions from per-directory distinct counts, not per-bucket sums", () => { // One session that spans two days appears in two buckets. Summing bucket // sessions would say 2; the source's distinct count says 1. const merged = mergeUsage( [ environment( "env-a", summary( [bucket({ day: "2026-08-06" as UsageDay }), bucket({ day: "2026-08-07" as UsageDay })], [ { provider: "claude", hostId: "mac", homePath: "/a/.claude", distinctSessions: 1, }, ], ), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.sessions).toBe(1); expect(merged.providers[0]?.sessions).toBe(1); }); it("returns empty totals with no environments", () => { const merged = mergeUsage([], USAGE_CONTRACT_VERSION); expect(merged.costUsd).toBe(0); expect(merged.daily).toHaveLength(0); expect(merged.hourly).toHaveLength(0); }); it("omits providers with no sessions or usage", () => { const merged = mergeUsage( [ environment( "env-a", summary( [], [ { provider: "claude", hostId: "mac", homePath: "/a/.claude", distinctSessions: 0, }, ], ), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.providers).toEqual([]); }); it("derives hourly totals without losing the daily rollup", () => { const merged = mergeUsage( [ environment( "env-a", summary( [ bucket({ hourStart: "2026-08-07T09:37:00.000Z", costUsd: 3 }), bucket({ hourStart: "2026-08-07T10:37:00.000Z", costUsd: 7 }), ], [{ provider: "claude", hostId: "mac", homePath: "/a/.claude" }], ), ), ], USAGE_CONTRACT_VERSION, ); expect(merged.hourly.map((hour) => [hour.hourStart, hour.costUsd])).toEqual([ ["2026-08-07T09:37:00.000Z", 3], ["2026-08-07T10:37:00.000Z", 7], ]); expect(merged.daily).toHaveLength(1); expect(merged.daily[0]?.costUsd).toBe(10); }); });