mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-02 16:04:38 -07:00
255 lines
8.2 KiB
TypeScript
255 lines
8.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import type { GeminiUserTier } from '../code_assist/types.js';
|
|
import {
|
|
buildG1Url,
|
|
getG1CreditBalance,
|
|
G1_CREDIT_TYPE,
|
|
G1_UTM_CAMPAIGNS,
|
|
isOverageEligibleModel,
|
|
shouldAutoUseCredits,
|
|
shouldShowEmptyWalletMenu,
|
|
shouldShowOverageMenu,
|
|
wrapInAccountChooser,
|
|
} from './billing.js';
|
|
|
|
describe('billing', () => {
|
|
describe('wrapInAccountChooser', () => {
|
|
it('should wrap URL with AccountChooser redirect', () => {
|
|
const result = wrapInAccountChooser(
|
|
'user@gmail.com',
|
|
'https://one.google.com/ai/activity',
|
|
);
|
|
expect(result).toBe(
|
|
'https://accounts.google.com/AccountChooser?Email=user%40gmail.com&continue=https%3A%2F%2Fone.google.com%2Fai%2Factivity',
|
|
);
|
|
});
|
|
|
|
it('should handle special characters in email', () => {
|
|
const result = wrapInAccountChooser(
|
|
'user+test@example.com',
|
|
'https://example.com',
|
|
);
|
|
expect(result).toContain('Email=user%2Btest%40example.com');
|
|
});
|
|
});
|
|
|
|
describe('buildG1Url', () => {
|
|
it('should build activity URL with UTM params wrapped in AccountChooser', () => {
|
|
const result = buildG1Url(
|
|
'activity',
|
|
'user@gmail.com',
|
|
G1_UTM_CAMPAIGNS.MANAGE_ACTIVITY,
|
|
);
|
|
|
|
// Should contain AccountChooser prefix
|
|
expect(result).toContain('https://accounts.google.com/AccountChooser');
|
|
expect(result).toContain('Email=user%40gmail.com');
|
|
|
|
// The continue URL should contain the G1 activity path and UTM params
|
|
expect(result).toContain('one.google.com%2Fai%2Factivity');
|
|
expect(result).toContain('utm_source%3Dgemini_cli');
|
|
expect(result).toContain(
|
|
'utm_campaign%3Dhydrogen_cli_settings_ai_credits_activity_page',
|
|
);
|
|
});
|
|
|
|
it('should build credits URL with UTM params wrapped in AccountChooser', () => {
|
|
const result = buildG1Url(
|
|
'credits',
|
|
'test@example.com',
|
|
G1_UTM_CAMPAIGNS.EMPTY_WALLET_ADD_CREDITS,
|
|
);
|
|
|
|
expect(result).toContain('https://accounts.google.com/AccountChooser');
|
|
expect(result).toContain('one.google.com%2Fai%2Fcredits');
|
|
expect(result).toContain(
|
|
'utm_campaign%3Dhydrogen_cli_insufficient_credits_add_credits',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getG1CreditBalance', () => {
|
|
it('should return null for null tier', () => {
|
|
expect(getG1CreditBalance(null)).toBeNull();
|
|
});
|
|
|
|
it('should return null for undefined tier', () => {
|
|
expect(getG1CreditBalance(undefined)).toBeNull();
|
|
});
|
|
|
|
it('should return null for tier without availableCredits', () => {
|
|
const tier: GeminiUserTier = { id: 'PERSONAL' };
|
|
expect(getG1CreditBalance(tier)).toBeNull();
|
|
});
|
|
|
|
it('should return null for empty availableCredits array', () => {
|
|
const tier: GeminiUserTier = { id: 'PERSONAL', availableCredits: [] };
|
|
expect(getG1CreditBalance(tier)).toBeNull();
|
|
});
|
|
|
|
it('should return null when no G1 credit type found', () => {
|
|
const tier: GeminiUserTier = {
|
|
id: 'PERSONAL',
|
|
availableCredits: [
|
|
{ creditType: 'CREDIT_TYPE_UNSPECIFIED', creditAmount: '100' },
|
|
],
|
|
};
|
|
expect(getG1CreditBalance(tier)).toBeNull();
|
|
});
|
|
|
|
it('should return G1 credit balance when present', () => {
|
|
const tier: GeminiUserTier = {
|
|
id: 'PERSONAL',
|
|
availableCredits: [{ creditType: G1_CREDIT_TYPE, creditAmount: '500' }],
|
|
};
|
|
expect(getG1CreditBalance(tier)).toBe(500);
|
|
});
|
|
|
|
it('should return G1 credit balance when multiple credit types present', () => {
|
|
const tier: GeminiUserTier = {
|
|
id: 'PERSONAL',
|
|
availableCredits: [
|
|
{ creditType: 'CREDIT_TYPE_UNSPECIFIED', creditAmount: '100' },
|
|
{ creditType: G1_CREDIT_TYPE, creditAmount: '750' },
|
|
],
|
|
};
|
|
expect(getG1CreditBalance(tier)).toBe(750);
|
|
});
|
|
|
|
it('should return 0 for invalid credit amount', () => {
|
|
const tier: GeminiUserTier = {
|
|
id: 'PERSONAL',
|
|
availableCredits: [
|
|
{ creditType: G1_CREDIT_TYPE, creditAmount: 'invalid' },
|
|
],
|
|
};
|
|
expect(getG1CreditBalance(tier)).toBe(0);
|
|
});
|
|
|
|
it('should handle large credit amounts (int64 as string)', () => {
|
|
const tier: GeminiUserTier = {
|
|
id: 'PERSONAL',
|
|
availableCredits: [
|
|
{ creditType: G1_CREDIT_TYPE, creditAmount: '9999999999' },
|
|
],
|
|
};
|
|
expect(getG1CreditBalance(tier)).toBe(9999999999);
|
|
});
|
|
|
|
it('should sum multiple credits of the same G1 type', () => {
|
|
const tier: GeminiUserTier = {
|
|
id: 'PERSONAL',
|
|
availableCredits: [
|
|
{ creditType: G1_CREDIT_TYPE, creditAmount: '1000' },
|
|
{ creditType: G1_CREDIT_TYPE, creditAmount: '8' },
|
|
],
|
|
};
|
|
expect(getG1CreditBalance(tier)).toBe(1008);
|
|
});
|
|
});
|
|
|
|
describe('shouldAutoUseCredits', () => {
|
|
it('should return true when strategy is always and balance > 0', () => {
|
|
expect(shouldAutoUseCredits('always', 100)).toBe(true);
|
|
});
|
|
|
|
it('should return false when strategy is always but balance is 0', () => {
|
|
expect(shouldAutoUseCredits('always', 0)).toBe(false);
|
|
});
|
|
|
|
it('should return false when strategy is ask', () => {
|
|
expect(shouldAutoUseCredits('ask', 100)).toBe(false);
|
|
});
|
|
|
|
it('should return false when strategy is never', () => {
|
|
expect(shouldAutoUseCredits('never', 100)).toBe(false);
|
|
});
|
|
|
|
it('should return false when creditBalance is null (ineligible)', () => {
|
|
expect(shouldAutoUseCredits('always', null)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('shouldShowOverageMenu', () => {
|
|
it('should return true when strategy is ask and balance > 0', () => {
|
|
expect(shouldShowOverageMenu('ask', 100)).toBe(true);
|
|
});
|
|
|
|
it('should return false when strategy is ask but balance is 0', () => {
|
|
expect(shouldShowOverageMenu('ask', 0)).toBe(false);
|
|
});
|
|
|
|
it('should return false when strategy is always', () => {
|
|
expect(shouldShowOverageMenu('always', 100)).toBe(false);
|
|
});
|
|
|
|
it('should return false when strategy is never', () => {
|
|
expect(shouldShowOverageMenu('never', 100)).toBe(false);
|
|
});
|
|
|
|
it('should return false when creditBalance is null (ineligible)', () => {
|
|
expect(shouldShowOverageMenu('ask', null)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('shouldShowEmptyWalletMenu', () => {
|
|
it('should return true when strategy is ask and balance is 0', () => {
|
|
expect(shouldShowEmptyWalletMenu('ask', 0)).toBe(true);
|
|
});
|
|
|
|
it('should return true when strategy is always and balance is 0', () => {
|
|
expect(shouldShowEmptyWalletMenu('always', 0)).toBe(true);
|
|
});
|
|
|
|
it('should return false when strategy is never', () => {
|
|
expect(shouldShowEmptyWalletMenu('never', 0)).toBe(false);
|
|
});
|
|
|
|
it('should return false when balance > 0', () => {
|
|
expect(shouldShowEmptyWalletMenu('ask', 100)).toBe(false);
|
|
});
|
|
|
|
it('should return false when creditBalance is null (ineligible)', () => {
|
|
expect(shouldShowEmptyWalletMenu('ask', null)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isOverageEligibleModel', () => {
|
|
it('should return true for gemini-3-pro-preview', () => {
|
|
expect(isOverageEligibleModel('gemini-3-pro-preview')).toBe(true);
|
|
});
|
|
|
|
it('should return true for gemini-3.1-pro-preview', () => {
|
|
expect(isOverageEligibleModel('gemini-3.1-pro-preview')).toBe(true);
|
|
});
|
|
|
|
it('should return false for gemini-3.1-pro-preview-customtools', () => {
|
|
expect(isOverageEligibleModel('gemini-3.1-pro-preview-customtools')).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it('should return true for gemini-3-flash-preview', () => {
|
|
expect(isOverageEligibleModel('gemini-3-flash-preview')).toBe(true);
|
|
});
|
|
|
|
it('should return false for gemini-2.5-pro', () => {
|
|
expect(isOverageEligibleModel('gemini-2.5-pro')).toBe(false);
|
|
});
|
|
|
|
it('should return false for gemini-2.5-flash', () => {
|
|
expect(isOverageEligibleModel('gemini-2.5-flash')).toBe(false);
|
|
});
|
|
|
|
it('should return false for custom model names', () => {
|
|
expect(isOverageEligibleModel('my-custom-model')).toBe(false);
|
|
});
|
|
});
|
|
});
|