Files
OmniRoute/tests/unit/codex-quota-sync-no-proactive-refresh.test.ts
oyi77 158fb1a806 test(executors): adapt remaining 35 suites to the async lazy registry (round 2)
Round 1 only covered suites importing from executors/index.ts directly;
suites importing getExecutor through other paths (open-sse root
re-export, dynamic imports inside tests) still called it synchronously,
producing Promise-vs-instance assertion failures in CI (e.g.
providers-yuanbao-web 'instanceof YuanbaoWebExecutor' false).

Sweep method: repo-wide scan for un-awaited getExecutor call sites,
adapted mechanically (await + async callbacks + Response|{response}
union narrowing); assert.rejects guards with promise-returning
callbacks verified correct as-is.

Locally verified green: providers-yuanbao-web, ninerouter-executor,
provider-request-failure-pipeline, provider-limits-accesstoken-fallback,
executor-registry, chatcore-executor-proxy, poe-api-executor-regression,
web-cookie-providers-new (113/113).
2026-08-24 21:44:23 -03:00

107 lines
4.1 KiB
TypeScript

/**
* Codex multi-account family-revocation cascade — quota-sync trigger.
*
* The Quota / Providers dashboard (POST /api/usage/provider-limits ->
* syncAllProviderLimits, and GET /api/usage/[connectionId]) calls
* refreshAndUpdateCredentials() for each connection, in chunks of N CONCURRENT.
* For rotating-refresh providers (Codex/OpenAI share one Auth0 client_id) a
* single-use refresh_token is rotated on every refresh; refreshing several
* sibling accounts concurrently makes Auth0 revoke the WHOLE token family
* (openai/codex#9648) -> every account but the last dies with `[403] <!DOCTYPE`.
*
* On the affected VM expires_at is persisted as ~0, so needsRefresh() is
* effectively always true -> every codex account gets refreshed on every page
* visit -> guaranteed cascade.
*
* Fix: refreshAndUpdateCredentials must NEVER proactively rotate the
* refresh_token of a rotating-refresh provider. The quota fetch reuses the
* current access_token (multi-day) and genuine expiry is handled by the
* reactive, serialized 401 path during real requests. Non-rotating providers
* must keep refreshing proactively (no over-broadening).
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-codex-quota-"));
const { getExecutor } = await import("../../open-sse/executors/index.ts");
const { refreshAndUpdateCredentials } = await import("../../src/lib/usage/providerLimits.ts");
test("codex: quota-sync must NOT proactively rotate the refresh_token (Auth0 family-revocation cascade guard)", async () => {
const exec = await getExecutor("codex");
const origNeeds = exec.needsRefresh;
const origRefresh = exec.refreshCredentials;
let refreshCalls = 0;
// Simulate the VM state: expires_at ~0 makes needsRefresh always-true.
exec.needsRefresh = () => true;
exec.refreshCredentials = async () => {
refreshCalls++;
return null;
};
try {
const result = await refreshAndUpdateCredentials({
id: "codex-1",
provider: "codex",
accessToken: "existing-access-token",
refreshToken: "rotating-refresh-token",
tokenExpiresAt: new Date(2000).toISOString(),
providerSpecificData: {},
});
assert.equal(
refreshCalls,
0,
"the rotating refresh_token must NOT be exercised from the quota-sync path"
);
assert.equal(result.refreshed, false, "no proactive refresh happened");
assert.equal(
result.connection.accessToken,
"existing-access-token",
"the current access_token is reused for the quota fetch"
);
} finally {
exec.needsRefresh = origNeeds;
exec.refreshCredentials = origRefresh;
}
});
test("non-rotating OAuth provider is still refreshed proactively from quota-sync (gate is not over-broad)", async () => {
const exec = await getExecutor("cursor");
const origNeeds = exec.needsRefresh;
const origRefresh = exec.refreshCredentials;
let refreshCalls = 0;
exec.needsRefresh = () => true;
// Refresh returns null with no usable access token left -> surfaces a 401;
// proves the refresh was actually attempted (the codex no-rotate gate did not
// block this non-rotating provider). Note: since #4786 a *present* accessToken
// would gracefully fall back for any OAuth provider, so we leave it empty here
// to keep the 401-throw branch reachable and the gate assertion meaningful.
exec.refreshCredentials = async () => {
refreshCalls++;
return null;
};
try {
await assert.rejects(
refreshAndUpdateCredentials({
id: "cursor-1",
provider: "cursor",
accessToken: "",
refreshToken: "r",
tokenExpiresAt: new Date(2000).toISOString(),
providerSpecificData: {},
}),
"a non-rotating provider with a failed refresh should surface the 401"
);
assert.equal(
refreshCalls,
1,
"non-rotating provider must still attempt the proactive refresh"
);
} finally {
exec.needsRefresh = origNeeds;
exec.refreshCredentials = origRefresh;
}
});