1import {
2 EnvironmentId,
3 ProviderDriverKind,
4 ProviderInstanceId,
5 UsageLimitSourceId,
6 type ServerProvider,
7} from "@t3tools/contracts";
8import { describe, expect, it, vi } from "vite-plus/test";
9import {
10 buildSubscriptionUsageSnapshot,
11 createWidgetRefresher,
12 WIDGET_REFRESH_INTERVAL,
13 subscriptionUsageTimeline,
14} from "./subscriptionUsageSnapshot";
15
16const checkedAt = "2026-09-05T12:00:00.000Z";
17const now = Date.parse(checkedAt);
18const window = {
19 id: "session",
20 kind: "session",
21 label: "5 hours",
22 usedPercent: 40,
23 resetsAt: "2026-09-05T12:10:00.000Z",
24} as const;
25const limits = { checkedAt, windows: [window] };
26const deepLink = "t3code-dev://settings/usage?tab=limits";
27function provider(overrides: Partial<ServerProvider> = {}): ServerProvider {
28 return {
29 instanceId: ProviderInstanceId.make("codex"),
30 driver: ProviderDriverKind.make("codex"),
31 enabled: true,
32 installed: true,
33 version: null,
34 status: "ready",
35 auth: { status: "authenticated", email: "private@example.com" },
36 checkedAt,
37 models: [],
38 slashCommands: [],
39 skills: [],
40 usageLimits: limits,
41 ...overrides,
42 };
43}
44function presentations(providers: readonly ServerProvider[] = [provider()]) {
45 return new Map([
46 [
47 EnvironmentId.make("env"),
48 { entry: { target: { label: "Remote" } }, serverConfig: { providers } },
49 ],
50 ]);
51}
52
53describe("subscription widget snapshots", () => {
54 it("uses provider data and its observation time without exposing account emails", () => {
55 const snapshot = buildSubscriptionUsageSnapshot(
56 presentations([provider({ displayName: "private@example.com" })]),
57 deepLink,
58 );
59 expect(snapshot.checkedAt).toBe(now);
60 expect(snapshot.providers[0]).toMatchObject({
61 name: "Codex",
62 windows: [{ remaining: 60 }],
63 expiresAt: now + 10 * 60_000,
64 });
65 expect(snapshot.url).toBe(deepLink);
66 expect(JSON.stringify(snapshot)).not.toContain("private@example.com");
67 });
68 it("clears data after removing environments", () => {
69 expect(buildSubscriptionUsageSnapshot(new Map(), deepLink).providers).toEqual([]);
70 });
71 it.each<{ name: string; overrides: Partial<ServerProvider> }>([
72 { name: "disabled", overrides: { enabled: false } },
73 {
74 name: "missing",
75 overrides: { installed: false, status: "error", usageLimits: undefined },
76 },
77 {
78 name: "API-key",
79 overrides: {
80 usageLimits: { checkedAt, windows: [], unavailable: { reason: "unsupported" } },
81 },
82 },
83 ])("hides $name providers", ({ overrides }) => {
84 expect(
85 buildSubscriptionUsageSnapshot(presentations([provider(overrides)]), deepLink).providers,
86 ).toEqual([]);
87 });
88 it.each([
89 { name: "Codex", driver: "codex" },
90 { name: "Claude", driver: "claudeAgent" },
91 ])("only shows $name when $name and OpenCode are configured", ({ name, driver }) => {
92 const snapshot = buildSubscriptionUsageSnapshot(
93 presentations([
94 provider({
95 instanceId: ProviderInstanceId.make(driver),
96 driver: ProviderDriverKind.make(driver),
97 }),
98 provider({
99 instanceId: ProviderInstanceId.make("opencode"),
100 driver: ProviderDriverKind.make("opencode"),
101 usageLimits: undefined,
102 }),
103 ]),
104 deepLink,
105 );
106 expect(snapshot.providers).toHaveLength(1);
107 expect(snapshot.providers[0]).toMatchObject({
108 name,
109 totalWindows: 1,
110 windows: [{ remaining: 60 }],
111 });
112 expect(subscriptionUsageTimeline(snapshot, now + 15 * 60_000)[0]?.props.providers).toEqual([
113 expect.objectContaining({ name, totalWindows: 0, windows: [] }),
114 ]);
115 });
116 it("keeps an enabled provider visible before its first usage read", () => {
117 const snapshot = buildSubscriptionUsageSnapshot(
118 presentations([provider({ usageLimits: undefined })]),
119 deepLink,
120 );
121 expect(snapshot.checkedAt).toBe(0);
122 expect(snapshot.providers).toEqual([
123 { name: "Codex", detail: "No limits available", windows: [], expiresAt: 0, totalWindows: 0 },
124 ]);
125 });
126 it("uses upstream deduplication for a native account also present in a proxy hub", () => {
127 const input = new Map([
128 [
129 EnvironmentId.make("env"),
130 {
131 entry: { target: { label: "Remote" } },
132 serverConfig: {
133 providers: [provider()],
134 usageLimitSources: [
135 {
136 id: UsageLimitSourceId.make("hub"),
137 kind: "cliproxy" as const,
138 label: "Hub",
139 checkedAt,
140 accounts: [
141 {
142 id: "account",
143 driver: ProviderDriverKind.make("codex"),
144 email: " PRIVATE@example.com ",
145 usageLimits: limits,
146 },
147 ],
148 },
149 ],
150 },
151 },
152 ],
153 ]);
154 expect(buildSubscriptionUsageSnapshot(input, deepLink).providers[0]?.detail).toBe(
155 "Subscription remaining",
156 );
157 input.get(EnvironmentId.make("env"))!.serverConfig.providers = [];
158 const snapshot = buildSubscriptionUsageSnapshot(input, deepLink);
159 expect(snapshot.providers[0]?.name).toBe("Codex");
160 expect(JSON.stringify(snapshot)).not.toContain("example.com");
161 });
162 it("keeps unavailable quotas distinct from zero usage and omits provider error messages", () => {
163 const snapshot = buildSubscriptionUsageSnapshot(
164 presentations([
165 provider({
166 usageLimits: {
167 ...limits,
168 unavailable: { reason: "probeFailed", message: "token secret" },
169 },
170 }),
171 ]),
172 deepLink,
173 );
174 expect(snapshot.providers[0]?.windows).toEqual([]);
175 expect(JSON.stringify(snapshot)).not.toContain("token secret");
176 expect(
177 buildSubscriptionUsageSnapshot(
178 presentations([
179 provider({ usageLimits: { checkedAt, windows: [{ ...window, usedPercent: 0 }] } }),
180 ]),
181 deepLink,
182 ).providers[0]?.windows[0]?.remaining,
183 ).toBe(100);
184 });
185 it("bounds OS storage and puts the most constrained windows first", () => {
186 const windows = Array.from({ length: 20 }, (_, index) => ({
187 ...window,
188 id: `${index}`,
189 usedPercent: index * 5,
190 }));
191 const snapshot = buildSubscriptionUsageSnapshot(
192 presentations([provider({ usageLimits: { checkedAt, windows } })]),
193 deepLink,
194 );
195 expect(snapshot.providers[0]?.windows).toHaveLength(6);
196 expect(snapshot.providers[0]?.totalWindows).toBe(20);
197 expect(snapshot.providers[0]?.windows[0]?.remaining).toBe(5);
198 });
199 it("includes every limit for the scrollable Android widget", () => {
200 const windows = Array.from({ length: 20 }, (_, index) => ({
201 ...window,
202 id: `${index}`,
203 usedPercent: index * 5,
204 }));
205 const snapshot = buildSubscriptionUsageSnapshot(
206 presentations([provider({ usageLimits: { checkedAt, windows } })]),
207 deepLink,
208 Infinity,
209 );
210 expect(snapshot.providers[0]?.windows.map((window) => window.remaining)).toEqual(
211 Array.from({ length: 20 }, (_, index) => 5 + index * 5),
212 );
213 });
214 it("marks unknown or distant reset times stale after fifteen minutes", () => {
215 const snapshot = buildSubscriptionUsageSnapshot(
216 presentations([
217 provider({ usageLimits: { checkedAt, windows: [{ ...window, resetsAt: undefined }] } }),
218 ]),
219 deepLink,
220 );
221 expect(snapshot.providers[0]?.expiresAt).toBe(now + 15 * 60_000);
222 expect(snapshot.providers[0]?.windows[0]?.reset).toBe("Reset time unavailable");
223 });
224 it("schedules a reset boundary without inventing a zero quota", () => {
225 const snapshot = buildSubscriptionUsageSnapshot(presentations(), deepLink);
226 const timeline = subscriptionUsageTimeline(snapshot, now);
227 expect(timeline.map((entry) => entry.date.getTime())).toEqual([now, now + 10 * 60_000]);
228 expect(timeline[1]?.props.providers[0]?.windows).toEqual([]);
229 expect(subscriptionUsageTimeline(snapshot, now + 60 * 60_000)).toHaveLength(1);
230 });
231 it("expires providers independently without inventing a refill", () => {
232 const snapshot = buildSubscriptionUsageSnapshot(
233 presentations([
234 provider(),
235 provider({
236 instanceId: ProviderInstanceId.make("claude"),
237 driver: ProviderDriverKind.make("claudeAgent"),
238 usageLimits: { checkedAt, windows: [{ ...window, resetsAt: undefined }] },
239 }),
240 ]),
241 deepLink,
242 );
243 const timeline = subscriptionUsageTimeline(snapshot, now);
244 expect(timeline.map((entry) => entry.date.getTime())).toEqual([
245 now,
246 now + 10 * 60_000,
247 now + 15 * 60_000,
248 ]);
249 expect(timeline[1]?.props.providers[0]?.windows).toEqual([]);
250 expect(timeline[1]?.props.providers[1]?.windows[0]?.remaining).toBe(60);
251 expect(timeline[2]?.props.providers.every((provider) => provider.windows.length === 0)).toBe(
252 true,
253 );
254 });
255 it("marks a malformed check time immediately stale without storing null", () => {
256 const snapshot = buildSubscriptionUsageSnapshot(
257 presentations([provider({ usageLimits: { ...limits, checkedAt: "invalid" } })]),
258 deepLink,
259 );
260 expect(snapshot.checkedAt).toBe(0);
261 expect(snapshot.providers[0]).toMatchObject({ expiresAt: 0, windows: [] });
262 expect(subscriptionUsageTimeline(snapshot, now)).toHaveLength(1);
263 expect(JSON.stringify(snapshot)).not.toContain("null");
264 });
265 it("uses the freshest copy of an account across environments before pooling", () => {
266 const input = presentations();
267 input.set(EnvironmentId.make("other"), {
268 entry: { target: { label: "Other" } },
269 serverConfig: {
270 providers: [
271 provider({
272 usageLimits: {
273 checkedAt: new Date(now + 60_000).toISOString(),
274 windows: [{ ...window, usedPercent: 80 }],
275 },
276 }),
277 ],
278 },
279 });
280 const snapshot = buildSubscriptionUsageSnapshot(input, deepLink);
281 expect(snapshot.providers[0]?.detail).toBe("Subscription remaining");
282 expect(snapshot.providers[0]?.windows[0]?.remaining).toBe(20);
283 });
284});
285
286describe("widget refresh probes", () => {
287 it("throttles each connected environment independently and retries failures", async () => {
288 const probe = vi
289 .fn<(id: string) => Promise<void>>()
290 .mockRejectedValueOnce(new Error("offline"))
291 .mockResolvedValue(undefined);
292 const refresh = createWidgetRefresher(probe);
293 await refresh([], now);
294 expect(probe).not.toHaveBeenCalled();
295 await refresh(["first"], now);
296 await refresh(["first", "second"], now + 1);
297 expect(probe.mock.calls).toEqual([["first"], ["second"]]);
298 await refresh(["first"], now + WIDGET_REFRESH_INTERVAL);
299 expect(probe.mock.calls).toEqual([["first"], ["second"], ["first"]]);
300 });
301
302 it("does not overlap a slow probe even after the refresh interval", async () => {
303 let finish!: () => void;
304 const probe = vi.fn(
305 () =>
306 new Promise<void>((resolve) => {
307 finish = resolve;
308 }),
309 );
310 const refresh = createWidgetRefresher(probe);
311 const first = refresh(["one", "one"], now);
312 await refresh(["one"], now + WIDGET_REFRESH_INTERVAL);
313 expect(probe).toHaveBeenCalledTimes(1);
314 finish();
315 await first;
316 });
317});