diff options
Diffstat (limited to 'packages/core/src/utils/user_account.test.ts')
| -rw-r--r-- | packages/core/src/utils/user_account.test.ts | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/packages/core/src/utils/user_account.test.ts b/packages/core/src/utils/user_account.test.ts new file mode 100644 index 00000000..a0c861cc --- /dev/null +++ b/packages/core/src/utils/user_account.test.ts @@ -0,0 +1,237 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { vi, describe, it, expect, beforeEach, afterEach, Mock } from 'vitest'; +import { + cacheGoogleAccount, + getCachedGoogleAccount, + clearCachedGoogleAccount, + getLifetimeGoogleAccounts, +} from './user_account.js'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import path from 'node:path'; + +vi.mock('os', async (importOriginal) => { + const os = await importOriginal<typeof import('os')>(); + return { + ...os, + homedir: vi.fn(), + }; +}); + +describe('user_account', () => { + let tempHomeDir: string; + const accountsFile = () => + path.join(tempHomeDir, '.gemini', 'google_accounts.json'); + beforeEach(() => { + tempHomeDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'gemini-cli-test-home-'), + ); + (os.homedir as Mock).mockReturnValue(tempHomeDir); + }); + afterEach(() => { + fs.rmSync(tempHomeDir, { recursive: true, force: true }); + vi.clearAllMocks(); + }); + + describe('cacheGoogleAccount', () => { + it('should create directory and write initial account file', async () => { + await cacheGoogleAccount('[email protected]'); + + // Verify Google Account ID was cached + expect(fs.existsSync(accountsFile())).toBe(true); + expect(fs.readFileSync(accountsFile(), 'utf-8')).toBe( + JSON.stringify({ active: '[email protected]', old: [] }, null, 2), + ); + }); + + it('should update active account and move previous to old', async () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync( + accountsFile(), + JSON.stringify( + { active: '[email protected]', old: ['[email protected]'] }, + null, + 2, + ), + ); + + await cacheGoogleAccount('[email protected]'); + + expect(fs.readFileSync(accountsFile(), 'utf-8')).toBe( + JSON.stringify( + { + active: '[email protected]', + old: ['[email protected]', '[email protected]'], + }, + null, + 2, + ), + ); + }); + + it('should not add a duplicate to the old list', async () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync( + accountsFile(), + JSON.stringify( + { active: '[email protected]', old: ['[email protected]'] }, + null, + 2, + ), + ); + await cacheGoogleAccount('[email protected]'); + await cacheGoogleAccount('[email protected]'); + + expect(fs.readFileSync(accountsFile(), 'utf-8')).toBe( + JSON.stringify( + { active: '[email protected]', old: ['[email protected]'] }, + null, + 2, + ), + ); + }); + + it('should handle corrupted JSON by starting fresh', async () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync(accountsFile(), 'not valid json'); + const consoleDebugSpy = vi + .spyOn(console, 'debug') + .mockImplementation(() => {}); + + await cacheGoogleAccount('[email protected]'); + + expect(consoleDebugSpy).toHaveBeenCalled(); + expect(JSON.parse(fs.readFileSync(accountsFile(), 'utf-8'))).toEqual({ + active: '[email protected]', + old: [], + }); + }); + }); + + describe('getCachedGoogleAccount', () => { + it('should return the active account if file exists and is valid', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync( + accountsFile(), + JSON.stringify({ active: '[email protected]', old: [] }, null, 2), + ); + const account = getCachedGoogleAccount(); + expect(account).toBe('[email protected]'); + }); + + it('should return null if file does not exist', () => { + const account = getCachedGoogleAccount(); + expect(account).toBeNull(); + }); + + it('should return null if file is empty', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync(accountsFile(), ''); + const account = getCachedGoogleAccount(); + expect(account).toBeNull(); + }); + + it('should return null and log if file is corrupted', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync(accountsFile(), '{ "active": "[email protected]"'); // Invalid JSON + const consoleDebugSpy = vi + .spyOn(console, 'debug') + .mockImplementation(() => {}); + + const account = getCachedGoogleAccount(); + + expect(account).toBeNull(); + expect(consoleDebugSpy).toHaveBeenCalled(); + }); + }); + + describe('clearCachedGoogleAccount', () => { + it('should set active to null and move it to old', async () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync( + accountsFile(), + JSON.stringify( + { active: '[email protected]', old: ['[email protected]'] }, + null, + 2, + ), + ); + + await clearCachedGoogleAccount(); + + const stored = JSON.parse(fs.readFileSync(accountsFile(), 'utf-8')); + expect(stored.active).toBeNull(); + expect(stored.old).toEqual(['[email protected]', '[email protected]']); + }); + + it('should handle empty file gracefully', async () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync(accountsFile(), ''); + await clearCachedGoogleAccount(); + const stored = JSON.parse(fs.readFileSync(accountsFile(), 'utf-8')); + expect(stored.active).toBeNull(); + expect(stored.old).toEqual([]); + }); + }); + + describe('getLifetimeGoogleAccounts', () => { + it('should return 0 if the file does not exist', () => { + expect(getLifetimeGoogleAccounts()).toBe(0); + }); + + it('should return 0 if the file is empty', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync(accountsFile(), ''); + expect(getLifetimeGoogleAccounts()).toBe(0); + }); + + it('should return 0 if the file is corrupted', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync(accountsFile(), 'invalid json'); + const consoleDebugSpy = vi + .spyOn(console, 'debug') + .mockImplementation(() => {}); + + expect(getLifetimeGoogleAccounts()).toBe(0); + expect(consoleDebugSpy).toHaveBeenCalled(); + }); + + it('should return 1 if there is only an active account', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync( + accountsFile(), + JSON.stringify({ active: '[email protected]', old: [] }), + ); + expect(getLifetimeGoogleAccounts()).toBe(1); + }); + + it('should correctly count old accounts when active is null', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync( + accountsFile(), + JSON.stringify({ + active: null, + old: ['[email protected]', '[email protected]'], + }), + ); + expect(getLifetimeGoogleAccounts()).toBe(2); + }); + + it('should correctly count both active and old accounts', () => { + fs.mkdirSync(path.dirname(accountsFile()), { recursive: true }); + fs.writeFileSync( + accountsFile(), + JSON.stringify({ + active: '[email protected]', + old: ['[email protected]', '[email protected]'], + }), + ); + expect(getLifetimeGoogleAccounts()).toBe(3); + }); + }); +}); |
