Files
Kilo-Org_kilocode/packages/opencode/test/kilocode/codex-auth-refresh.test.ts
T
2026-05-18 11:45:36 -03:00

125 lines
3.4 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { CodexAuthExpiredError, refreshCodexAuth } from "../../src/kilocode/provider/codex-refresh"
import type { PluginInput } from "@kilocode/plugin"
import { MessageV2 } from "../../src/session/message-v2"
import { ProviderID } from "../../src/provider/schema"
type Auth = {
type: "oauth"
refresh: string
access: string
expires: number
accountId?: string
}
const expired = (): Auth => ({
type: "oauth",
access: "old-access",
refresh: "old-refresh",
expires: 0,
})
function plugin(persist: (auth: Auth) => void): PluginInput {
const set = async (req: { body: Auth }) => {
persist(req.body)
}
return {
client: {
auth: { set },
},
} as unknown as PluginInput
}
describe("Codex auth refresh", () => {
test("serializes expired Codex auth as ProviderAuthError", () => {
const result = MessageV2.fromError(new CodexAuthExpiredError(), { providerID: ProviderID.make("openai") })
expect(result).toStrictEqual({
name: "ProviderAuthError",
data: {
providerID: "openai",
message:
"Your ChatGPT sign-in expired or was revoked. Sign in with ChatGPT again to continue using Codex models.",
},
})
})
test("coalesces concurrent refreshes and persists rotated tokens", async () => {
const calls: string[] = []
const writes: Auth[] = []
const first = expired()
const second = expired()
const refresh = async (token: string) => {
calls.push(token)
await new Promise((resolve) => setTimeout(resolve, 1))
return { id_token: "", access_token: "next-access", refresh_token: "next-refresh", expires_in: 60 }
}
const [a, b] = await Promise.all([
refreshCodexAuth({
input: plugin((auth) => writes.push(auth)),
getAuth: async () => first,
auth: first,
refresh,
account: () => undefined,
}),
refreshCodexAuth({
input: plugin((auth) => writes.push(auth)),
getAuth: async () => second,
auth: second,
refresh,
account: () => undefined,
}),
])
expect(calls).toEqual(["old-refresh"])
expect(writes).toHaveLength(1)
expect(a.access).toBe("next-access")
expect(b.refresh).toBe("next-refresh")
expect(first.access).toBe("next-access")
expect(second.access).toBe("next-access")
})
test("uses a newer stored token after refresh 401", async () => {
const fresh = {
type: "oauth" as const,
access: "fresh-access",
refresh: "fresh-refresh",
expires: Date.now() + 60_000,
}
const auth = expired()
let count = 0
const getAuth = async () => {
count++
return count === 1 ? auth : fresh
}
const result = await refreshCodexAuth({
input: plugin(() => {}),
getAuth,
auth,
refresh: async () => {
throw new Error("Token refresh failed: 401")
},
account: () => undefined,
})
expect(result).toBe(fresh)
})
test("throws reauth error when refresh 401 has no newer stored token", async () => {
const auth = expired()
await expect(
refreshCodexAuth({
input: plugin(() => {}),
getAuth: async () => auth,
auth,
refresh: async () => {
throw new Error("Token refresh failed: 401")
},
account: () => undefined,
}),
).rejects.toBeInstanceOf(CodexAuthExpiredError)
})
})