fix(review): harden quota retry and tooltip edge cases

This commit is contained in:
Tam Nhu Tran
2026-04-09 00:14:51 -04:00
parent b6fb443f57
commit 7ac6d0a4d4
11 changed files with 206 additions and 21 deletions
+11 -2
View File
@@ -21,9 +21,18 @@ export function getTokenExpiryTimestamp(expiredValue?: string | number | null):
return null;
}
const normalizeNumericTimestamp = (value: number): number | null => {
if (!Number.isFinite(value) || value <= 0) {
return null;
}
// Support Unix seconds from older token stores while preserving millisecond timestamps.
return value < 1_000_000_000_000 ? value * 1000 : value;
};
try {
if (typeof expiredValue === 'number') {
return Number.isFinite(expiredValue) ? expiredValue : null;
return normalizeNumericTimestamp(expiredValue);
}
const trimmed = expiredValue.trim();
@@ -33,7 +42,7 @@ export function getTokenExpiryTimestamp(expiredValue?: string | number | null):
if (/^\d+$/.test(trimmed)) {
const numericTimestamp = Number(trimmed);
return Number.isFinite(numericTimestamp) ? numericTimestamp : null;
return normalizeNumericTimestamp(numericTimestamp);
}
const expiredDate = new Date(trimmed);
+3 -3
View File
@@ -745,10 +745,9 @@ export async function fetchGeminiCliQuota(
const expiresAt = getTokenExpiryTimestamp(authData.expiresAt);
const shouldRefresh =
authData.isExpired || expiresAt === null || expiresAt - Date.now() < REFRESH_LEAD_TIME_MS;
let attemptedRefresh = false;
let refreshedBeforeQuotaFetch = false;
if (shouldRefresh) {
attemptedRefresh = true;
if (verbose)
console.error(
authData.isExpired
@@ -758,6 +757,7 @@ export async function fetchGeminiCliQuota(
const refreshResult = await refreshGeminiToken(accountId);
if (refreshResult.success) {
refreshedBeforeQuotaFetch = true;
if (verbose) console.error('[i] Token refreshed successfully');
// Re-read auth data after successful refresh
const refreshedAuthData = readGeminiCliAuthData(accountId);
@@ -784,7 +784,7 @@ export async function fetchGeminiCliQuota(
const result = await fetchWithAuthData(authData, accountId, verbose);
// Retry once with an account-scoped refresh when the quota endpoint rejects auth.
if (result.needsReauth && !attemptedRefresh) {
if (result.needsReauth && !refreshedBeforeQuotaFetch) {
if (verbose) console.error('[i] Got 401, attempting refresh and retry...');
const refreshResult = await refreshGeminiToken(accountId);
if (refreshResult.success) {
+9
View File
@@ -89,10 +89,19 @@ describe('Auth Utilities', () => {
expect(isTokenExpired(futureTimestamp)).toBe(false);
});
it('should treat Unix-seconds values as seconds, not milliseconds', () => {
const futureUnixSeconds = Math.floor((Date.now() + 60000) / 1000);
expect(isTokenExpired(futureUnixSeconds)).toBe(false);
expect(isTokenExpired(String(futureUnixSeconds))).toBe(false);
});
it('should expose normalized expiry timestamps for string and numeric inputs', () => {
const futureTimestamp = Date.now() + 60000;
expect(getTokenExpiryTimestamp(futureTimestamp)).toBe(futureTimestamp);
expect(getTokenExpiryTimestamp(String(futureTimestamp))).toBe(futureTimestamp);
const futureUnixSeconds = Math.floor((Date.now() + 60000) / 1000);
expect(getTokenExpiryTimestamp(futureUnixSeconds)).toBe(futureUnixSeconds * 1000);
expect(getTokenExpiryTimestamp(String(futureUnixSeconds))).toBe(futureUnixSeconds * 1000);
expect(getTokenExpiryTimestamp('not-a-date')).toBeNull();
});
});
@@ -717,6 +717,111 @@ describe('Gemini CLI Quota Fetcher', () => {
expect(refreshRequest.body).not.toContain('default-refresh-token');
expect(quotaRequest.headers.Authorization).toBe('Bearer target-fresh-token');
});
it('retries a 401 quota failure after a transient proactive refresh failure', async () => {
writeGeminiToken(
{
type: 'gemini',
email: 'retry@example.com',
project_id: 'retry-project',
token: {
access_token: 'retry-stale-token',
refresh_token: 'retry-refresh-token',
expiry: Date.now() + 60 * 1000,
client_id: 'retry-client-id',
client_secret: 'retry-client-secret',
token_uri: GOOGLE_TOKEN_URL,
},
},
'gemini-retry.json'
);
mockFetch([
{
url: GOOGLE_TOKEN_URL,
method: 'POST',
response: { access_token: 'unused-default', expires_in: 1800 },
},
{
url: GEMINI_QUOTA_URL,
method: 'POST',
status: 200,
response: {
buckets: [{ model_id: 'gemini-3-flash-preview', remaining_fraction: 0.9 }],
},
},
{
url: GEMINI_CODE_ASSIST_URL,
method: 'POST',
status: 503,
response: { error: { message: 'supplementary unavailable' } },
},
]);
const originalFetch = globalThis.fetch;
let refreshAttempt = 0;
let quotaAttempt = 0;
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url =
typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
if (url === GOOGLE_TOKEN_URL) {
refreshAttempt += 1;
return refreshAttempt === 1
? new Response(JSON.stringify({ error: 'temporarily_unavailable' }), {
status: 503,
headers: { 'Content-Type': 'application/json' },
})
: new Response(JSON.stringify({ access_token: 'retry-fresh-token', expires_in: 1800 }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
if (url === GEMINI_QUOTA_URL) {
quotaAttempt += 1;
return quotaAttempt === 1
? new Response(
JSON.stringify({
error: {
message: 'Session expired',
status: 'UNAUTHENTICATED',
},
}),
{
status: 401,
headers: { 'Content-Type': 'application/json' },
}
)
: new Response(
JSON.stringify({
buckets: [{ model_id: 'gemini-3-flash-preview', remaining_fraction: 0.9 }],
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
}
);
}
return originalFetch(input, init);
}) as typeof fetch;
try {
const result = await fetchGeminiCliQuota('retry@example.com');
expect(result.success).toBe(true);
expect(refreshAttempt).toBe(2);
expect(quotaAttempt).toBe(2);
const storedToken = JSON.parse(
fs.readFileSync(path.join(getProviderAuthDir('gemini'), 'gemini-retry.json'), 'utf8')
) as { token?: { access_token?: string } };
expect(storedToken.token?.access_token).toBe('retry-fresh-token');
} finally {
globalThis.fetch = originalFetch;
}
});
});
describe('direct Gemini error helper coverage', () => {
@@ -249,7 +249,7 @@ export function AccountCard({
)}
</div>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-sm">
<TooltipContent side="top" className="sm:max-w-sm">
<QuotaTooltipContent quota={quota} resetTime={resetTime} />
</TooltipContent>
</Tooltip>
@@ -250,7 +250,7 @@ export function AccountQuotaPanel({
</div>
)}
</TooltipTrigger>
<TooltipContent side={mode === 'compact' ? 'top' : 'bottom'} className="max-w-sm">
<TooltipContent side={mode === 'compact' ? 'top' : 'bottom'} className="sm:max-w-sm">
<QuotaTooltipContent quota={quota} resetTime={resetTime} />
</TooltipContent>
</Tooltip>
@@ -306,7 +306,7 @@ export function AccountQuotaPanel({
</div>
)}
</TooltipTrigger>
<TooltipContent side={mode === 'compact' ? 'top' : 'bottom'} className="max-w-sm">
<TooltipContent side={mode === 'compact' ? 'top' : 'bottom'} className="sm:max-w-sm">
<QuotaTooltipContent quota={quota} resetTime={resetTime} />
</TooltipContent>
</Tooltip>
@@ -27,6 +27,8 @@ interface QuotaTooltipContentProps {
resetTime: string | null;
}
const lowQuotaTextClass = 'text-red-700 dark:text-red-400';
function formatPlanLabel(planType: string | null | undefined): string | null {
if (!planType) return null;
const normalized = planType
@@ -94,7 +96,7 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
: 'text-foreground';
return (
<div className="min-w-[16rem] space-y-2 text-xs">
<div className="max-w-sm space-y-2 text-xs">
<div className="space-y-1">
<p className={cn('font-semibold tracking-tight', failureToneClass)}>
{failureInfo?.label || quota.error || 'Failed to load quota'}
@@ -140,10 +142,10 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
{!isFirst && <div className="my-1 border-t border-border/40" />}
{models.map((m) => (
<div key={m.name} className="flex justify-between gap-4">
<span className={cn('truncate', m.exhausted && 'text-red-500')}>
<span className={cn('truncate', m.exhausted && lowQuotaTextClass)}>
{m.displayName}
</span>
<span className={cn('font-mono', m.exhausted && 'text-red-500')}>
<span className={cn('font-mono', m.exhausted && lowQuotaTextClass)}>
{m.percentage}%
</span>
</div>
@@ -180,7 +182,7 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
key={`${w.label}-${w.resetAt ?? 'no-reset'}-${index}`}
className="flex justify-between gap-4"
>
<span className={cn(w.remainingPercent < 20 && 'text-red-500')}>
<span className={cn(w.remainingPercent < 20 && lowQuotaTextClass)}>
{getCodexWindowDisplayLabel(w, orderedWindows)}
</span>
<span className="font-mono">{w.remainingPercent}%</span>
@@ -248,7 +250,7 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
key={`${window.rateLimitType}-${window.resetAt ?? 'no-reset'}-${window.status}-${index}`}
className="flex justify-between gap-4"
>
<span className={cn(window.remainingPercent < 20 && 'text-red-500')}>
<span className={cn(window.remainingPercent < 20 && lowQuotaTextClass)}>
{getClaudeWindowDisplayLabel(window.rateLimitType, window.label)}
</span>
<span className="font-mono">{window.remainingPercent}%</span>
@@ -285,7 +287,7 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
{quota.buckets.map((b) => (
<div key={b.id} className="space-y-0.5">
<div className="flex justify-between gap-4">
<span className={cn(b.remainingPercent < 20 && 'text-red-500')}>
<span className={cn(b.remainingPercent < 20 && lowQuotaTextClass)}>
{b.label}
{b.tokenType ? ` (${b.tokenType})` : ''}
</span>
@@ -327,8 +329,8 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
return (
<div key={label} className="space-y-0.5">
<div className="flex justify-between gap-4">
<span className={cn(isLow && 'text-red-500')}>{label}</span>
<span className={cn('font-mono', isLow && 'text-red-500')}>
<span className={cn(isLow && lowQuotaTextClass)}>{label}</span>
<span className={cn('font-mono', isLow && lowQuotaTextClass)}>
{snapshot.unlimited
? 'Unlimited'
: `${formatQuotaPercent(snapshot.percentRemaining)}%`}
+1 -1
View File
@@ -42,7 +42,7 @@ function TooltipContent({
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
'animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-w-sm origin-(--radix-tooltip-content-transform-origin) rounded-lg border border-border/70 bg-popover px-3 py-2 text-left text-xs leading-relaxed text-popover-foreground shadow-xl shadow-black/10',
'animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit max-w-[calc(100vw-2rem)] origin-(--radix-tooltip-content-transform-origin) rounded-lg border border-border/70 bg-popover px-3 py-2 text-left text-xs leading-relaxed text-popover-foreground shadow-xl shadow-black/10',
className
)}
{...props}
@@ -131,14 +131,21 @@ describe('AccountCard grouped quota tooltip', () => {
);
await userEvent.hover(screen.getByText('Business'));
expect((await screen.findAllByText('Plan: team')).length).toBeGreaterThan(0);
const businessPlan = (await screen.findAllByText('Plan: team')).find((node) =>
node.closest('[data-slot="tooltip-content"]')
);
expect(businessPlan).toBeInTheDocument();
expect(screen.getAllByText('5h usage limit').length).toBeGreaterThan(0);
const tooltipContent = document.querySelector('[data-slot="tooltip-content"]');
const tooltipContent = businessPlan.closest('[data-slot="tooltip-content"]');
expect(tooltipContent?.className).toContain('bg-popover');
expect(tooltipContent?.className).toContain('text-popover-foreground');
expect(tooltipContent?.className).toContain('max-w-[calc(100vw-2rem)]');
await userEvent.hover(screen.getByText('Personal'));
expect((await screen.findAllByText('Plan: plus')).length).toBeGreaterThan(0);
const personalPlan = (await screen.findAllByText('Plan: plus')).find((node) =>
node.closest('[data-slot="tooltip-content"]')
);
expect(personalPlan).toBeInTheDocument();
expect(screen.getAllByText('Weekly usage limit').length).toBeGreaterThan(0);
});
});
@@ -0,0 +1,52 @@
import { render, screen, userEvent } from '@tests/setup/test-utils';
import { describe, expect, it } from 'vitest';
import { AccountQuotaPanel } from '@/components/account/shared/account-quota-panel';
import type { GeminiCliQuotaResult } from '@/lib/api-client';
function createGeminiFailureQuota(): GeminiCliQuotaResult {
return {
success: false,
buckets: [],
projectId: 'test-project',
lastUpdated: Date.now(),
error: 'Request had invalid authentication credentials.',
httpStatus: 401,
errorCode: 'UNAUTHENTICATED',
errorDetail:
'{"error":{"code":401,"message":"Request had invalid authentication credentials.","status":"UNAUTHENTICATED"}}',
actionHint: 'Run ccs gemini --auth to reconnect this account.',
needsReauth: true,
};
}
describe('AccountQuotaPanel failure tooltip', () => {
it('renders the shared failure tooltip content with viewport-safe shell classes', async () => {
render(
<AccountQuotaPanel
provider="gemini"
quota={createGeminiFailureQuota()}
quotaLoading={false}
mode="detailed"
/>
);
await userEvent.hover(screen.getByText('Reauth'));
const summary = (
await screen.findAllByText('Request had invalid authentication credentials.')
).find((node) => node.closest('[data-slot="tooltip-content"]'));
expect(summary).toBeInTheDocument();
const tooltipContent = summary.closest('[data-slot="tooltip-content"]');
const actionHint = screen
.getAllByText('Run ccs gemini --auth to reconnect this account.')
.find((node) => node.closest('[data-slot="tooltip-content"]') === tooltipContent);
const technicalDetail = screen
.getAllByText('HTTP 401 | UNAUTHENTICATED')
.find((node) => node.closest('[data-slot="tooltip-content"]') === tooltipContent);
expect(actionHint).toBeInTheDocument();
expect(technicalDetail).toBeInTheDocument();
expect(tooltipContent?.className).toContain('max-w-[calc(100vw-2rem)]');
expect(tooltipContent?.className).toContain('bg-popover');
expect(tooltipContent?.className).toContain('text-popover-foreground');
});
});
@@ -104,7 +104,7 @@ describe('QuotaTooltipContent', () => {
needsReauth: true,
});
render(<QuotaTooltipContent quota={quota} resetTime={null} />);
const { container } = render(<QuotaTooltipContent quota={quota} resetTime={null} />);
expect(screen.getByText('Reauth')).toBeInTheDocument();
expect(screen.getByText('Request had invalid authentication credentials.')).toBeInTheDocument();
@@ -113,5 +113,6 @@ describe('QuotaTooltipContent', () => {
).toBeInTheDocument();
expect(screen.getByText('HTTP 401 | UNAUTHENTICATED')).toBeInTheDocument();
expect(screen.getByText(/"status":"UNAUTHENTICATED"/)).toBeInTheDocument();
expect(container.firstChild).not.toHaveClass('min-w-[16rem]');
});
});