From e19fb7857e55124a79d646a3c71bd889b484bcca Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 11:16:58 +0100 Subject: [PATCH] test: clear openai codex provider broad matchers --- .../openai/openai-codex-provider.test.ts | 286 ++++++++++-------- 1 file changed, 160 insertions(+), 126 deletions(-) diff --git a/extensions/openai/openai-codex-provider.test.ts b/extensions/openai/openai-codex-provider.test.ts index f9daf7c1837..fb9362d3266 100644 --- a/extensions/openai/openai-codex-provider.test.ts +++ b/extensions/openai/openai-codex-provider.test.ts @@ -45,6 +45,56 @@ function createSingleModelRegistry( }; } +type CodexProvider = ReturnType; +type CodexAuthMethod = NonNullable[number]; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function requireRecord(value: unknown, label: string): Record { + if (!isRecord(value)) { + throw new Error(`expected ${label} to be an object`); + } + return value; +} + +function requireArray(value: unknown, label: string): unknown[] { + if (!Array.isArray(value)) { + throw new Error(`expected ${label} to be an array`); + } + return value; +} + +function requireAuthMethod(provider: CodexProvider, id: string): CodexAuthMethod { + const method = provider.auth?.find((candidate) => candidate.id === id); + if (!method) { + throw new Error(`expected OpenAI Codex auth method ${id}`); + } + return method; +} + +function expectRecordFields(value: unknown, label: string, expected: Record) { + const record = requireRecord(value, label); + for (const [key, expectedValue] of Object.entries(expected)) { + expect(record[key]).toEqual(expectedValue); + } +} + +function expectModelFields(model: unknown, expected: Record) { + expectRecordFields(model, "model", expected); +} + +function requireEntryById(entries: unknown, id: string): Record { + const entry = requireArray(entries, "model catalog entries") + .filter(isRecord) + .find((candidate) => candidate.id === id); + if (!entry) { + throw new Error(`expected model catalog entry ${id}`); + } + return entry; +} + describe("openai codex provider", () => { beforeAll(async () => { ({ buildOpenAICodexProviderPlugin } = await import("./openai-codex-provider.js")); @@ -102,26 +152,29 @@ describe("openai codex provider", () => { expires: Date.now() + 60_000, }); - await expect(provider.refreshOAuth?.(credential)).resolves.toEqual({ + const refreshed = await provider.refreshOAuth?.(credential); + + expect(refreshed?.expires).toBeTypeOf("number"); + expect(refreshed).toEqual({ ...credential, access: "next-access", refresh: "next-refresh", - expires: expect.any(Number), + expires: refreshed?.expires, }); }); it("exposes grouped model/auth picker labels for Codex auth methods", () => { const provider = buildOpenAICodexProviderPlugin(); - const oauth = provider.auth?.find((method) => method.id === "oauth"); - const deviceCode = provider.auth?.find((method) => method.id === "device-code"); + const oauth = requireAuthMethod(provider, "oauth"); + const deviceCode = requireAuthMethod(provider, "device-code"); - expect(oauth?.wizard).toMatchObject({ + expectRecordFields(oauth.wizard, "oauth wizard", { choiceLabel: "OpenAI Codex Browser Login", groupId: "openai-codex", groupLabel: "OpenAI Codex", groupHint: "ChatGPT/Codex sign-in", }); - expect(deviceCode?.wizard).toMatchObject({ + expectRecordFields(deviceCode.wizard, "device-code wizard", { choiceLabel: "OpenAI Codex Device Pairing", groupId: "openai-codex", groupLabel: "OpenAI Codex", @@ -157,32 +210,30 @@ describe("openai codex provider", () => { it("offers OpenAI menu auth methods for browser login and device pairing", () => { const provider = buildOpenAICodexProviderPlugin(); + const oauth = requireAuthMethod(provider, "oauth"); + const deviceCode = requireAuthMethod(provider, "device-code"); expect(provider.auth?.map((method) => method.id)).toEqual(["oauth", "device-code"]); - expect(provider.auth?.find((method) => method.id === "oauth")).toMatchObject({ - label: "OpenAI Codex Browser Login", - hint: "Sign in with OpenAI in your browser", - wizard: { - choiceId: "openai-codex", - choiceLabel: "OpenAI Codex Browser Login", - assistantPriority: -30, - }, + expect(oauth.label).toBe("OpenAI Codex Browser Login"); + expect(oauth.hint).toBe("Sign in with OpenAI in your browser"); + expectRecordFields(oauth.wizard, "oauth wizard", { + choiceId: "openai-codex", + choiceLabel: "OpenAI Codex Browser Login", + assistantPriority: -30, }); - expect(provider.auth?.find((method) => method.id === "device-code")).toMatchObject({ - label: "OpenAI Codex Device Pairing", - hint: "Pair in browser with a device code", - kind: "device_code", - wizard: { - choiceId: "openai-codex-device-code", - choiceLabel: "OpenAI Codex Device Pairing", - assistantPriority: -10, - }, + expect(deviceCode.label).toBe("OpenAI Codex Device Pairing"); + expect(deviceCode.hint).toBe("Pair in browser with a device code"); + expect(deviceCode.kind).toBe("device_code"); + expectRecordFields(deviceCode.wizard, "device-code wizard", { + choiceId: "openai-codex-device-code", + choiceLabel: "OpenAI Codex Device Pairing", + assistantPriority: -10, }); }); it("stores device-code logins as OpenAI Codex oauth profiles", async () => { const provider = buildOpenAICodexProviderPlugin(); - const deviceCodeMethod = provider.auth?.find((method) => method.id === "device-code"); + const deviceCodeMethod = requireAuthMethod(provider, "device-code"); const note = vi.fn(async () => {}); const progress = { update: vi.fn(), stop: vi.fn() }; const runtime = { @@ -197,7 +248,7 @@ describe("openai codex provider", () => { expires: Date.now() + 60_000, }); - const result = await deviceCodeMethod?.run({ + const result = await deviceCodeMethod.run({ config: {}, env: process.env, prompter: { @@ -216,26 +267,27 @@ describe("openai codex provider", () => { "Trouble with device code login? See https://docs.openclaw.ai/start/faq", "OAuth help", ); - expect(result).toMatchObject({ - profiles: [ - { - credential: { - type: "oauth", - provider: "openai-codex", - access: - "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL2F1dGgiOnsiY2hhdGdwdF9hY2NvdW50X2lkIjoiYWNjdC1kZXZpY2UtMTIzIn19.signature", - refresh: "device-refresh-token", - accountId: "acct-device-123", - }, - }, - ], - defaultModel: "openai/gpt-5.5", - configPatch: { - agents: { - defaults: { - models: { - "openai/gpt-5.5": {}, - }, + const profiles = requireArray(result?.profiles, "device-code profiles"); + expect(profiles).toHaveLength(1); + const profile = requireRecord(profiles[0], "device-code profile"); + const credential = requireRecord(profile.credential, "device-code credential"); + expect(profile.profileId).toBe("openai-codex:default"); + expect(credential).toEqual({ + type: "oauth", + provider: "openai-codex", + access: + "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL2F1dGgiOnsiY2hhdGdwdF9hY2NvdW50X2lkIjoiYWNjdC1kZXZpY2UtMTIzIn19.signature", + refresh: "device-refresh-token", + expires: credential.expires, + accountId: "acct-device-123", + }); + expect(credential.expires).toBeTypeOf("number"); + expect(result?.defaultModel).toBe("openai/gpt-5.5"); + expect(result?.configPatch).toEqual({ + agents: { + defaults: { + models: { + "openai/gpt-5.5": {}, }, }, }, @@ -245,10 +297,7 @@ describe("openai codex provider", () => { async function runRemoteDeviceCodeAuthFlow() { const provider = buildOpenAICodexProviderPlugin(); - const deviceCodeMethod = provider.auth?.find((method) => method.id === "device-code"); - if (!deviceCodeMethod) { - throw new Error("expected OpenAI Codex device-code auth method"); - } + const deviceCodeMethod = requireAuthMethod(provider, "device-code"); const note = vi.fn(async () => {}); const progress = { update: vi.fn(), stop: vi.fn() }; const runtime = { @@ -270,25 +319,25 @@ describe("openai codex provider", () => { }; }); - await expect( - deviceCodeMethod.run({ - config: {}, - env: process.env, - prompter: { - note, - progress: vi.fn(() => progress), - } as never, - runtime: runtime as never, - isRemote: true, - openUrl: async () => {}, - oauth: { createVpsAwareHandlers: (() => ({})) as never }, - }), - ).resolves.toMatchObject({ - profiles: expect.arrayContaining([ - expect.objectContaining({ profileId: "openai-codex:default" }), - ]), + const result = await deviceCodeMethod.run({ + config: {}, + env: process.env, + prompter: { + note, + progress: vi.fn(() => progress), + } as never, + runtime: runtime as never, + isRemote: true, + openUrl: async () => {}, + oauth: { createVpsAwareHandlers: (() => ({})) as never }, }); + expect( + requireArray(result.profiles, "remote device-code profiles").map( + (profile) => requireRecord(profile, "remote device-code profile").profileId, + ), + ).toContain("openai-codex:default"); + return { note, runtime }; } @@ -334,7 +383,7 @@ describe("openai codex provider", () => { modelRegistry: createSingleModelRegistry(createCodexTemplate({})) as never, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.4", contextWindow: 1_050_000, contextTokens: 272_000, @@ -351,7 +400,7 @@ describe("openai codex provider", () => { modelRegistry: createSingleModelRegistry(createCodexTemplate({})) as never, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.4-pro", contextWindow: 1_050_000, contextTokens: 272_000, @@ -380,7 +429,7 @@ describe("openai codex provider", () => { modelRegistry: createSingleModelRegistry(createCodexTemplate({ id: "gpt-5.4-pro" })) as never, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.5", api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api", @@ -389,7 +438,7 @@ describe("openai codex provider", () => { maxTokens: 128_000, cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, }); - expect(pro).toMatchObject({ + expectModelFields(pro, { id: "gpt-5.5-pro", api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api", @@ -409,7 +458,7 @@ describe("openai codex provider", () => { modelRegistry: createSingleModelRegistry(createCodexTemplate({}), null) as never, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.5", api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api/codex", @@ -431,7 +480,7 @@ describe("openai codex provider", () => { providerConfig: { baseUrl: "http://proxy.local:30400" }, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.5", api: "openai-codex-responses", baseUrl: "http://proxy.local:30400", @@ -449,7 +498,7 @@ describe("openai codex provider", () => { providerConfig: { baseUrl: "http://proxy.local:30400" }, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.4", api: "openai-codex-responses", baseUrl: "http://proxy.local:30400", @@ -474,7 +523,7 @@ describe("openai codex provider", () => { ) as never, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.4-pro", api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api", @@ -494,7 +543,7 @@ describe("openai codex provider", () => { modelRegistry: createSingleModelRegistry(createCodexTemplate({})) as never, }); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.4", name: "gpt-5.4", contextWindow: 1_050_000, @@ -520,7 +569,7 @@ describe("openai codex provider", () => { ) as never, } as never); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.4-mini", name: "gpt-5.4-mini", api: "openai-codex-responses", @@ -549,43 +598,31 @@ describe("openai codex provider", () => { ], } as never); - expect(entries).not.toContainEqual( - expect.objectContaining({ - id: "gpt-5.5", - }), - ); - expect(entries).toContainEqual( - expect.objectContaining({ - id: "gpt-5.5-pro", - contextWindow: 1_000_000, - contextTokens: 272_000, - cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, - }), - ); - expect(entries).toContainEqual( - expect.objectContaining({ - id: "gpt-5.4", - contextWindow: 1_050_000, - contextTokens: 272_000, - cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, - }), - ); - expect(entries).toContainEqual( - expect.objectContaining({ - id: "gpt-5.4-pro", - contextWindow: 1_050_000, - contextTokens: 272_000, - cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, - }), - ); - expect(entries).toContainEqual( - expect.objectContaining({ - id: "gpt-5.4-mini", - contextWindow: 400_000, - contextTokens: 272_000, - cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }, - }), - ); + expect( + requireArray(entries, "model catalog entries").some( + (entry) => isRecord(entry) && entry.id === "gpt-5.5", + ), + ).toBe(false); + expectRecordFields(requireEntryById(entries, "gpt-5.5-pro"), "gpt-5.5-pro entry", { + contextWindow: 1_000_000, + contextTokens: 272_000, + cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, + }); + expectRecordFields(requireEntryById(entries, "gpt-5.4"), "gpt-5.4 entry", { + contextWindow: 1_050_000, + contextTokens: 272_000, + cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 }, + }); + expectRecordFields(requireEntryById(entries, "gpt-5.4-pro"), "gpt-5.4-pro entry", { + contextWindow: 1_050_000, + contextTokens: 272_000, + cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, + }); + expectRecordFields(requireEntryById(entries, "gpt-5.4-mini"), "gpt-5.4-mini entry", { + contextWindow: 400_000, + contextTokens: 272_000, + cost: { input: 0.75, output: 4.5, cacheRead: 0.075, cacheWrite: 0 }, + }); }); it("augments gpt-5.4-pro from catalog gpt-5.4 when legacy codex rows are absent", () => { @@ -605,14 +642,11 @@ describe("openai codex provider", () => { ], } as never); - expect(entries).toContainEqual( - expect.objectContaining({ - id: "gpt-5.4-pro", - contextWindow: 1_050_000, - contextTokens: 272_000, - cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, - }), - ); + expectRecordFields(requireEntryById(entries, "gpt-5.4-pro"), "gpt-5.4-pro entry", { + contextWindow: 1_050_000, + contextTokens: 272_000, + cost: { input: 30, output: 180, cacheRead: 0, cacheWrite: 0 }, + }); }); it("canonicalizes legacy gpt-5.4-codex models during resolved-model normalization", () => { @@ -635,7 +669,7 @@ describe("openai codex provider", () => { }, } as never); - expect(model).toMatchObject({ + expectModelFields(model, { id: "gpt-5.4", name: "gpt-5.4", }); @@ -660,7 +694,7 @@ describe("openai codex provider", () => { }, } as never); - expect(model).toMatchObject({ + expectModelFields(model, { api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api/codex", }); @@ -686,7 +720,7 @@ describe("openai codex provider", () => { }, } as never); - expect(model).toMatchObject({ + expectModelFields(model, { api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api/codex", }); @@ -712,7 +746,7 @@ describe("openai codex provider", () => { }, } as never); - expect(model).toMatchObject({ + expectModelFields(model, { api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api/codex", }); @@ -738,7 +772,7 @@ describe("openai codex provider", () => { }, } as never); - expect(model).toMatchObject({ + expectModelFields(model, { api: "openai-codex-responses", baseUrl: "https://chatgpt.com/backend-api/codex", });