262 lines
7.8 KiB
TypeScript
262 lines
7.8 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
fetchCodexQuota,
|
|
getCodexQuotaCooldownMs,
|
|
invalidateCodexQuotaCache,
|
|
registerCodexConnection,
|
|
registerCodexQuotaFetcher,
|
|
} from "../../open-sse/services/codexQuotaFetcher.ts";
|
|
import { preflightQuota } from "../../open-sse/services/quotaPreflight.ts";
|
|
import {
|
|
clearQuotaMonitors,
|
|
getActiveMonitorCount,
|
|
startQuotaMonitor,
|
|
stopQuotaMonitor,
|
|
} from "../../open-sse/services/quotaMonitor.ts";
|
|
import { clearSessions, touchSession } from "../../open-sse/services/sessionManager.ts";
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
test.afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
clearQuotaMonitors();
|
|
clearSessions();
|
|
});
|
|
|
|
test("fetchCodexQuota returns null when no registered credentials exist", async () => {
|
|
const quota = await fetchCodexQuota(`missing-${Date.now()}`);
|
|
assert.equal(quota, null);
|
|
});
|
|
|
|
test("fetchCodexQuota can read credentials directly from the provided connection snapshot", async () => {
|
|
const connectionId = `codex-inline-${Date.now()}`;
|
|
const calls = [];
|
|
|
|
globalThis.fetch = async (url, init) => {
|
|
calls.push({ url, init });
|
|
return new Response(
|
|
JSON.stringify({
|
|
rate_limit: {
|
|
primary_window: {
|
|
used_percent: 70,
|
|
reset_after_seconds: 45,
|
|
},
|
|
secondary_window: {
|
|
used_percent: 20,
|
|
reset_after_seconds: 300,
|
|
},
|
|
},
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}
|
|
);
|
|
};
|
|
|
|
const quota = await fetchCodexQuota(connectionId, {
|
|
accessToken: "inline-token",
|
|
providerSpecificData: {
|
|
workspaceId: "workspace-inline",
|
|
},
|
|
});
|
|
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].init.headers.Authorization, "Bearer inline-token");
|
|
assert.equal(calls[0].init.headers["chatgpt-account-id"], "workspace-inline");
|
|
assert.equal(quota.percentUsed, 0.7);
|
|
assert.ok(typeof quota.resetAt === "string");
|
|
|
|
invalidateCodexQuotaCache(connectionId);
|
|
});
|
|
|
|
test("fetchCodexQuota parses dual-window usage, forwards workspace headers, and caches results", async () => {
|
|
const connectionId = `codex-cache-${Date.now()}`;
|
|
const calls = [];
|
|
|
|
registerCodexConnection(connectionId, {
|
|
accessToken: "access-token",
|
|
workspaceId: "workspace-123",
|
|
});
|
|
|
|
globalThis.fetch = async (url, init) => {
|
|
calls.push({ url, init });
|
|
return new Response(
|
|
JSON.stringify({
|
|
rate_limit: {
|
|
primary_window: {
|
|
used_percent: 80,
|
|
reset_after_seconds: 60,
|
|
},
|
|
secondary_window: {
|
|
used_percent: 40,
|
|
reset_at: Math.floor((Date.now() + 3600_000) / 1000),
|
|
},
|
|
},
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}
|
|
);
|
|
};
|
|
|
|
const first = await fetchCodexQuota(connectionId);
|
|
const second = await fetchCodexQuota(connectionId);
|
|
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].url, "https://chatgpt.com/backend-api/wham/usage");
|
|
assert.equal(calls[0].init.headers.Authorization, "Bearer access-token");
|
|
assert.equal(calls[0].init.headers["chatgpt-account-id"], "workspace-123");
|
|
assert.equal(first.percentUsed, 0.8);
|
|
assert.equal(first.window5h.percentUsed, 0.8);
|
|
assert.equal(first.window7d.percentUsed, 0.4);
|
|
assert.deepEqual(second, first);
|
|
|
|
invalidateCodexQuotaCache(connectionId);
|
|
});
|
|
|
|
test("fetchCodexQuota evaluates normal and Spark windows independently by requested model", async () => {
|
|
const connectionId = `codex-spark-scope-${Date.now()}`;
|
|
let calls = 0;
|
|
|
|
registerCodexConnection(connectionId, {
|
|
accessToken: "access-token-spark",
|
|
});
|
|
|
|
globalThis.fetch = async () => {
|
|
calls++;
|
|
return new Response(
|
|
JSON.stringify({
|
|
rate_limit: {
|
|
primary_window: { used_percent: 20, reset_after_seconds: 60 },
|
|
secondary_window: { used_percent: 30, reset_after_seconds: 120 },
|
|
},
|
|
additional_rate_limits: [
|
|
{
|
|
limit_id: "codex_bengalfox",
|
|
limit_name: "GPT-5.3-Codex-Spark",
|
|
metered_feature: "gpt_5_3_codex_spark",
|
|
rate_limit: {
|
|
primary_window: { used_percent: 100, reset_after_seconds: 300 },
|
|
secondary_window: { used_percent: 40, reset_after_seconds: 600 },
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}
|
|
);
|
|
};
|
|
|
|
const normal = await fetchCodexQuota(connectionId, { requestedModel: "gpt-5.3-codex" });
|
|
const spark = await fetchCodexQuota(connectionId, { requestedModel: "gpt-5.3-codex-spark" });
|
|
|
|
assert.equal(calls, 2, "normal and Spark scopes use separate cache entries");
|
|
assert.equal(normal.percentUsed, 0.3);
|
|
assert.equal(normal.windows?.session.percentUsed, 0.2);
|
|
assert.equal(normal.windows?.weekly.percentUsed, 0.3);
|
|
assert.equal(normal.windows?.gpt_5_3_codex_spark_session, undefined);
|
|
assert.equal(spark.percentUsed, 1);
|
|
assert.equal(spark.windows?.gpt_5_3_codex_spark_session.percentUsed, 1);
|
|
assert.equal(spark.windows?.gpt_5_3_codex_spark_weekly.percentUsed, 0.4);
|
|
assert.equal(spark.windows?.session, undefined);
|
|
|
|
const sparkCached = await fetchCodexQuota(connectionId, {
|
|
requestedModel: "gpt-5.3-codex-spark",
|
|
});
|
|
assert.equal(calls, 2);
|
|
assert.deepEqual(sparkCached, spark);
|
|
|
|
invalidateCodexQuotaCache(connectionId);
|
|
});
|
|
|
|
test("fetchCodexQuota drops bad credentials after an authorization failure", async () => {
|
|
const connectionId = `codex-auth-${Date.now()}`;
|
|
let calls = 0;
|
|
|
|
registerCodexConnection(connectionId, {
|
|
accessToken: "expired-token",
|
|
});
|
|
|
|
globalThis.fetch = async () => {
|
|
calls++;
|
|
return new Response("unauthorized", { status: 401 });
|
|
};
|
|
|
|
const first = await fetchCodexQuota(connectionId);
|
|
const second = await fetchCodexQuota(connectionId);
|
|
|
|
assert.equal(first, null);
|
|
assert.equal(second, null);
|
|
assert.equal(calls, 1);
|
|
});
|
|
|
|
test("getCodexQuotaCooldownMs prefers the 7d window before the 5h window", () => {
|
|
const now = Date.now();
|
|
const quota = {
|
|
used: 99,
|
|
total: 100,
|
|
percentUsed: 0.99,
|
|
window5h: {
|
|
percentUsed: 0.99,
|
|
resetAt: new Date(now + 60_000).toISOString(),
|
|
},
|
|
window7d: {
|
|
percentUsed: 0.97,
|
|
resetAt: new Date(now + 300_000).toISOString(),
|
|
},
|
|
limitReached: false,
|
|
};
|
|
|
|
const cooldownMs = getCodexQuotaCooldownMs(quota);
|
|
|
|
assert.ok(cooldownMs >= 295_000);
|
|
assert.ok(cooldownMs <= 300_000);
|
|
});
|
|
|
|
test("registerCodexQuotaFetcher exposes Codex quota to preflight and monitor flows", async () => {
|
|
const connectionId = `codex-preflight-${Date.now()}`;
|
|
|
|
registerCodexQuotaFetcher();
|
|
registerCodexConnection(connectionId, {
|
|
accessToken: "quota-token",
|
|
});
|
|
|
|
// Use 100% (fully exhausted) to avoid floating-point boundary issues:
|
|
// (1 - 0.98) * 100 = 2.0000000000000018, which is > DEFAULT_MIN_REMAINING_PERCENT (2),
|
|
// so the preflight wouldn't block. 100% used → 0% remaining, clearly below 2%.
|
|
globalThis.fetch = async () =>
|
|
new Response(
|
|
JSON.stringify({
|
|
rate_limit: {
|
|
primary_window: { used_percent: 100, reset_after_seconds: 90 },
|
|
},
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}
|
|
);
|
|
|
|
const preflight = await preflightQuota("codex", connectionId, {
|
|
providerSpecificData: { quotaPreflightEnabled: true },
|
|
});
|
|
|
|
touchSession("session-codex", connectionId);
|
|
startQuotaMonitor("session-codex", "codex", connectionId, {
|
|
providerSpecificData: { quotaMonitorEnabled: true },
|
|
});
|
|
|
|
assert.equal(preflight.proceed, false);
|
|
assert.equal(preflight.reason, "quota_exhausted");
|
|
assert.equal(getActiveMonitorCount(), 1);
|
|
|
|
stopQuotaMonitor("session-codex");
|
|
assert.equal(getActiveMonitorCount(), 0);
|
|
});
|