refactor(cli): improve quota reset formatting robustness and add unit tests

This commit is contained in:
Spencer
2026-02-20 22:15:05 +00:00
parent 7abb4f5770
commit 0ae29ab780
3 changed files with 67 additions and 17 deletions
@@ -10,9 +10,60 @@ import {
formatBytes,
formatTimeAgo,
stripReferenceContent,
formatResetTime,
} from './formatters.js';
describe('formatters', () => {
describe('formatResetTime', () => {
const NOW = new Date('2026-02-20T12:00:00Z');
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(NOW);
});
afterEach(() => {
vi.useRealTimers();
});
it('should format ISO date strings', () => {
const resetTime = '2026-02-20T13:30:00Z'; // 1h 30m from now
expect(formatResetTime(resetTime)).toBe('resets in 1h 30m');
});
it('should format unix timestamps (milliseconds)', () => {
const resetTime = (NOW.getTime() + 65 * 60 * 1000).toString(); // 1h 5m
expect(formatResetTime(resetTime)).toBe('resets in 1h 5m');
});
it('should format unix timestamps (seconds)', () => {
const resetTime = (Math.floor(NOW.getTime() / 1000) + 120).toString(); // 2m
expect(formatResetTime(resetTime)).toBe('resets in 2m');
});
it('should return "Resetting..." for past times', () => {
const resetTime = '2026-02-20T11:59:59Z';
expect(formatResetTime(resetTime)).toBe('Resetting...');
});
it('should return "< 1m" for imminent resets', () => {
const resetTime = new Date(NOW.getTime() + 30 * 1000).toISOString();
expect(formatResetTime(resetTime)).toBe('< 1m');
});
it('should support capitalization and prefix options', () => {
const resetTime = '2026-02-20T14:00:00Z'; // 2h
expect(
formatResetTime(resetTime, { capitalize: true, includePrefix: true }),
).toBe('Resets in 2h');
expect(formatResetTime(resetTime, { includePrefix: false })).toBe('2h');
});
it('should return empty string for invalid dates', () => {
expect(formatResetTime('not-a-date')).toBe('');
});
});
describe('formatBytes', () => {
it('should format bytes into KB', () => {
expect(formatBytes(12345)).toBe('12.1 KB');
+6 -13
View File
@@ -125,23 +125,16 @@ export const formatResetTime = (
const diff = date.getTime() - Date.now();
if (diff <= 0) return 'Resetting...';
const totalMinutes = Math.ceil(diff / (1000 * 60));
const totalMinutes = Math.floor(diff / (1000 * 60));
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
const fmt = (val: number, unit: 'hour' | 'minute') =>
new Intl.RelativeTimeFormat('en', {
style: 'narrow',
numeric: 'always',
})
.formatToParts(val, unit)
.filter(
(p) =>
p.type !== 'literal' || (p.value !== 'in ' && p.value !== ' ago'),
)
.map((p) => p.value)
.join('')
.trim();
new Intl.NumberFormat('en', {
style: 'unit',
unit,
unitDisplay: 'narrow',
}).format(val);
let timeStr = '';
if (hours > 0 && minutes > 0) {
+10 -4
View File
@@ -68,7 +68,9 @@ import { ideContextStore } from '../ide/ideContext.js';
import { WriteTodosTool } from '../tools/write-todos.js';
import type { FileSystemService } from '../services/fileSystemService.js';
import { StandardFileSystemService } from '../services/fileSystemService.js';
import { logRipgrepFallback, logFlashFallback ,
import {
logRipgrepFallback,
logFlashFallback,
logApprovalModeSwitch,
logApprovalModeDuration,
} from '../telemetry/loggers.js';
@@ -106,7 +108,11 @@ import type { EventEmitter } from 'node:events';
import { PolicyEngine } from '../policy/policy-engine.js';
import { ApprovalMode, type PolicyEngineConfig } from '../policy/types.js';
import { HookSystem } from '../hooks/index.js';
import type { UserTierId , RetrieveUserQuotaResponse , AdminControlsSettings } from '../code_assist/types.js';
import type {
UserTierId,
RetrieveUserQuotaResponse,
AdminControlsSettings,
} from '../code_assist/types.js';
import type { HierarchicalMemory } from './memory.js';
import { getCodeAssistServer } from '../code_assist/codeAssist.js';
import type { Experiments } from '../code_assist/experiments/experiments.js';
@@ -1335,8 +1341,8 @@ export class Config {
// For reset time, take the one that is furthest in the future (most conservative)
const resetTime = [proQuota?.resetTime, flashQuota?.resetTime]
.filter((t): t is string => !!t)
.sort()
.reverse()[0];
.map((t) => ({ original: t, date: new Date(t) }))
.sort((a, b) => b.date.getTime() - a.date.getTime())[0]?.original;
return {
remaining: (proQuota?.remaining ?? 0) + (flashQuota?.remaining ?? 0),