import { readFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { runUsageCommand } from "../src/cli/usage-command.js"; import { AuthStorage } from "../src/core/auth-storage.js"; const fixtures = resolve( dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "native", "usage", "tests", "fixtures", ); const fixture = (name: string): string => readFileSync(join(fixtures, name), "utf8"); const oauth = (access: string, email: string, accountId: string) => ({ type: "oauth" as const, access, refresh: `refresh-${accountId}`, expires: Date.now() + 60_000, email, accountId, }); /** Answer each provider the way it answers in reality. */ function realishFetch(): typeof globalThis.fetch { return vi.fn(async (input: string | URL) => { const url = String(input); const body = url.includes("anthropic.com/api/oauth/usage") ? fixture("anthropic-usage.json") : url.includes("anthropic.com/api/oauth/profile") ? fixture("anthropic-profile.json") : JSON.stringify({ plan_type: "plus", rate_limit: { primary_window: { used_percent: 25.5, limit_window_seconds: 18_000, reset_after_seconds: 7200 }, }, }); return new Response(body, { status: 200, headers: { "Content-Type": "application/json" } }); }) as unknown as typeof globalThis.fetch; } describe("runUsageCommand CLI", () => { let originalFetch: typeof globalThis.fetch; let consoleLog: ReturnType; beforeEach(() => { originalFetch = globalThis.fetch; consoleLog = vi.spyOn(console, "log").mockImplementation(() => {}); }); afterEach(() => { globalThis.fetch = originalFetch; vi.restoreAllMocks(); }); it("outputs one JSON object covering every provider with a feed", async () => { const auth = AuthStorage.inMemory({ "openai-codex": [oauth("access-test", "user@example.com", "acct-test")], anthropic: [oauth("access-claude", "user2@example.com", "acct-claude")], }); vi.spyOn(AuthStorage, "create").mockReturnValue(auth); globalThis.fetch = realishFetch(); await runUsageCommand(true); expect(consoleLog).toHaveBeenCalledTimes(1); const parsed = JSON.parse(consoleLog.mock.calls[0][0]); expect(parsed).toMatchObject({ fetchedAt: expect.any(Number), reports: [ { provider: "openai-codex", providerName: "ChatGPT Codex", accountId: "acct-test", email: "user@example.com", planType: "plus", limits: [ { label: "Primary", usedPercent: 25.5, windowLabel: "5h window", resetsAt: expect.any(Number), status: "ok", }, ], balance: null, notes: [], error: null, }, { provider: "anthropic", providerName: "Anthropic", planType: "team_standard", limits: [ { label: "Session", usedPercent: 0, windowLabel: "5h window", status: "ok" }, { label: "Weekly", usedPercent: 94, windowLabel: "7d window", status: "warning" }, ], error: null, }, ], skipped: [], }); // One JSON object on stdout, nothing else: this is the contract a native host reads, so // no colour escapes and no extra lines. expect(Object.keys(parsed)).toEqual(["fetchedAt", "reports", "skipped"]); expect(consoleLog.mock.calls[0][0]).not.toContain("\u001b["); }); it("carries the reason a provider is skipped", async () => { const auth = AuthStorage.inMemory({ "openai-codex": [oauth("access-test", "user@example.com", "acct-test")], alibaba: [{ type: "api_key", key: "sk-sp-test" }], }); vi.spyOn(AuthStorage, "create").mockReturnValue(auth); globalThis.fetch = realishFetch(); await runUsageCommand(true); const parsed = JSON.parse(consoleLog.mock.calls[0][0]); expect(parsed.skipped).toEqual([{ provider: "alibaba", reason: "needs a cli or session credential" }]); }); it("outputs human-readable text when json is false", async () => { const auth = AuthStorage.inMemory({ "openai-codex": [oauth("access-test", "user@example.com", "acct-test")], }); vi.spyOn(AuthStorage, "create").mockReturnValue(auth); globalThis.fetch = realishFetch(); await runUsageCommand(false); expect(consoleLog).toHaveBeenCalledTimes(1); const text = consoleLog.mock.calls[0][0]; expect(text).toContain("ChatGPT Codex"); expect(text).toContain("user@example.com"); }); it("exits non-zero only when the auth store itself cannot be read", async () => { vi.spyOn(AuthStorage, "create").mockImplementation(() => { throw new Error("no auth file"); }); await runUsageCommand(true); expect(process.exitCode).toBe(1); process.exitCode = 0; }); });