Download packages/cli/src/gemini_cleanup.test.tsx from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 14.8 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/gemini_cleanup.test.tsx
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/gemini_cleanup.test.tsx
-
curl -L -o gemini_cleanup.test.tsx https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/gemini_cleanup.test.tsx
14.8 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; | |
| import { main } from './gemini.js'; | |
| import { | |
| debugLogger, | |
| SessionEndReason, | |
| type Config, | |
| type HookSystem, | |
| } from '@google/gemini-cli-core'; | |
| vi.mock('@google/gemini-cli-core', async (importOriginal) => { | |
| const actual = | |
| await importOriginal<typeof import('@google/gemini-cli-core')>(); | |
| return { | |
| ...actual, | |
| writeToStdout: vi.fn(), | |
| patchStdio: vi.fn(() => () => {}), | |
| createWorkingStdio: vi.fn(() => ({ | |
| stdout: { | |
| write: vi.fn(), | |
| columns: 80, | |
| rows: 24, | |
| on: vi.fn(), | |
| removeListener: vi.fn(), | |
| }, | |
| stderr: { write: vi.fn() }, | |
| })), | |
| enableMouseEvents: vi.fn(), | |
| disableMouseEvents: vi.fn(), | |
| enterAlternateScreen: vi.fn(), | |
| disableLineWrapping: vi.fn(), | |
| ProjectRegistry: vi.fn().mockImplementation(() => ({ | |
| initialize: vi.fn(), | |
| getShortId: vi.fn().mockReturnValue('project-slug'), | |
| })), | |
| }; | |
| }); | |
| vi.mock('ink', async (importOriginal) => { | |
| const actual = await importOriginal<typeof import('ink')>(); | |
| return { | |
| ...actual, | |
| render: vi.fn(() => ({ | |
| unmount: vi.fn(), | |
| rerender: vi.fn(), | |
| cleanup: vi.fn(), | |
| waitUntilExit: vi.fn(), | |
| })), | |
| }; | |
| }); | |
| vi.mock('./config/settings.js', async (importOriginal) => { | |
| const actual = await importOriginal<typeof import('./config/settings.js')>(); | |
| return { | |
| ...actual, | |
| loadSettings: vi.fn().mockReturnValue({ | |
| merged: { advanced: {}, security: { auth: {} }, ui: {} }, | |
| workspace: { settings: {} }, | |
| setValue: vi.fn(), | |
| forScope: () => ({ settings: {}, originalSettings: {}, path: '' }), | |
| errors: [], | |
| }), | |
| }; | |
| }); | |
| vi.mock('./ui/utils/ConsolePatcher.js', () => ({ | |
| ConsolePatcher: vi.fn().mockImplementation(() => ({ | |
| patch: vi.fn(), | |
| cleanup: vi.fn(), | |
| })), | |
| })); | |
| vi.mock('./config/config.js', () => ({ | |
| loadCliConfig: vi.fn().mockResolvedValue({ | |
| getSandbox: vi.fn(() => false), | |
| getQuestion: vi.fn(() => ''), | |
| isInteractive: () => false, | |
| getSessionId: vi.fn().mockReturnValue('test-session-id'), | |
| storage: { initialize: vi.fn().mockResolvedValue(undefined) }, | |
| } as unknown as Config), | |
| parseArguments: vi.fn().mockResolvedValue({}), | |
| isDebugMode: vi.fn(() => false), | |
| getRequestedWorktreeName: vi.fn(() => undefined), | |
| getWorktreeArg: vi.fn(() => undefined), | |
| })); | |
| vi.mock('read-package-up', () => ({ | |
| readPackageUp: vi.fn().mockResolvedValue({ | |
| packageJson: { name: 'test-pkg', version: 'test-version' }, | |
| path: '/fake/path/package.json', | |
| }), | |
| })); | |
| vi.mock('update-notifier', () => ({ | |
| default: vi.fn(() => ({ notify: vi.fn() })), | |
| })); | |
| vi.mock('./utils/events.js', async (importOriginal) => { | |
| const actual = await importOriginal<typeof import('./utils/events.js')>(); | |
| return { ...actual, appEvents: { emit: vi.fn() } }; | |
| }); | |
| vi.mock('./utils/sandbox.js', () => ({ | |
| sandbox_command: vi.fn(() => ''), | |
| start_sandbox: vi.fn(() => Promise.resolve()), | |
| })); | |
| vi.mock('./utils/relaunch.js', () => ({ | |
| relaunchAppInChildProcess: vi.fn(), | |
| relaunchOnExitCode: vi.fn(), | |
| })); | |
| vi.mock('./config/sandboxConfig.js', () => ({ | |
| loadSandboxConfig: vi.fn(), | |
| })); | |
| vi.mock('./ui/utils/mouse.js', () => ({ | |
| enableMouseEvents: vi.fn(), | |
| disableMouseEvents: vi.fn(), | |
| parseMouseEvent: vi.fn(), | |
| isIncompleteMouseSequence: vi.fn(), | |
| })); | |
| vi.mock('./validateNonInterActiveAuth.js', () => ({ | |
| validateNonInteractiveAuth: vi.fn().mockResolvedValue({}), | |
| })); | |
| vi.mock('./core/initializer.js', () => ({ | |
| initializeApp: vi.fn().mockResolvedValue({ | |
| authError: null, | |
| themeError: null, | |
| shouldOpenAuthDialog: false, | |
| geminiMdFileCount: 0, | |
| }), | |
| })); | |
| vi.mock('./nonInteractiveCli.js', () => ({ | |
| runNonInteractive: vi.fn().mockResolvedValue(undefined), | |
| })); | |
| vi.mock('./utils/cleanup.js', async (importOriginal) => { | |
| const actual = await importOriginal<typeof import('./utils/cleanup.js')>(); | |
| return { | |
| ...actual, | |
| cleanupCheckpoints: vi.fn().mockResolvedValue(undefined), | |
| registerCleanup: vi.fn(), | |
| removeCleanup: vi.fn(), | |
| registerSyncCleanup: vi.fn(), | |
| removeSyncCleanup: vi.fn(), | |
| registerTelemetryConfig: vi.fn(), | |
| runExitCleanup: vi.fn().mockResolvedValue(undefined), | |
| }; | |
| }); | |
| vi.mock('./acp/acpStdioTransport.js', () => ({ | |
| runAcpClient: vi.fn().mockResolvedValue(undefined), | |
| })); | |
| vi.mock('./zed-integration/zedIntegration.js', () => ({ | |
| runZedIntegration: vi.fn().mockResolvedValue(undefined), | |
| })); | |
| vi.mock('./utils/readStdin.js', () => ({ | |
| readStdin: vi.fn().mockResolvedValue(''), | |
| })); | |
| const { cleanupMockState } = vi.hoisted(() => ({ | |
| cleanupMockState: { shouldThrow: false, called: false }, | |
| })); | |
| // Mock sessionCleanup.js at the top level | |
| vi.mock('./utils/sessionCleanup.js', async (importOriginal) => { | |
| const actual = | |
| await importOriginal<typeof import('./utils/sessionCleanup.js')>(); | |
| return { | |
| ...actual, | |
| cleanupExpiredSessions: async () => { | |
| cleanupMockState.called = true; | |
| if (cleanupMockState.shouldThrow) { | |
| throw new Error('Cleanup failed'); | |
| } | |
| }, | |
| }; | |
| }); | |
| describe('gemini.tsx main function cleanup', () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| process.env['GEMINI_CLI_NO_RELAUNCH'] = 'true'; | |
| vi.stubEnv('GEMINI_CLI_TRUST_WORKSPACE', 'true'); | |
| }); | |
| afterEach(() => { | |
| delete process.env['GEMINI_CLI_NO_RELAUNCH']; | |
| vi.restoreAllMocks(); | |
| }); | |
| it.skip('should log error when cleanupExpiredSessions fails', async () => { | |
| const { loadCliConfig, parseArguments } = await import( | |
| './config/config.js' | |
| ); | |
| const { loadSettings } = await import('./config/settings.js'); | |
| cleanupMockState.shouldThrow = true; | |
| cleanupMockState.called = false; | |
| const debugLoggerErrorSpy = vi | |
| .spyOn(debugLogger, 'error') | |
| .mockImplementation(() => {}); | |
| vi.mocked(loadSettings).mockReturnValue({ | |
| merged: { advanced: {}, security: { auth: {} }, ui: {} }, | |
| workspace: { settings: {} }, | |
| setValue: vi.fn(), | |
| forScope: () => ({ settings: {}, originalSettings: {}, path: '' }), | |
| errors: [], | |
| } as unknown as ReturnType<typeof loadSettings>); | |
| vi.mocked(parseArguments).mockResolvedValue({ | |
| promptInteractive: false, | |
| } as unknown as Awaited<ReturnType<typeof parseArguments>>); | |
| vi.mocked(loadCliConfig).mockResolvedValue({ | |
| isInteractive: vi.fn(() => false), | |
| getQuestion: vi.fn(() => 'test'), | |
| getSandbox: vi.fn(() => false), | |
| getDebugMode: vi.fn(() => false), | |
| getPolicyEngine: vi.fn(), | |
| getSessionId: vi.fn().mockReturnValue('test-session-id'), | |
| getMessageBus: () => ({ subscribe: vi.fn() }), | |
| getEnableHooks: vi.fn(() => false), | |
| getHookSystem: () => undefined, | |
| initialize: vi.fn(), | |
| storage: { initialize: vi.fn().mockResolvedValue(undefined) }, | |
| getContentGeneratorConfig: vi.fn(), | |
| getMcpServers: () => ({}), | |
| getMcpClientManager: vi.fn(), | |
| getIdeMode: vi.fn(() => false), | |
| getAcpMode: vi.fn(() => true), | |
| getScreenReader: vi.fn(() => false), | |
| getGeminiMdFileCount: vi.fn(() => 0), | |
| getProjectRoot: vi.fn(() => '/'), | |
| getListExtensions: vi.fn(() => false), | |
| getListSessions: vi.fn(() => false), | |
| getDeleteSession: vi.fn(() => undefined), | |
| getToolRegistry: vi.fn(), | |
| getExtensions: vi.fn(() => []), | |
| getModel: vi.fn(() => 'gemini-pro'), | |
| getEmbeddingModel: vi.fn(() => 'embedding-001'), | |
| getApprovalMode: vi.fn(() => 'default'), | |
| getCoreTools: vi.fn(() => []), | |
| getTelemetryEnabled: vi.fn(() => false), | |
| getTelemetryLogPromptsEnabled: vi.fn(() => false), | |
| getFileFilteringRespectGitIgnore: vi.fn(() => true), | |
| getOutputFormat: vi.fn(() => 'text'), | |
| getUsageStatisticsEnabled: vi.fn(() => false), | |
| setTerminalBackground: vi.fn(), | |
| refreshAuth: vi.fn(), | |
| getRemoteAdminSettings: vi.fn(() => undefined), | |
| getUseAlternateBuffer: vi.fn(() => false), | |
| } as unknown as Config); | |
| await main(); | |
| expect(cleanupMockState.called).toBe(true); | |
| expect(debugLoggerErrorSpy).toHaveBeenCalledWith( | |
| 'Failed to cleanup expired sessions:', | |
| expect.objectContaining({ message: 'Cleanup failed' }), | |
| ); | |
| }); | |
| it('should register SessionEnd hook exactly once in non-interactive mode', async () => { | |
| const { loadCliConfig, parseArguments } = await import( | |
| './config/config.js' | |
| ); | |
| const { registerCleanup } = await import('./utils/cleanup.js'); | |
| const mockHookSystem = { | |
| fireSessionEndEvent: vi.fn().mockResolvedValue(undefined), | |
| fireSessionStartEvent: vi.fn().mockResolvedValue(undefined), | |
| } as unknown as HookSystem; | |
| vi.mocked(parseArguments).mockResolvedValue({ | |
| promptInteractive: false, | |
| } as unknown as Awaited<ReturnType<typeof parseArguments>>); | |
| vi.mocked(loadCliConfig).mockResolvedValue( | |
| buildMockConfig({ | |
| getHookSystem: vi.fn(() => mockHookSystem), | |
| getSessionId: vi.fn().mockReturnValue('test-session-id'), | |
| }), | |
| ); | |
| vi.spyOn(process, 'exit').mockImplementation(() => undefined as never); | |
| await main(); | |
| const registeredCallbacks = vi | |
| .mocked(registerCleanup) | |
| .mock.calls.map(([fn]) => fn); | |
| for (const fn of registeredCallbacks) await fn(); | |
| expect(mockHookSystem.fireSessionEndEvent).toHaveBeenCalledTimes(1); | |
| expect(mockHookSystem.fireSessionEndEvent).toHaveBeenCalledWith( | |
| SessionEndReason.Exit, | |
| ); | |
| }); | |
| it('should not register ConsolePatcher cleanup in ACP mode', async () => { | |
| const { registerCleanup } = await import('./utils/cleanup.js'); | |
| const { ConsolePatcher } = await import('./ui/utils/ConsolePatcher.js'); | |
| const { loadCliConfig, parseArguments } = await import( | |
| './config/config.js' | |
| ); | |
| const { loadSettings } = await import('./config/settings.js'); | |
| vi.mocked(parseArguments).mockResolvedValue({ | |
| acp: true, | |
| startupMessages: [], | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| } as any); | |
| vi.mocked(loadSettings).mockReturnValue({ | |
| merged: { | |
| tools: { allowed: [], exclude: [] }, | |
| advanced: { dnsResolutionOrder: 'ipv4first' }, | |
| security: { auth: { selectedType: 'google' } }, | |
| ui: { theme: 'default' }, | |
| }, | |
| workspace: { settings: {} }, | |
| errors: [], | |
| subscribe: vi.fn(), | |
| getSnapshot: vi.fn(), | |
| setValue: vi.fn(), | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| } as any); | |
| vi.mocked(loadCliConfig).mockResolvedValue( | |
| buildMockConfig({ | |
| getAcpMode: () => true, | |
| }), | |
| ); | |
| let capturedCleanup: () => void; | |
| vi.mocked(ConsolePatcher).mockImplementation(() => { | |
| const instance = { | |
| patch: vi.fn(), | |
| cleanup: vi.fn(), | |
| }; | |
| capturedCleanup = instance.cleanup; | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| return instance as any; | |
| }); | |
| await main(); | |
| const registeredFunctions = vi | |
| .mocked(registerCleanup) | |
| .mock.calls.map((call) => call[0]); | |
| expect(registeredFunctions).not.toContain(capturedCleanup!); | |
| }); | |
| it('should register ConsolePatcher cleanup in non-ACP mode', async () => { | |
| const { registerCleanup } = await import('./utils/cleanup.js'); | |
| const { ConsolePatcher } = await import('./ui/utils/ConsolePatcher.js'); | |
| const { loadCliConfig, parseArguments } = await import( | |
| './config/config.js' | |
| ); | |
| const { loadSettings } = await import('./config/settings.js'); | |
| vi.mocked(parseArguments).mockResolvedValue({ | |
| acp: false, | |
| query: 'test', | |
| startupMessages: [], | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| } as any); | |
| vi.mocked(loadSettings).mockReturnValue({ | |
| merged: { | |
| tools: { allowed: [], exclude: [] }, | |
| advanced: { dnsResolutionOrder: 'ipv4first' }, | |
| security: { auth: { selectedType: 'google' } }, | |
| ui: { theme: 'default' }, | |
| }, | |
| workspace: { settings: {} }, | |
| errors: [], | |
| subscribe: vi.fn(), | |
| getSnapshot: vi.fn(), | |
| setValue: vi.fn(), | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| } as any); | |
| vi.mocked(loadCliConfig).mockResolvedValue( | |
| buildMockConfig({ | |
| getAcpMode: () => false, | |
| getQuestion: () => 'test', | |
| }), | |
| ); | |
| let capturedCleanup: () => void; | |
| vi.mocked(ConsolePatcher).mockImplementation(() => { | |
| const instance = { | |
| patch: vi.fn(), | |
| cleanup: vi.fn(), | |
| }; | |
| capturedCleanup = instance.cleanup; | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| return instance as any; | |
| }); | |
| try { | |
| await main(); | |
| } catch { | |
| // Ignore errors from incomplete mocks in full main() execution | |
| } | |
| const registeredFunctions = vi | |
| .mocked(registerCleanup) | |
| .mock.calls.map((call) => call[0]); | |
| expect(registeredFunctions).toContain(capturedCleanup!); | |
| }); | |
| function buildMockConfig(overrides: Partial<Config> = {}): Config { | |
| return { | |
| isInteractive: vi.fn(() => false), | |
| getQuestion: vi.fn(() => 'test'), | |
| getSandbox: vi.fn(() => false), | |
| getDebugMode: vi.fn(() => false), | |
| getPolicyEngine: vi.fn(), | |
| getMessageBus: () => ({ subscribe: vi.fn() }), | |
| getEnableHooks: vi.fn(() => true), | |
| getHookSystem: vi.fn(() => undefined), | |
| getExperimentalGemma: vi.fn(() => false), | |
| initialize: vi.fn(), | |
| storage: { initialize: vi.fn().mockResolvedValue(undefined) }, | |
| getContentGeneratorConfig: vi.fn(), | |
| getMcpClientManager: vi.fn(), | |
| getIdeMode: vi.fn(() => false), | |
| getAcpMode: vi.fn(() => false), | |
| getScreenReader: vi.fn(() => false), | |
| getGeminiMdFileCount: vi.fn(() => 0), | |
| getProjectRoot: vi.fn(() => '/'), | |
| getListExtensions: vi.fn(() => false), | |
| getListSessions: vi.fn(() => false), | |
| getDeleteSession: vi.fn(() => undefined), | |
| getExtensions: vi.fn(() => []), | |
| getModel: vi.fn(() => 'gemini-pro'), | |
| getEmbeddingModel: vi.fn(() => 'embedding-001'), | |
| getApprovalMode: vi.fn(() => 'default'), | |
| getCoreTools: vi.fn(() => []), | |
| getTelemetryEnabled: vi.fn(() => false), | |
| getTelemetryLogPromptsEnabled: vi.fn(() => false), | |
| getFileFilteringRespectGitIgnore: vi.fn(() => true), | |
| getOutputFormat: vi.fn(() => 'text'), | |
| getUsageStatisticsEnabled: vi.fn(() => false), | |
| setTerminalBackground: vi.fn(), | |
| refreshAuth: vi.fn(), | |
| getRemoteAdminSettings: vi.fn(() => undefined), | |
| getUseAlternateBuffer: vi.fn(() => false), | |
| getUseTerminalBuffer: vi.fn(() => false), | |
| ...overrides, | |
| } as unknown as Config; | |
| } | |
| }); | |