Files
2026-07-13 13:39:12 +08:00

113 lines
4.5 KiB
TypeScript

/**
* Front 1 (Codex multi-account cascade) — global refresh serialization.
*
* Providers under the same Auth0 client_id (OpenAI Codex + openai) get the WHOLE
* refresh_token family revoked when two sibling accounts refresh concurrently
* (openai/codex#9648). The per-connection mutex does NOT help here — the colliding
* refreshes are on DIFFERENT connections. `serializeRefresh` forces concurrency=1
* across every connection in a rotation group, so two siblings never hit Auth0's
* /oauth/token at the same time. Non-rotating providers are left untouched.
*
* Evidence: VM 192.168.0.15 logs showed bursts of 5-6 Codex refreshes within
* ~14s whenever a batch of accounts was added — exactly the cascade trigger.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
serializeRefresh,
rotationGroupFor,
wasRefreshTokenRotated,
__resetRefreshSerializerForTest,
} from "../../open-sse/services/refreshSerializer.ts";
// These tests assert serialization ORDERING (concurrency=1 per group); the
// inter-refresh settle gap is covered by refresh-serializer-spacing.test.ts.
// Opt out of the gap here so the ordering checks stay fast and deterministic.
process.env.CODEX_REFRESH_SPACING_MS = "0";
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
test("codex and openai share one group and never refresh concurrently", async () => {
__resetRefreshSerializerForTest();
let active = 0;
let maxActive = 0;
const order: string[] = [];
const task = (label: string, provider: string, ms: number) =>
serializeRefresh(provider, async () => {
active++;
maxActive = Math.max(maxActive, active);
await delay(ms);
order.push(label);
active--;
return label;
});
// codex + openai = same Auth0 client → must serialize together.
await Promise.all([task("a", "codex", 30), task("b", "openai", 10), task("c", "codex", 10)]);
assert.equal(maxActive, 1, "no two refreshes in the openai-auth0 group may overlap");
assert.equal(order.length, 3);
});
test("different rotation groups run concurrently (not serialized against each other)", async () => {
__resetRefreshSerializerForTest();
let active = 0;
let maxActive = 0;
const run = (provider: string) =>
serializeRefresh(provider, async () => {
active++;
maxActive = Math.max(maxActive, active);
await delay(20);
active--;
});
await Promise.all([run("codex"), run("claude"), run("kiro")]);
assert.equal(maxActive, 3, "codex / claude / kiro are independent groups");
});
test("non-rotating providers are not serialized", async () => {
__resetRefreshSerializerForTest();
let active = 0;
let maxActive = 0;
const run = (provider: string) =>
serializeRefresh(provider, async () => {
active++;
maxActive = Math.max(maxActive, active);
await delay(20);
active--;
});
await Promise.all([run("antigravity"), run("antigravity"), run("claude")]);
assert.equal(maxActive, 3, "non-rotating providers must keep running in parallel");
});
test("an error in one refresh does not deadlock the group queue", async () => {
__resetRefreshSerializerForTest();
await assert.rejects(
serializeRefresh("codex", async () => {
throw new Error("boom");
}),
/boom/
);
const recovered = await serializeRefresh("codex", async () => "ok");
assert.equal(recovered, "ok", "queue must keep flowing after a failed refresh");
});
test("rotationGroupFor maps the OpenAI Auth0 family together", () => {
assert.equal(rotationGroupFor("codex"), rotationGroupFor("openai"));
assert.notEqual(rotationGroupFor("codex"), rotationGroupFor("claude"));
assert.equal(rotationGroupFor("antigravity"), null);
});
// Front 3 — reuse-race tolerance: only keep a connection active after an
// unrecoverable refresh failure when the DB token actually changed under us.
test("wasRefreshTokenRotated: true only when the DB token changed to a non-empty value", () => {
assert.equal(wasRefreshTokenRotated("rt-old", "rt-new"), true, "a sibling rotated it");
assert.equal(wasRefreshTokenRotated("rt-old", "rt-old"), false, "same token = genuinely dead");
assert.equal(wasRefreshTokenRotated("rt-old", null), false, "no DB token = deactivate");
assert.equal(wasRefreshTokenRotated("rt-old", ""), false, "empty DB token = deactivate");
assert.equal(wasRefreshTokenRotated(null, "rt-new"), false, "unknown attempted = deactivate");
assert.equal(wasRefreshTokenRotated(undefined, undefined), false);
});