import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import { clampPercent, resolveLegacyPiAgentAccessToken, resolveUsageProviderId, withTimeout, } from "./provider-usage.shared.js"; async function withLegacyPiAuthFile( contents: string, run: (home: string) => Promise | void, ): Promise { const home = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-provider-usage-")); await fs.mkdir(path.join(home, ".pi", "agent"), { recursive: true }); await fs.writeFile(path.join(home, ".pi", "agent", "auth.json"), contents, "utf8"); try { await run(home); } finally { await fs.rm(home, { recursive: true, force: true }); } } describe("provider-usage.shared", () => { afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); }); it.each([ { value: "z-ai", expected: "zai" }, { value: " GOOGLE-GEMINI-CLI ", expected: "google-gemini-cli" }, { value: "unknown-provider", expected: undefined }, { value: undefined, expected: undefined }, { value: null, expected: undefined }, ])("normalizes provider ids for %j", ({ value, expected }) => { expect(resolveUsageProviderId(value)).toBe(expected); }); it.each([ { value: -5, expected: 0 }, { value: 42, expected: 42 }, { value: 120, expected: 100 }, { value: Number.NaN, expected: 0 }, { value: Number.POSITIVE_INFINITY, expected: 0 }, ])("clamps usage percents for %j", ({ value, expected }) => { expect(clampPercent(value)).toBe(expected); }); it.each([ { name: "returns work result when it resolves before timeout", promise: () => Promise.resolve("ok"), expected: "ok", }, { name: "propagates work errors before timeout", promise: () => Promise.reject(new Error("boom")), error: "boom", }, ])("$name", async ({ promise, expected, error }) => { if (error) { await expect(withTimeout(promise(), 100, "fallback")).rejects.toThrow(error); return; } await expect(withTimeout(promise(), 100, "fallback")).resolves.toBe(expected); }); it("returns fallback when timeout wins", async () => { vi.useFakeTimers(); const late = new Promise((resolve) => setTimeout(() => resolve("late"), 50)); const result = withTimeout(late, 1, "fallback"); await vi.advanceTimersByTimeAsync(1); await expect(result).resolves.toBe("fallback"); }); it("clears the timeout after successful work", async () => { const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout"); await expect(withTimeout(Promise.resolve("ok"), 100, "fallback")).resolves.toBe("ok"); expect(clearTimeoutSpy).toHaveBeenCalledTimes(1); }); it.each([ { name: "reads legacy pi auth tokens for known provider aliases", contents: `${JSON.stringify({ "z-ai": { access: "legacy-zai-key" } }, null, 2)}\n`, expected: "legacy-zai-key", }, { name: "returns undefined for invalid legacy pi auth files", contents: "{not-json", expected: undefined, }, ])("$name", async ({ contents, expected }) => { await withLegacyPiAuthFile(contents, async (home) => { expect(resolveLegacyPiAgentAccessToken({ HOME: home }, ["z-ai", "zai"])).toBe(expected); }); }); });