fix(FN-XXX): unify codex auth and chat fallback

This commit is contained in:
gsxdsm
2026-05-03 23:08:05 -07:00
parent 7fca3e6628
commit bacc103e4e
41 changed files with 2340 additions and 325 deletions

View File

@@ -0,0 +1,101 @@
import { describe, expect, it } from "vitest";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
choosePreferredStoredCredential,
extractCodexCliStoredCredential,
readStoredCredentialsFromAuthFile,
shouldHydrateStoredCredential,
} from "../oauth-credential-interop.js";
function encodeBase64Url(value: string): string {
return Buffer.from(value, "utf-8").toString("base64url");
}
function createJwt(payload: Record<string, unknown>): string {
return [
encodeBase64Url(JSON.stringify({ alg: "none", typ: "JWT" })),
encodeBase64Url(JSON.stringify(payload)),
"signature",
].join(".");
}
describe("oauth credential interop", () => {
it("extracts Codex CLI OAuth credentials from auth.json token payload", () => {
const expiresAtSeconds = Math.floor(Date.now() / 1000) + 3600;
const accessToken = createJwt({
exp: expiresAtSeconds,
"https://api.openai.com/auth": {
chatgpt_account_id: "acct_123",
},
});
const credential = extractCodexCliStoredCredential({
tokens: {
access_token: accessToken,
refresh_token: "refresh-token",
},
});
expect(credential).toEqual({
type: "oauth",
access: accessToken,
refresh: "refresh-token",
expires: expiresAtSeconds * 1000,
accountId: "acct_123",
});
});
it("falls back to last_refresh when Codex CLI JWT has no exp claim", () => {
const accessToken = createJwt({
sub: "user-123",
});
const lastRefresh = "2026-05-03T10:00:00.000Z";
const credential = extractCodexCliStoredCredential({
last_refresh: lastRefresh,
tokens: {
access_token: accessToken,
refresh_token: "refresh-token",
account_id: "acct_from_token",
},
});
expect(credential?.type).toBe("oauth");
expect(credential?.accountId).toBe("acct_from_token");
expect(credential?.expires).toBe(Date.parse(lastRefresh) + 55 * 60 * 1000);
});
it("prefers a valid OAuth credential over an expired one and hydrates only when better", () => {
const expired = {
type: "oauth",
access: "expired-access",
refresh: "expired-refresh",
expires: Date.now() - 60_000,
} as const;
const valid = {
type: "oauth",
access: "valid-access",
refresh: "valid-refresh",
expires: Date.now() + 60_000,
} as const;
expect(choosePreferredStoredCredential(expired, valid)).toEqual(valid);
expect(shouldHydrateStoredCredential(expired, valid)).toBe(true);
expect(shouldHydrateStoredCredential({ type: "api_key", key: "sk-live" }, valid)).toBe(false);
});
it("gracefully ignores malformed auth files", () => {
const tempDir = mkdtempSync(join(tmpdir(), "fusion-oauth-interop-"));
try {
const malformedPath = join(tempDir, "auth.json");
writeFileSync(malformedPath, "{ not-json");
expect(readStoredCredentialsFromAuthFile(malformedPath)).toEqual({});
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
});