mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 04:17:54 +00:00
fix(cliproxy): surface quota metadata in CLI
This commit is contained in:
@@ -22,6 +22,7 @@ describe('Gemini CLI Quota Fetcher', () => {
|
||||
let buildGeminiCliBuckets: typeof import('../../../src/cliproxy/quota-fetcher-gemini-cli').buildGeminiCliBuckets;
|
||||
let fetchGeminiCliQuota: typeof import('../../../src/cliproxy/quota-fetcher-gemini-cli').fetchGeminiCliQuota;
|
||||
let resolveGeminiCliProjectId: typeof import('../../../src/cliproxy/quota-fetcher-gemini-cli').resolveGeminiCliProjectId;
|
||||
let geminiTestExports: typeof import('../../../src/cliproxy/quota-fetcher-gemini-cli').__testExports;
|
||||
let refreshGeminiToken: typeof import('../../../src/cliproxy/auth/gemini-token-refresh').refreshGeminiToken;
|
||||
let getProviderAuthDir: typeof import('../../../src/cliproxy/config-generator').getProviderAuthDir;
|
||||
|
||||
@@ -69,7 +70,12 @@ describe('Gemini CLI Quota Fetcher', () => {
|
||||
const configGenerator = await import(
|
||||
`../../../src/cliproxy/config-generator?gemini-config-generator=${moduleVersion}`
|
||||
);
|
||||
({ buildGeminiCliBuckets, fetchGeminiCliQuota, resolveGeminiCliProjectId } = await import(
|
||||
({
|
||||
buildGeminiCliBuckets,
|
||||
fetchGeminiCliQuota,
|
||||
resolveGeminiCliProjectId,
|
||||
__testExports: geminiTestExports,
|
||||
} = await import(
|
||||
`../../../src/cliproxy/quota-fetcher-gemini-cli?gemini-quota-fetcher=${moduleVersion}`
|
||||
));
|
||||
({ refreshGeminiToken } = await import(
|
||||
@@ -452,6 +458,62 @@ describe('Gemini CLI Quota Fetcher', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('direct Gemini error helper coverage', () => {
|
||||
it('sanitizes HTML and truncates oversized token-bearing error details', () => {
|
||||
const longTokenBody = JSON.stringify({
|
||||
access_token: 'super-secret-token',
|
||||
detail: `Bearer top-secret ${'x'.repeat(400)}`,
|
||||
});
|
||||
|
||||
const sanitized = geminiTestExports.sanitizeGeminiCliErrorDetail(longTokenBody);
|
||||
|
||||
expect(sanitized).toContain('[redacted]');
|
||||
expect(sanitized).toContain('Bearer [redacted]');
|
||||
expect(sanitized?.endsWith('...[truncated]')).toBe(true);
|
||||
expect(sanitized?.length).toBeLessThanOrEqual(320);
|
||||
expect(geminiTestExports.sanitizeGeminiCliErrorDetail('<html>bad gateway</html>')).toBe(
|
||||
'[HTML error response omitted]'
|
||||
);
|
||||
});
|
||||
|
||||
it('extracts nested messages and parses structured JSON error bodies', () => {
|
||||
expect(
|
||||
geminiTestExports.extractGeminiCliNestedMessage([
|
||||
{ reason: 'ACCOUNT_VERIFICATION_REQUIRED' },
|
||||
])
|
||||
).toBe('ACCOUNT_VERIFICATION_REQUIRED');
|
||||
|
||||
const parsed = geminiTestExports.parseGeminiCliErrorBody(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
message: 'Verification required',
|
||||
status: 'PERMISSION_DENIED',
|
||||
details: [{ reason: 'ACCOUNT_VERIFICATION_REQUIRED' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(parsed.message).toBe('Verification required');
|
||||
expect(parsed.errorCode).toBe('PERMISSION_DENIED');
|
||||
expect(parsed.errorDetail).toContain('ACCOUNT_VERIFICATION_REQUIRED');
|
||||
});
|
||||
|
||||
it('builds verification and project-specific forbidden action hints', () => {
|
||||
expect(
|
||||
geminiTestExports.buildGeminiCliForbiddenActionHint({
|
||||
message: 'Please verify this account',
|
||||
errorDetail: 'ACCOUNT_VERIFICATION_REQUIRED',
|
||||
})
|
||||
).toContain('verification');
|
||||
|
||||
expect(
|
||||
geminiTestExports.buildGeminiCliForbiddenActionHint({
|
||||
message: 'Project no longer has access',
|
||||
})
|
||||
).toContain('project');
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshGeminiToken', () => {
|
||||
it('uses OAuth client metadata stored in the token file', async () => {
|
||||
writeGeminiToken({
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
async function loadQuotaCommandTestExports() {
|
||||
const moduleId = Date.now() + Math.random();
|
||||
const mod = await import(
|
||||
`../../../src/commands/cliproxy/quota-subcommand?cliproxy-quota-subcommand=${moduleId}`
|
||||
);
|
||||
return mod.__testExports;
|
||||
}
|
||||
|
||||
describe('cliproxy quota subcommand failure formatting', () => {
|
||||
it('builds Gemini failure lines with the remediation hint, code, and detail', async () => {
|
||||
const { getQuotaFailureDisplayEntries } = await loadQuotaCommandTestExports();
|
||||
|
||||
const entries = getQuotaFailureDisplayEntries({
|
||||
error: 'Google requires you to verify this account before using Gemini CLI quota.',
|
||||
actionHint:
|
||||
'Complete the Google account verification mentioned above, then retry quota refresh.',
|
||||
httpStatus: 403,
|
||||
errorCode: 'PERMISSION_DENIED',
|
||||
errorDetail: 'ACCOUNT_VERIFICATION_REQUIRED',
|
||||
retryable: false,
|
||||
});
|
||||
|
||||
expect(entries).toEqual([
|
||||
{
|
||||
tone: 'error',
|
||||
text: 'Google requires you to verify this account before using Gemini CLI quota.',
|
||||
},
|
||||
{
|
||||
tone: 'info',
|
||||
text: 'Complete the Google account verification mentioned above, then retry quota refresh.',
|
||||
},
|
||||
{
|
||||
tone: 'dim',
|
||||
text: 'HTTP 403 | Code: PERMISSION_DENIED',
|
||||
},
|
||||
{
|
||||
tone: 'dim',
|
||||
text: 'Detail: ACCOUNT_VERIFICATION_REQUIRED',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('marks retryable failures in the CLI diagnostics line', async () => {
|
||||
const { getQuotaFailureDisplayEntries } = await loadQuotaCommandTestExports();
|
||||
|
||||
const entries = getQuotaFailureDisplayEntries({
|
||||
error: 'Gemini quota service unavailable (HTTP 503)',
|
||||
actionHint: 'Retry later. This looks like a temporary Google upstream problem.',
|
||||
httpStatus: 503,
|
||||
errorCode: 'provider_unavailable',
|
||||
errorDetail: 'Service temporarily unavailable',
|
||||
retryable: true,
|
||||
});
|
||||
|
||||
expect(entries[2]).toEqual({
|
||||
tone: 'dim',
|
||||
text: 'HTTP 503 | Code: provider_unavailable | Retryable',
|
||||
});
|
||||
});
|
||||
|
||||
it('suppresses duplicate error detail lines', async () => {
|
||||
const { getQuotaFailureDisplayEntries } = await loadQuotaCommandTestExports();
|
||||
|
||||
const entries = getQuotaFailureDisplayEntries({
|
||||
error: 'Internal Server Error',
|
||||
errorDetail: 'Internal Server Error',
|
||||
});
|
||||
|
||||
expect(entries).toEqual([
|
||||
{
|
||||
tone: 'error',
|
||||
text: 'Internal Server Error',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user