Skip to content
Source
Sign inSign up
Open History to see the last commit

codexUsageLimits.test.ts

315 lines9.9 KB

1import { ProviderDriverKind, ProviderInstanceId } from "@t3tools/contracts";
2import { exhaustedUsageWindow } from "@t3tools/shared/usageLimits";
3import * as CodexErrors from "effect-codex-app-server/errors";
4import { describe, expect, it } from "vite-plus/test";
5​
6import {
7 codexRateLimitsFailureMessage,
8 codexRateLimitsToLimits,
9 codexRateLimitsToUpdate,
10 mergeCodexRateLimits,
11} from "./codexUsageLimits.ts";
12​
13const checkedAt = "2026-07-18T10:00:00.000Z";
14​
15describe("codexRateLimitsToLimits", () => {
16 it("shows an Enterprise monthly credit allowance in reads and notifications without a duration", () => {
17 const snapshot = {
18 limitId: "codex",
19 planType: "enterprise_cbp_usage_based",
20 primary: null,
21 secondary: null,
22 individualLimit: { remainingPercent: 99, resetsAt: 1_784_500_000 },
23 };
24 const windows = [
25 {
26 id: "individual",
27 kind: "monthly",
28 label: "Monthly credit limit",
29 usedPercent: 1,
30 resetsAt: "2026-07-19T22:26:40.000Z",
31 },
32 ];
33​
34 expect(
35 codexRateLimitsToLimits({
36 checkedAt,
37 snapshot: { limitId: "spark" },
38 rateLimitsByLimitId: { codex: snapshot },
39 }),
40 ).toEqual({ checkedAt, windows });
41 expect(codexRateLimitsToUpdate(snapshot)).toEqual({ windows });
42 });
43​
44 it("keeps a seat-based workspace with a zero credit cap usable while its plan windows have room", () => {
45 const usageLimits = codexRateLimitsToLimits({
46 checkedAt,
47 snapshot: {
48 limitId: "codex",
49 planType: "team",
50 rateLimitReachedType: null,
51 primary: { usedPercent: 0, resetsAt: 1_784_000_000, windowDurationMins: 300 },
52 secondary: { usedPercent: 14, resetsAt: 1_784_500_000, windowDurationMins: 10080 },
53 individualLimit: { remainingPercent: 0, resetsAt: 1_796_083_200 },
54 },
55 });
56 expect(
57 exhaustedUsageWindow(
58 {
59 instanceId: ProviderInstanceId.make("codex"),
60 driver: ProviderDriverKind.make("codex"),
61 enabled: true,
62 installed: true,
63 version: null,
64 status: "ready",
65 auth: { status: "authenticated" },
66 checkedAt,
67 models: [],
68 slashCommands: [],
69 skills: [],
70 usageLimits,
71 },
72 Date.parse(checkedAt),
73 "gpt-6.1-sol",
74 ),
75 ).toBeUndefined();
76 });
77​
78 it("maps primary and secondary onto the session and weekly windows", () => {
79 expect(
80 codexRateLimitsToLimits({
81 checkedAt,
82 snapshot: {
83 planType: "plus",
84 primary: { usedPercent: 12, resetsAt: 1_784_000_000, windowDurationMins: 300 },
85 secondary: { usedPercent: 47, resetsAt: 1_784_500_000, windowDurationMins: 10080 },
86 },
87 }),
88 ).toEqual({
89 checkedAt,
90 windows: [
91 {
92 id: "primary",
93 kind: "session",
94 label: "Session",
95 usedPercent: 12,
96 windowDurationMins: 300,
97 resetsAt: "2026-07-14T03:33:20.000Z",
98 },
99 {
100 id: "secondary",
101 kind: "weekly",
102 label: "Weekly",
103 usedPercent: 47,
104 windowDurationMins: 10080,
105 resetsAt: "2026-07-19T22:26:40.000Z",
106 },
107 ],
108 });
109 });
110​
111 it("treats a lone duration-less primary as monthly on Free and Go", () => {
112 expect(
113 codexRateLimitsToLimits({
114 checkedAt,
115 snapshot: { planType: "free", primary: { usedPercent: 80, resetsAt: null } },
116 }).windows,
117 ).toEqual([
118 {
119 id: "primary",
120 kind: "monthly",
121 label: "Monthly",
122 usedPercent: 80,
123 windowDurationMins: 43_200,
124 },
125 ]);
126 });
127​
128 it("selects the main Codex allowance and leaves Spark out", () => {
129 const spark = {
130 limitId: "codex_bengalfox",
131 individualLimit: { remainingPercent: 99, resetsAt: 1_784_500_000 },
132 primary: { usedPercent: 0, windowDurationMins: 300 },
133 secondary: { usedPercent: 90, windowDurationMins: 10080 },
134 };
135 expect(
136 codexRateLimitsToLimits({
137 checkedAt,
138 snapshot: spark,
139 rateLimitsByLimitId: {
140 codex_bengalfox: spark,
141 codex: { secondary: { usedPercent: 42, windowDurationMins: 10080 } },
142 },
143 }).windows,
144 ).toEqual([
145 {
146 id: "secondary",
147 kind: "weekly",
148 label: "Weekly",
149 usedPercent: 42,
150 windowDurationMins: 10080,
151 },
152 ]);
153 });
154​
155 it.each([undefined, null, {}])(
156 "supports legacy reads with no bucket map: %j",
157 (rateLimitsByLimitId) => {
158 const snapshot = { primary: { usedPercent: 12, windowDurationMins: 300 } };
159 expect(codexRateLimitsToLimits({ checkedAt, snapshot, rateLimitsByLimitId })).toEqual(
160 codexRateLimitsToLimits({ checkedAt, snapshot }),
161 );
162 },
163 );
164});
165​
166describe("codexRateLimitsToUpdate", () => {
167 it("does not republish cached windows over a newer account probe", () => {
168 const cached = {
169 primary: { usedPercent: 0, resetsAt: 1_784_000_000, windowDurationMins: 300 },
170 secondary: { usedPercent: 20, resetsAt: 1_784_500_000, windowDurationMins: 10080 },
171 };
172 expect(codexRateLimitsToUpdate(cached)?.windows).toHaveLength(2);
173 expect(codexRateLimitsToUpdate(structuredClone(cached), cached)).toBeUndefined();
174​
175 const changed = { ...cached, secondary: { ...cached.secondary, usedPercent: 21 } };
176 expect(codexRateLimitsToUpdate(changed, cached)?.windows.map((window) => window.id)).toEqual([
177 "secondary",
178 ]);
179 expect(codexRateLimitsToUpdate({ primary: cached.primary }, cached)).toBeUndefined();
180 expect(
181 codexRateLimitsToUpdate({ primary: { ...cached.primary, resetsAt: 1_784_001_000 } }, cached)
182 ?.windows,
183 ).toHaveLength(1);
184 expect(
185 codexRateLimitsToUpdate({ primary: { ...cached.primary, usedPercent: 76 } }, cached)
186 ?.windows[0]?.usedPercent,
187 ).toBe(76);
188 });
189​
190 it("carries only the windows the notification names", () => {
191 expect(
192 codexRateLimitsToUpdate({
193 secondary: { usedPercent: 51, windowDurationMins: 10080 },
194 }),
195 ).toEqual({
196 windows: [
197 {
198 id: "secondary",
199 kind: "weekly",
200 label: "Weekly",
201 usedPercent: 51,
202 windowDurationMins: 10080,
203 },
204 ],
205 });
206 expect(codexRateLimitsToUpdate({ planType: "plus" })).toBeUndefined();
207 });
208​
209 it("ignores Spark notifications so they cannot overwrite the main allowance", () => {
210 expect(
211 codexRateLimitsToUpdate({
212 limitId: "codex_bengalfox",
213 individualLimit: { remainingPercent: 99, resetsAt: 1_784_500_000 },
214 primary: { usedPercent: 0, windowDurationMins: 300 },
215 secondary: { usedPercent: 90, windowDurationMins: 10080 },
216 }),
217 ).toBeUndefined();
218 expect(
219 codexRateLimitsToUpdate({
220 limitId: "codex",
221 secondary: { usedPercent: 42, windowDurationMins: 10080 },
222 })?.windows,
223 ).toEqual([
224 {
225 id: "secondary",
226 kind: "weekly",
227 label: "Weekly",
228 usedPercent: 42,
229 windowDurationMins: 10080,
230 },
231 ]);
232 });
233});
234​
235describe("codexRateLimitsFailureMessage", () => {
236 it("keeps the JSON-RPC code and nothing else from a request failure", () => {
237 expect(
238 codexRateLimitsFailureMessage(
239 new CodexErrors.CodexAppServerRequestError({
240 code: -32603,
241 errorMessage:
242 "failed to fetch codex rate limits: GET https://chatgpt.com/backend-api/wham/usage failed: 401 Unauthorized",
243 }),
244 ),
245 ).toBe("Codex could not read usage (JSON-RPC -32603).");
246 });
247});
248​
249describe("mergeCodexRateLimits", () => {
250 it("updates monthly credit usage, retains it when omitted, and clears an explicit null", () => {
251 const snapshot = {
252 limitId: "codex",
253 primary: { usedPercent: 12 },
254 individualLimit: { remainingPercent: 99, resetsAt: 1_784_500_000 },
255 };
256 const individualLimit = { remainingPercent: 75, resetsAt: 1_787_000_000 };
257 const changed = mergeCodexRateLimits(snapshot, { individualLimit });
258 expect(changed).toEqual({ ...snapshot, individualLimit });
259 expect(codexRateLimitsToUpdate(changed!)).toEqual({
260 windows: [
261 {
262 id: "primary",
263 kind: "session",
264 label: "Session",
265 usedPercent: 12,
266 windowDurationMins: 300,
267 },
268 {
269 id: "individual",
270 kind: "monthly",
271 label: "Monthly credit limit",
272 usedPercent: 25,
273 resetsAt: "2026-08-17T20:53:20.000Z",
274 },
275 ],
276 });
277​
278 const retained = mergeCodexRateLimits(changed, { primary: null });
279 expect(retained).toEqual({ ...snapshot, primary: null, individualLimit });
280 const cleared = mergeCodexRateLimits(retained, { individualLimit: null });
281 expect(cleared).toEqual({ ...snapshot, primary: null, individualLimit: null });
282 expect(codexRateLimitsToLimits({ checkedAt, snapshot: cleared! }).windows).toEqual([]);
283 });
284​
285 it("ignores a model-specific snapshot so it cannot replace the main allowance", () => {
286 const main = {
287 limitId: "codex",
288 primary: { usedPercent: 100, resetsAt: 1_800_000_000, windowDurationMins: 300 },
289 };
290 expect(
291 mergeCodexRateLimits(main, {
292 limitId: "spark",
293 individualLimit: { remainingPercent: 99, resetsAt: 1_784_500_000 },
294 primary: { usedPercent: 3, resetsAt: 1_800_000_000, windowDurationMins: 300 },
295 }),
296 ).toBe(main);
297 });
298});
299​
300import { codexUsageLimitResetAt } from "./codexUsageLimits.ts";
301​
302describe("codexUsageLimitResetAt", () => {
303 it("waits for every exhausted window and never invents an unknown reset", () => {
304 expect(
305 codexUsageLimitResetAt({
306 primary: { usedPercent: 100, resetsAt: 2000000000 },
307 secondary: { usedPercent: 100, resetsAt: 2000100000 },
308 }),
309 ).toBe("2033-05-19T07:20:00.000Z");
310 expect(codexUsageLimitResetAt({ primary: { usedPercent: 100 } })).toBeNull();
311 expect(
312 codexUsageLimitResetAt({ primary: { usedPercent: 50, resetsAt: 2000000000 } }),
313 ).toBeNull();
314 });
315});