a2854ba768
Migrate 203 test files (356 call sites) from fs.rm/fs.rmSync to
removeWithRetries/removeSyncWithRetries to reduce EBUSY test failures
on Windows. removeWithRetries is now exported from @oh-my-pi/pi-utils.
The migration uses a regex-based approach that:
- Replaces fs.rm(path, { recursive, force }) → removeWithRetries(path)
- Replaces fs.rmSync(path, { recursive, force }) → removeSyncWithRetries(path)
- Replaces fs.rm(path) → removeWithRetries(path) (no options)
- Skips fs.rm/fs.rmSync inside template literals (bun --eval scripts)
- Adds imports to existing @oh-my-pi/pi-utils import or creates new one
- Removes unused fs imports where fs.rm was the only fs usage (4 files)
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import type { UsageProvider } from "@oh-my-pi/pi-ai";
|
|
import * as oauth from "@oh-my-pi/pi-ai/oauth";
|
|
import type { OAuthCredentials } from "@oh-my-pi/pi-ai/oauth/types";
|
|
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
|
|
import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils";
|
|
|
|
describe("AuthStorage account rotation", () => {
|
|
let tempDir: string;
|
|
let authStorage: AuthStorage;
|
|
let usageExhausted = false;
|
|
|
|
const usageProvider: UsageProvider = {
|
|
id: "openai-codex",
|
|
async fetchUsage(params) {
|
|
const accountId = params.credential.accountId ?? "unknown";
|
|
return {
|
|
provider: "openai-codex",
|
|
fetchedAt: Date.now(),
|
|
limits: [
|
|
{
|
|
id: `requests-${accountId}`,
|
|
label: "Requests",
|
|
scope: { provider: "openai-codex", accountId },
|
|
amount: { unit: "requests", used: usageExhausted ? 100 : 10, limit: 100 },
|
|
status: usageExhausted ? "exhausted" : "ok",
|
|
},
|
|
],
|
|
};
|
|
},
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
tempDir = path.join(os.tmpdir(), `pi-test-auth-rotation-${Snowflake.next()}`);
|
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
usageExhausted = false;
|
|
|
|
authStorage = await AuthStorage.create(path.join(tempDir, "testauth.db"), {
|
|
usageProviderResolver: provider => (provider === "openai-codex" ? usageProvider : undefined),
|
|
});
|
|
|
|
// Stub the refresh path so AuthStorage doesn't hit a real OAuth endpoint
|
|
// when the credential lands inside the 60s skew. Returning the credential
|
|
// unchanged preserves the test's deterministic accountId routing.
|
|
vi.spyOn(oauth, "refreshOAuthToken").mockImplementation(async (_provider, credential) => {
|
|
return credential;
|
|
});
|
|
vi.spyOn(oauth, "getOAuthApiKey").mockImplementation(async (_provider, credentials) => {
|
|
const credential = credentials["openai-codex"] as OAuthCredentials | undefined;
|
|
if (!credential) return null;
|
|
return {
|
|
apiKey: `api-${credential.accountId ?? "unknown"}`,
|
|
newCredentials: credential,
|
|
};
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
authStorage.close();
|
|
if (tempDir && fs.existsSync(tempDir)) {
|
|
removeSyncWithRetries(tempDir);
|
|
}
|
|
});
|
|
|
|
test("returns a fallback key when every OAuth account is usage-limited", async () => {
|
|
await authStorage.set("openai-codex", [
|
|
{
|
|
type: "oauth",
|
|
access: "access-1",
|
|
refresh: "refresh-1",
|
|
expires: Date.now() + 60_000,
|
|
accountId: "acct-1",
|
|
},
|
|
{
|
|
type: "oauth",
|
|
access: "access-2",
|
|
refresh: "refresh-2",
|
|
expires: Date.now() + 60_000,
|
|
accountId: "acct-2",
|
|
},
|
|
]);
|
|
|
|
const sessionId = "issue-55-session";
|
|
const firstKey = await authStorage.getApiKey("openai-codex", sessionId);
|
|
expect(firstKey).toMatch(/^api-acct-/);
|
|
|
|
usageExhausted = true;
|
|
const { switched } = await authStorage.markUsageLimitReached("openai-codex", sessionId);
|
|
expect(switched).toBe(true);
|
|
|
|
const exhaustedFallbackKey = await authStorage.getApiKey("openai-codex", sessionId);
|
|
expect(exhaustedFallbackKey).toMatch(/^api-acct-/);
|
|
});
|
|
});
|