Spaces:
Running
Running
| 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(), "openclaw-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(), "openclaw-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(), "openclaw-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 }); | |
| } | |
| }); | |
| }); | |