Skip to content

Commit 49505d3

Browse files
committed
refactor: stabilize billing reset formatting
1 parent 5bdc1ae commit 49505d3

2 files changed

Lines changed: 36 additions & 19 deletions

File tree

src/provider/billing.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface BillingUsage {
1616
weekly?: WeeklyUsage;
1717
}
1818

19-
const RESET_FORMATTER = new Intl.DateTimeFormat(undefined, {
19+
const RESET_FORMATTER = new Intl.DateTimeFormat('en-US', {
2020
month: 'short',
2121
day: 'numeric',
2222
hour: '2-digit',
@@ -25,7 +25,7 @@ const RESET_FORMATTER = new Intl.DateTimeFormat(undefined, {
2525
timeZoneName: 'short',
2626
});
2727

28-
export const LOCAL_TIME_ZONE = RESET_FORMATTER.resolvedOptions().timeZone;
28+
const LOCAL_TIME_ZONE = RESET_FORMATTER.resolvedOptions().timeZone;
2929

3030
const billingHeaders = (token: string) => ({
3131
authorization: `Bearer ${token}`,

tests/provider/register.test.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { tmpdir } from 'node:os';
33
import { join } from 'node:path';
44
import type { Api, Model, OAuthCredentials } from '@earendil-works/pi-ai';
55
import type { ExtensionAPI, ProviderConfig } from '@earendil-works/pi-coding-agent';
6-
import { afterEach, describe, expect, it, vi } from 'vitest';
7-
import { LOCAL_TIME_ZONE } from '../../src/provider/billing.js';
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
87
import { GROK_SHIM_TOOL_NAMES, grokToolsToActivate } from '../../src/tools/register.js';
98
import * as webSearchDelegate from '../../src/tools/webSearchDelegate.js';
109

@@ -81,9 +80,14 @@ type ExtensionHandler = (event: unknown, ctx: TestContext) => unknown;
8180

8281
const originalFetch = globalThis.fetch;
8382
const originalHome = process.env.HOME;
83+
const originalTimeZone = process.env.TZ;
8484
const originalToken = process.env.GROK_CLI_OAUTH_TOKEN;
8585
const tempDirs: string[] = [];
8686

87+
beforeEach(() => {
88+
process.env.TZ = 'America/New_York';
89+
});
90+
8791
afterEach(() => {
8892
vi.resetModules();
8993
streamSimpleOpenAIResponses.mockClear();
@@ -93,6 +97,11 @@ afterEach(() => {
9397
} else {
9498
process.env.HOME = originalHome;
9599
}
100+
if (originalTimeZone === undefined) {
101+
delete process.env.TZ;
102+
} else {
103+
process.env.TZ = originalTimeZone;
104+
}
96105
if (originalToken === undefined) {
97106
delete process.env.GROK_CLI_OAUTH_TOKEN;
98107
} else {
@@ -211,11 +220,6 @@ const billingFetchMock = (monthly: Response, credits: Response) =>
211220
return url.includes('format=credits') ? credits : monthly;
212221
});
213222

214-
function expectReset(status: string) {
215-
expect(status).toMatch(/Reset\s+.+\s(?:GMT[+-]\d+(?::\d+)?|UTC|[A-Z]{2,5})\s\S+/);
216-
expect(status).toContain(LOCAL_TIME_ZONE);
217-
}
218-
219223
async function runStatus(extension: Awaited<ReturnType<typeof setupExtension>>) {
220224
const notify = vi.fn();
221225
await extension.commands.get('grok-cli-usage')?.handler([], statusContext(notify));
@@ -245,18 +249,26 @@ describe('Grok CLI status command', () => {
245249
accept: 'application/json',
246250
});
247251
expect(fetchMock.mock.calls[0]?.[1]?.headers).not.toHaveProperty('x-userid');
248-
const status = String(notify.mock.calls.at(-1)?.[0]);
249-
expect(status).toContain('1,421 / 4,000 used 36%');
250-
expect(status).toContain('2,579 credits');
251-
expect(status).toContain('1% used');
252-
expectReset(status);
252+
expect(notify.mock.calls.at(-1)?.[0]).toBe(
253+
[
254+
' Usage:',
255+
' Monthly',
256+
' Credits 1,421 / 4,000 used 36%',
257+
' Remaining 2,579 credits',
258+
' Reset Jun 30, 20:00 EDT America/New_York',
259+
'',
260+
' Weekly',
261+
' Limit 1% used',
262+
' Reset Jul 13, 20:19 EDT America/New_York',
263+
].join('\n'),
264+
);
253265
});
254266

255267
it('omits the weekly block when the credits endpoint is unavailable', async () => {
256268
process.env.GROK_CLI_OAUTH_TOKEN = 'env-token';
257269
setupHome();
258270
const fetchMock = billingFetchMock(
259-
billingResponse(4000, 172, '2026-08-01T00:00:00+00:00'),
271+
billingResponse(4000, 172, '2026-01-01T00:00:00+00:00'),
260272
new Response('nope', { status: 500 }),
261273
);
262274
globalThis.fetch = fetchMock;
@@ -266,10 +278,15 @@ describe('Grok CLI status command', () => {
266278
expect(fetchMock.mock.calls[1]?.[0]).toBe(
267279
'https://cli-chat-proxy.grok.com/v1/billing?format=credits',
268280
);
269-
const status = String(notify.mock.calls.at(-1)?.[0]);
270-
expect(status).toContain('172 / 4,000 used 4%');
271-
expect(status).toContain('3,828 credits');
272-
expectReset(status);
281+
expect(notify.mock.calls.at(-1)?.[0]).toBe(
282+
[
283+
' Usage:',
284+
' Monthly',
285+
' Credits 172 / 4,000 used 4%',
286+
' Remaining 3,828 credits',
287+
' Reset Dec 31, 19:00 EST America/New_York',
288+
].join('\n'),
289+
);
273290
});
274291

275292
it('uses the registered provider token when no env token is set', async () => {

0 commit comments

Comments
 (0)