test: share openai codex oauth fixtures

This commit is contained in:
Peter Steinberger
2026-04-20 21:39:30 +01:00
parent 56529d7850
commit 134a56f3e4

View File

@@ -25,6 +25,13 @@ vi.mock("./provider-openai-codex-oauth-tls.js", () => ({
import { loginOpenAICodexOAuth } from "./provider-openai-codex-oauth.js";
const CODEX_AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize?state=abc";
type CodexLoginOptions = {
onAuth: (event: { url: string }) => Promise<void>;
onManualCodeInput?: () => Promise<string>;
};
function createPrompter() {
const spin = { update: vi.fn(), stop: vi.fn() };
const prompter: Pick<WizardPrompter, "note" | "progress" | "text"> = {
@@ -45,6 +52,22 @@ function createRuntime(): RuntimeEnv {
};
}
function createCodexCredentials(extra: Record<string, unknown> = {}) {
return {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
...extra,
};
}
async function startCodexAuth(opts: CodexLoginOptions) {
await opts.onAuth({ url: CODEX_AUTHORIZE_URL });
expect(opts.onManualCodeInput).toBeTypeOf("function");
}
async function runCodexOAuth(params: {
isRemote: boolean;
openUrl?: (url: string) => Promise<void>;
@@ -68,13 +91,7 @@ describe("loginOpenAICodexOAuth", () => {
});
it("returns credentials on successful oauth login", async () => {
const creds = {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
const creds = createCodexCredentials();
mocks.loginOpenAICodex.mockResolvedValue(creds);
const { result, spin, runtime } = await runCodexOAuth({ isRemote: false });
@@ -89,13 +106,7 @@ describe("loginOpenAICodexOAuth", () => {
});
it("passes through Pi-provided authorize URLs without mutation", async () => {
const creds = {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
const creds = createCodexCredentials();
mocks.loginOpenAICodex.mockImplementation(
async (opts: { onAuth: (event: { url: string }) => Promise<void> }) => {
await opts.onAuth({
@@ -117,18 +128,10 @@ describe("loginOpenAICodexOAuth", () => {
});
it("preserves authorize urls that omit scope", async () => {
const creds = {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
const creds = createCodexCredentials();
mocks.loginOpenAICodex.mockImplementation(
async (opts: { onAuth: (event: { url: string }) => Promise<void> }) => {
await opts.onAuth({
url: "https://auth.openai.com/oauth/authorize?state=abc",
});
await opts.onAuth({ url: CODEX_AUTHORIZE_URL });
return creds;
},
);
@@ -136,17 +139,11 @@ describe("loginOpenAICodexOAuth", () => {
const openUrl = vi.fn(async () => {});
await runCodexOAuth({ isRemote: false, openUrl });
expect(openUrl).toHaveBeenCalledWith("https://auth.openai.com/oauth/authorize?state=abc");
expect(openUrl).toHaveBeenCalledWith(CODEX_AUTHORIZE_URL);
});
it("preserves slash-terminated authorize paths too", async () => {
const creds = {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
const creds = createCodexCredentials();
mocks.loginOpenAICodex.mockImplementation(
async (opts: { onAuth: (event: { url: string }) => Promise<void> }) => {
await opts.onAuth({
@@ -185,26 +182,12 @@ describe("loginOpenAICodexOAuth", () => {
});
it("passes manual code input hook for remote oauth flows", async () => {
const creds = {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
mocks.loginOpenAICodex.mockImplementation(
async (opts: {
onAuth: (event: { url: string }) => Promise<void>;
onManualCodeInput?: () => Promise<string>;
}) => {
await opts.onAuth({
url: "https://auth.openai.com/oauth/authorize?state=abc",
});
expect(opts.onManualCodeInput).toBeTypeOf("function");
await expect(opts.onManualCodeInput?.()).resolves.toContain("code=test");
return creds;
},
);
const creds = createCodexCredentials();
mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => {
await startCodexAuth(opts);
await expect(opts.onManualCodeInput?.()).resolves.toContain("code=test");
return creds;
});
const { result, prompter } = await runCodexOAuth({ isRemote: true });
@@ -219,32 +202,17 @@ describe("loginOpenAICodexOAuth", () => {
vi.useFakeTimers();
const { prompter } = createPrompter();
const runtime = createRuntime();
mocks.loginOpenAICodex.mockImplementation(
async (opts: {
onAuth: (event: { url: string }) => Promise<void>;
onManualCodeInput?: () => Promise<string>;
}) => {
await opts.onAuth({
url: "https://auth.openai.com/oauth/authorize?state=abc",
});
expect(opts.onManualCodeInput).toBeTypeOf("function");
const manualPromise = opts.onManualCodeInput?.();
await vi.advanceTimersByTimeAsync(14_000);
expect(manualPromise).toBeDefined();
expect(prompter.text).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(1_000);
expect(prompter.text).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(1_000);
return {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
manualCode: await manualPromise,
};
},
);
mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => {
await startCodexAuth(opts);
const manualPromise = opts.onManualCodeInput?.();
await vi.advanceTimersByTimeAsync(14_000);
expect(manualPromise).toBeDefined();
expect(prompter.text).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(1_000);
expect(prompter.text).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(1_000);
return createCodexCredentials({ manualCode: await manualPromise });
});
await expect(
loginOpenAICodexOAuth({
@@ -270,25 +238,11 @@ describe("loginOpenAICodexOAuth", () => {
it("clears the local manual fallback timer when browser callback settles first", async () => {
vi.useFakeTimers();
mocks.loginOpenAICodex.mockImplementation(
async (opts: {
onAuth: (event: { url: string }) => Promise<void>;
onManualCodeInput?: () => Promise<string>;
}) => {
await opts.onAuth({
url: "https://auth.openai.com/oauth/authorize?state=abc",
});
expect(opts.onManualCodeInput).toBeTypeOf("function");
void opts.onManualCodeInput?.();
return {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
},
);
mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => {
await startCodexAuth(opts);
void opts.onManualCodeInput?.();
return createCodexCredentials();
});
await expect(runCodexOAuth({ isRemote: false })).resolves.toMatchObject({
result: expect.objectContaining({
@@ -302,13 +256,7 @@ describe("loginOpenAICodexOAuth", () => {
});
it("continues OAuth flow on non-certificate preflight failures", async () => {
const creds = {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
const creds = createCodexCredentials();
mocks.runOpenAIOAuthTlsPreflight.mockResolvedValue({
ok: false,
kind: "network",
@@ -332,13 +280,7 @@ describe("loginOpenAICodexOAuth", () => {
message: "unable to get local issuer certificate",
});
mocks.formatOpenAIOAuthTlsPreflightFix.mockReturnValue("Run brew postinstall openssl@3");
const creds = {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
const creds = createCodexCredentials();
mocks.loginOpenAICodex.mockResolvedValue(creds);
const { prompter } = createPrompter();
@@ -368,14 +310,7 @@ describe("loginOpenAICodexOAuth", () => {
async (opts: { onManualCodeInput?: () => Promise<string> }) => {
expect(opts.onManualCodeInput).toBeTypeOf("function");
const manualCode = await opts.onManualCodeInput?.();
return {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
manualCode,
};
return createCodexCredentials({ manualCode });
},
);
@@ -403,25 +338,12 @@ describe("loginOpenAICodexOAuth", () => {
vi.useFakeTimers();
const { prompter } = createPrompter();
const runtime = createRuntime();
mocks.loginOpenAICodex.mockImplementation(
async (opts: {
onAuth: (event: { url: string }) => Promise<void>;
onManualCodeInput?: () => Promise<string>;
}) => {
await opts.onAuth({
url: "https://auth.openai.com/oauth/authorize?state=abc",
});
void opts.onManualCodeInput?.();
await vi.advanceTimersByTimeAsync(15_500);
return {
provider: "openai-codex" as const,
access: "access-token",
refresh: "refresh-token",
expires: Date.now() + 60_000,
email: "user@example.com",
};
},
);
mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => {
await startCodexAuth(opts);
void opts.onManualCodeInput?.();
await vi.advanceTimersByTimeAsync(15_500);
return createCodexCredentials();
});
await expect(
loginOpenAICodexOAuth({