mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-04 06:01:15 -07:00
refactor(cli): improve quota reset formatting robustness and add unit tests
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user