mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 14:17:11 +00:00
fix(cliproxy): refresh non-default gemini quota tokens
This commit is contained in:
1 parent
04878f0864
commit
b6fb443f57
11 files changed
+207
-71
No files matched your search
@@ -5,7 +5,11 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { sanitizeEmail, isTokenExpired } from '../../../src/cliproxy/auth-utils';
|
||||
import {
|
||||
getTokenExpiryTimestamp,
|
||||
isTokenExpired,
|
||||
sanitizeEmail,
|
||||
} from '../../../src/cliproxy/auth-utils';
|
||||
|
||||
describe('Auth Utilities', () => {
|
||||
describe('sanitizeEmail', () => {
|
||||
@@ -75,14 +79,21 @@ describe('Auth Utilities', () => {
|
||||
expect(isTokenExpired(futureISO)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle Unix timestamp strings', () => {
|
||||
// JavaScript Date can parse numeric strings as timestamps
|
||||
it('should handle Unix timestamp strings deterministically', () => {
|
||||
const pastTimestamp = String(Date.now() - 86400000); // Yesterday
|
||||
// Note: Date parsing of pure numbers as strings is inconsistent
|
||||
// This test documents the actual behavior
|
||||
const result = isTokenExpired(pastTimestamp);
|
||||
// The behavior depends on how Date parses the string
|
||||
expect(typeof result).toBe('boolean');
|
||||
expect(isTokenExpired(pastTimestamp)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle Unix timestamps provided as numbers', () => {
|
||||
const futureTimestamp = Date.now() + 86400000;
|
||||
expect(isTokenExpired(futureTimestamp)).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);
|
||||
expect(getTokenExpiryTimestamp('not-a-date')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -27,10 +27,10 @@ describe('Gemini CLI Quota Fetcher', () => {
|
||||
let refreshGeminiToken: typeof import('../../../src/cliproxy/auth/gemini-token-refresh').refreshGeminiToken;
|
||||
let getProviderAuthDir: typeof import('../../../src/cliproxy/config-generator').getProviderAuthDir;
|
||||
|
||||
function writeGeminiToken(token: Record<string, unknown>): string {
|
||||
function writeGeminiToken(token: Record<string, unknown>, filename = 'gemini-test.json'): string {
|
||||
const authDir = getProviderAuthDir('gemini');
|
||||
fs.mkdirSync(authDir, { recursive: true });
|
||||
const tokenPath = path.join(authDir, 'gemini-test.json');
|
||||
const tokenPath = path.join(authDir, filename);
|
||||
fs.writeFileSync(tokenPath, JSON.stringify(token, null, 2));
|
||||
return tokenPath;
|
||||
}
|
||||
@@ -649,6 +649,74 @@ describe('Gemini CLI Quota Fetcher', () => {
|
||||
expect(result.error).toBe('Gemini quota service unavailable (HTTP 502)');
|
||||
expect(result.errorDetail).toBe('[HTML error response omitted]');
|
||||
});
|
||||
|
||||
it('refreshes the requested Gemini account instead of the default account', async () => {
|
||||
writeGeminiToken(
|
||||
{
|
||||
type: 'gemini',
|
||||
email: 'default@example.com',
|
||||
project_id: 'default-project',
|
||||
token: {
|
||||
access_token: 'default-access-token',
|
||||
refresh_token: 'default-refresh-token',
|
||||
expiry: Date.now() + 60 * 60 * 1000,
|
||||
client_id: 'default-client-id',
|
||||
client_secret: 'default-client-secret',
|
||||
token_uri: GOOGLE_TOKEN_URL,
|
||||
},
|
||||
},
|
||||
'gemini-default.json'
|
||||
);
|
||||
|
||||
writeGeminiToken(
|
||||
{
|
||||
type: 'gemini',
|
||||
email: 'target@example.com',
|
||||
project_id: 'target-project',
|
||||
token: {
|
||||
access_token: 'target-stale-token',
|
||||
refresh_token: 'target-refresh-token',
|
||||
expiry: Date.now() - 1000,
|
||||
client_id: 'target-client-id',
|
||||
client_secret: 'target-client-secret',
|
||||
token_uri: GOOGLE_TOKEN_URL,
|
||||
},
|
||||
},
|
||||
'gemini-target.json'
|
||||
);
|
||||
|
||||
mockFetch([
|
||||
{
|
||||
url: GOOGLE_TOKEN_URL,
|
||||
method: 'POST',
|
||||
response: { access_token: 'target-fresh-token', expires_in: 1800 },
|
||||
},
|
||||
{
|
||||
url: GEMINI_QUOTA_URL,
|
||||
method: 'POST',
|
||||
status: 200,
|
||||
response: {
|
||||
buckets: [{ model_id: 'gemini-3-flash-preview', remaining_fraction: 0.88 }],
|
||||
},
|
||||
},
|
||||
{
|
||||
url: GEMINI_CODE_ASSIST_URL,
|
||||
method: 'POST',
|
||||
status: 503,
|
||||
response: { error: { message: 'supplementary unavailable' } },
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await fetchGeminiCliQuota('target@example.com');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const [refreshRequest, quotaRequest] = getCapturedFetchRequests();
|
||||
expect(refreshRequest.url).toBe(GOOGLE_TOKEN_URL);
|
||||
expect(refreshRequest.body).toContain('refresh_token=target-refresh-token');
|
||||
expect(refreshRequest.body).not.toContain('default-refresh-token');
|
||||
expect(quotaRequest.headers.Authorization).toBe('Bearer target-fresh-token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('direct Gemini error helper coverage', () => {
|
||||
|
||||
Reference in new issue
Block a user