From 20e64be7adc05b13a1280a79f51d9aab13534d37 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:27:49 +0900 Subject: [PATCH 1/3] test(oauth): exercise configured generic pool validators --- .../account-pool-management-api.test.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/server/account-pool-management-api.test.ts b/tests/server/account-pool-management-api.test.ts index 1c66163c87c..2bb6f1b832d 100644 --- a/tests/server/account-pool-management-api.test.ts +++ b/tests/server/account-pool-management-api.test.ts @@ -615,6 +615,17 @@ describe("legacy pool contract goldens (#wp5)", () => { const resp = await handleCodexAuthAPI(req, new URL(req.url), makeCodexConfig()); return resp!.status; }; + const previousHome = process.env.OPENCODEX_HOME; + const testDir = mkdtempSync(join(tmpdir(), "ocx-pool-validator-")); + process.env.OPENCODEX_HOME = testDir; + saveConfig({ + port: 0, + hostname: "127.0.0.1", + defaultProvider: "google-antigravity", + providers: { + "google-antigravity": { adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authMode: "oauth" }, + }, + } as OcxConfig); const server = startServer(0); try { const oauth = async (payload: Record) => { @@ -623,11 +634,14 @@ describe("legacy pool contract goldens (#wp5)", () => { }); return res.status; }; - for (const strategy of ["weighted", "", 3, null]) { + for (const strategy of ["weighted", "", 3]) { expect(await codex({ strategy })).toBe(400); expect(await oauth({ provider: "anthropic", strategy })).toBe(400); expect(await oauth({ provider: "google-antigravity", strategy })).toBe(400); } + expect(await codex({ strategy: null })).toBe(400); + expect(await oauth({ provider: "anthropic", strategy: null })).toBe(400); + expect(await oauth({ provider: "google-antigravity", strategy: null })).toBe(200); // 0 and 101 sit just outside the shared bound; 1 and 100 are the edges that must pass. for (const stickyLimit of [0, 101, 1.5]) { expect(await codex({ stickyLimit })).toBe(400); @@ -639,6 +653,9 @@ describe("legacy pool contract goldens (#wp5)", () => { } } finally { await server.stop(true); + if (previousHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = previousHome; + removeTreeWithRetry(testDir); } }); From f0d9ff2dfa5e824781f83f9b94ee904b34589d19 Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Tue, 22 Sep 2026 06:50:16 +0900 Subject: [PATCH 2/3] test(oauth): prove the generic null-strategy clear and harden test teardown --- .../account-pool-management-api.test.ts | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/tests/server/account-pool-management-api.test.ts b/tests/server/account-pool-management-api.test.ts index 2bb6f1b832d..1dfb3728239 100644 --- a/tests/server/account-pool-management-api.test.ts +++ b/tests/server/account-pool-management-api.test.ts @@ -603,11 +603,15 @@ describe("legacy pool contract goldens (#wp5)", () => { } }); - test("a bad strategy and a bad stickyLimit are rejected identically on every kind", async () => { + test("a bad strategy and a bad stickyLimit are rejected identically on every kind, with one null exception", async () => { // One validator, three adapters. The kinds keep their own request and response shapes -- // that is what the goldens above pin -- but the VALUE rules are now a single implementation, // so "quota, round-robin, fill-first" and the 1..100 sticky bound cannot drift apart per // kind. Before this, the generic kind carried a private copy of both. + // ONE deliberate exception: `strategy: null` is not a bad value on the legacy generic + // endpoint -- it clears the saved strategy and answers 200. Codex, Anthropic, and the + // unified /api/pool/settings route all reject the same null with 400. A future reader + // who sees the 200 must not "fix" it back without deciding that contract first. const codex = async (payload: Record) => { const req = new Request("http://localhost/api/codex-auth/pool-strategy", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), @@ -617,23 +621,30 @@ describe("legacy pool contract goldens (#wp5)", () => { }; const previousHome = process.env.OPENCODEX_HOME; const testDir = mkdtempSync(join(tmpdir(), "ocx-pool-validator-")); - process.env.OPENCODEX_HOME = testDir; - saveConfig({ - port: 0, - hostname: "127.0.0.1", - defaultProvider: "google-antigravity", - providers: { - "google-antigravity": { adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authMode: "oauth" }, - }, - } as OcxConfig); - const server = startServer(0); + let server: ReturnType | undefined; try { + process.env.OPENCODEX_HOME = testDir; + saveConfig({ + port: 0, + hostname: "127.0.0.1", + defaultProvider: "google-antigravity", + providers: { + "google-antigravity": { adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authMode: "oauth" }, + }, + } as OcxConfig); + server = startServer(0); const oauth = async (payload: Record) => { const res = await fetch(new URL("/api/oauth/accounts/pool", server.url), { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), }); return res.status; }; + const oauthJson = async (payload: Record) => { + const res = await fetch(new URL("/api/oauth/accounts/pool", server.url), { + method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), + }); + return { status: res.status, body: await res.json() as { strategy?: unknown } }; + }; for (const strategy of ["weighted", "", 3]) { expect(await codex({ strategy })).toBe(400); expect(await oauth({ provider: "anthropic", strategy })).toBe(400); @@ -641,7 +652,12 @@ describe("legacy pool contract goldens (#wp5)", () => { } expect(await codex({ strategy: null })).toBe(400); expect(await oauth({ provider: "anthropic", strategy: null })).toBe(400); - expect(await oauth({ provider: "google-antigravity", strategy: null })).toBe(200); + // The generic legacy contract: null clears the saved strategy. Prove the clear actually + // happened -- a 200 that left the old strategy in place would be a silent no-op. + expect(await oauth({ provider: "google-antigravity", strategy: "round-robin" })).toBe(200); + const cleared = await oauthJson({ provider: "google-antigravity", strategy: null }); + expect(cleared.status).toBe(200); + expect(cleared.body.strategy ?? null).toBeNull(); // 0 and 101 sit just outside the shared bound; 1 and 100 are the edges that must pass. for (const stickyLimit of [0, 101, 1.5]) { expect(await codex({ stickyLimit })).toBe(400); @@ -652,7 +668,7 @@ describe("legacy pool contract goldens (#wp5)", () => { expect(await codex({ stickyLimit })).toBe(200); } } finally { - await server.stop(true); + await server?.stop(true); if (previousHome === undefined) delete process.env.OPENCODEX_HOME; else process.env.OPENCODEX_HOME = previousHome; removeTreeWithRetry(testDir); From bc136dcc27deb533a17a2bda9161415067ec7aeb Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:56:15 +0900 Subject: [PATCH 3/3] test(oauth): require the strategy property in the cleared response --- tests/server/account-pool-management-api.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/server/account-pool-management-api.test.ts b/tests/server/account-pool-management-api.test.ts index 1dfb3728239..fa52f8e076f 100644 --- a/tests/server/account-pool-management-api.test.ts +++ b/tests/server/account-pool-management-api.test.ts @@ -657,7 +657,7 @@ describe("legacy pool contract goldens (#wp5)", () => { expect(await oauth({ provider: "google-antigravity", strategy: "round-robin" })).toBe(200); const cleared = await oauthJson({ provider: "google-antigravity", strategy: null }); expect(cleared.status).toBe(200); - expect(cleared.body.strategy ?? null).toBeNull(); + expect(cleared.body).toHaveProperty("strategy", null); // 0 and 101 sit just outside the shared bound; 1 and 100 are the edges that must pass. for (const stickyLimit of [0, 101, 1.5]) { expect(await codex({ stickyLimit })).toBe(400);