import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { ensureAuthProfileStore, markAuthProfileFailure } from "./auth-profiles.js"; describe("markAuthProfileFailure", () => { it("disables billing failures for ~5 hours by default", async () => { const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-auth-")); try { const authPath = path.join(agentDir, "auth-profiles.json"); fs.writeFileSync( authPath, JSON.stringify({ version: 1, profiles: { "anthropic:default": { type: "api_key", provider: "anthropic", key: "sk-default", }, }, }), ); const store = ensureAuthProfileStore(agentDir); const startedAt = Date.now(); await markAuthProfileFailure({ store, profileId: "anthropic:default", reason: "billing", agentDir, }); const disabledUntil = store.usageStats?.["anthropic:default"]?.disabledUntil; expect(typeof disabledUntil).toBe("number"); const remainingMs = (disabledUntil as number) - startedAt; expect(remainingMs).toBeGreaterThan(4.5 * 60 * 60 * 1000); expect(remainingMs).toBeLessThan(5.5 * 60 * 60 * 1000); } finally { fs.rmSync(agentDir, { recursive: true, force: true }); } }); it("honors per-provider billing backoff overrides", async () => { const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-auth-")); try { const authPath = path.join(agentDir, "auth-profiles.json"); fs.writeFileSync( authPath, JSON.stringify({ version: 1, profiles: { "anthropic:default": { type: "api_key", provider: "anthropic", key: "sk-default", }, }, }), ); const store = ensureAuthProfileStore(agentDir); const startedAt = Date.now(); await markAuthProfileFailure({ store, profileId: "anthropic:default", reason: "billing", agentDir, cfg: { auth: { cooldowns: { billingBackoffHoursByProvider: { Anthropic: 1 }, billingMaxHours: 2, }, }, } as never, }); const disabledUntil = store.usageStats?.["anthropic:default"]?.disabledUntil; expect(typeof disabledUntil).toBe("number"); const remainingMs = (disabledUntil as number) - startedAt; expect(remainingMs).toBeGreaterThan(0.8 * 60 * 60 * 1000); expect(remainingMs).toBeLessThan(1.2 * 60 * 60 * 1000); } finally { fs.rmSync(agentDir, { recursive: true, force: true }); } }); it("resets backoff counters outside the failure window", async () => { const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-auth-")); try { const authPath = path.join(agentDir, "auth-profiles.json"); const now = Date.now(); fs.writeFileSync( authPath, JSON.stringify({ version: 1, profiles: { "anthropic:default": { type: "api_key", provider: "anthropic", key: "sk-default", }, }, usageStats: { "anthropic:default": { errorCount: 9, failureCounts: { billing: 3 }, lastFailureAt: now - 48 * 60 * 60 * 1000, }, }, }), ); const store = ensureAuthProfileStore(agentDir); await markAuthProfileFailure({ store, profileId: "anthropic:default", reason: "billing", agentDir, cfg: { auth: { cooldowns: { failureWindowHours: 24 } }, } as never, }); expect(store.usageStats?.["anthropic:default"]?.errorCount).toBe(1); expect(store.usageStats?.["anthropic:default"]?.failureCounts?.billing).toBe(1); } finally { fs.rmSync(agentDir, { recursive: true, force: true }); } }); });