diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 48205e9389..5df7be9963 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -23,7 +23,7 @@ import { } from '@google/gemini-cli-core'; import type { Part } from '@google/genai'; import { runNonInteractive } from './nonInteractiveCli.js'; -import { vi } from 'vitest'; +import { vi, type Mock, type MockInstance } from 'vitest'; import type { LoadedSettings } from './config/settings.js'; // Mock core modules @@ -63,13 +63,13 @@ describe('runNonInteractive', () => { let mockConfig: Config; let mockSettings: LoadedSettings; let mockToolRegistry: ToolRegistry; - let mockCoreExecuteToolCall: vi.Mock; - let mockShutdownTelemetry: vi.Mock; - let consoleErrorSpy: vi.SpyInstance; - let processStdoutSpy: vi.SpyInstance; + let mockCoreExecuteToolCall: Mock; + let mockShutdownTelemetry: Mock; + let consoleErrorSpy: MockInstance; + let processStdoutSpy: MockInstance; let mockGeminiClient: { - sendMessageStream: vi.Mock; - getChatRecordingService: vi.Mock; + sendMessageStream: Mock; + getChatRecordingService: Mock; }; beforeEach(async () => { diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index 904a7f7080..1067945ab6 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -4,17 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect, vi, type Mock } from 'vitest'; import { render } from 'ink-testing-library'; import { Text, useIsScreenReaderEnabled } from 'ink'; +import { makeFakeConfig } from '@google/gemini-cli-core'; import { App } from './App.js'; import { UIStateContext, type UIState } from './contexts/UIStateContext.js'; import { StreamingState } from './types.js'; -import { - ConfigContext, - type Config, - type Telemetry, -} from './contexts/ConfigContext.js'; +import { ConfigContext } from './contexts/ConfigContext.js'; vi.mock('ink', async (importOriginal) => { const original = await importOriginal(); @@ -64,9 +61,7 @@ describe('App', () => { }, }; - const mockConfig = { - telemetry: {} as Telemetry, - } as Config; + const mockConfig = makeFakeConfig(); const renderWithProviders = (ui: React.ReactElement, state: UIState) => render( @@ -132,7 +127,7 @@ describe('App', () => { }); it('should render ScreenReaderAppLayout when screen reader is enabled', () => { - (useIsScreenReaderEnabled as vi.Mock).mockReturnValue(true); + (useIsScreenReaderEnabled as Mock).mockReturnValue(true); const { lastFrame } = renderWithProviders(, mockUIState as UIState); @@ -142,7 +137,7 @@ describe('App', () => { }); it('should render DefaultAppLayout when screen reader is not enabled', () => { - (useIsScreenReaderEnabled as vi.Mock).mockReturnValue(false); + (useIsScreenReaderEnabled as Mock).mockReturnValue(false); const { lastFrame } = renderWithProviders(, mockUIState as UIState); diff --git a/packages/cli/src/ui/contexts/SessionContext.test.tsx b/packages/cli/src/ui/contexts/SessionContext.test.tsx index dbb89628da..c80262e503 100644 --- a/packages/cli/src/ui/contexts/SessionContext.test.tsx +++ b/packages/cli/src/ui/contexts/SessionContext.test.tsx @@ -84,6 +84,7 @@ describe('SessionStatsContext', () => { accept: 1, reject: 0, modify: 0, + auto_accept: 0, }, byName: { 'test-tool': { @@ -95,10 +96,15 @@ describe('SessionStatsContext', () => { accept: 1, reject: 0, modify: 0, + auto_accept: 0, }, }, }, }, + files: { + totalLinesAdded: 0, + totalLinesRemoved: 0, + }, }; act(() => { @@ -152,9 +158,13 @@ describe('SessionStatsContext', () => { totalSuccess: 0, totalFail: 0, totalDurationMs: 0, - totalDecisions: { accept: 0, reject: 0, modify: 0 }, + totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0 }, byName: {}, }, + files: { + totalLinesAdded: 0, + totalLinesRemoved: 0, + }, }; act(() => { diff --git a/packages/cli/src/ui/themes/theme.test.ts b/packages/cli/src/ui/themes/theme.test.ts index 6359a92286..1c06181cac 100644 --- a/packages/cli/src/ui/themes/theme.test.ts +++ b/packages/cli/src/ui/themes/theme.test.ts @@ -86,7 +86,9 @@ describe('themeManager.loadCustomThemes', () => { delete legacyTheme.DiffAdded; delete legacyTheme.DiffRemoved; - themeManager.loadCustomThemes({ 'Legacy Custom Theme': legacyTheme }); + themeManager.loadCustomThemes({ + 'Legacy Custom Theme': legacyTheme as CustomTheme, + }); const result = themeManager.getTheme('Legacy Custom Theme')!; expect(result.colors.DiffAdded).toBe(darkTheme.DiffAdded); diff --git a/packages/cli/src/validateNonInterActiveAuth.test.ts b/packages/cli/src/validateNonInterActiveAuth.test.ts index 9c570da500..d2a7e8e767 100644 --- a/packages/cli/src/validateNonInterActiveAuth.test.ts +++ b/packages/cli/src/validateNonInterActiveAuth.test.ts @@ -4,20 +4,39 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { + describe, + it, + expect, + vi, + beforeEach, + afterEach, + type MockInstance, + type Mock, +} from 'vitest'; import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js'; -import { AuthType, OutputFormat } from '@google/gemini-cli-core'; +import { + AuthType, + OutputFormat, + makeFakeConfig, +} from '@google/gemini-cli-core'; import type { Config } from '@google/gemini-cli-core'; import * as auth from './config/auth.js'; import { type LoadedSettings } from './config/settings.js'; +function createLocalMockConfig(overrides: Partial = {}): Config { + const config = makeFakeConfig(); + Object.assign(config, overrides); + return config; +} + describe('validateNonInterActiveAuth', () => { let originalEnvGeminiApiKey: string | undefined; let originalEnvVertexAi: string | undefined; let originalEnvGcp: string | undefined; let consoleErrorSpy: ReturnType; - let processExitSpy: ReturnType; - let refreshAuthMock: vi.Mock; + let processExitSpy: MockInstance; + let refreshAuthMock: Mock; let mockSettings: LoadedSettings; beforeEach(() => { @@ -28,11 +47,16 @@ describe('validateNonInterActiveAuth', () => { delete process.env['GOOGLE_GENAI_USE_VERTEXAI']; delete process.env['GOOGLE_GENAI_USE_GCA']; consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - processExitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => { - throw new Error(`process.exit(${code}) called`); - }); + processExitSpy = vi + .spyOn(process, 'exit') + .mockImplementation((code?: string | number | null | undefined) => { + throw new Error(`process.exit(${code}) called`); + }); vi.spyOn(auth, 'validateAuthMethod').mockReturnValue(null); - refreshAuthMock = vi.fn().mockResolvedValue('refreshed'); + refreshAuthMock = vi.fn().mockImplementation(async () => { + console.log('DEBUG: refreshAuthMock called'); + return 'refreshed'; + }); mockSettings = { system: { path: '', settings: {} }, systemDefaults: { path: '', settings: {} }, @@ -74,13 +98,13 @@ describe('validateNonInterActiveAuth', () => { }); it('exits if no auth type is configured or env vars set', async () => { - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), getContentGeneratorConfig: vi .fn() .mockReturnValue({ authType: undefined }), - }; + }); try { await validateNonInteractiveAuth( undefined, @@ -100,9 +124,9 @@ describe('validateNonInterActiveAuth', () => { it('uses LOGIN_WITH_GOOGLE if GOOGLE_GENAI_USE_GCA is set', async () => { process.env['GOOGLE_GENAI_USE_GCA'] = 'true'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -114,9 +138,9 @@ describe('validateNonInterActiveAuth', () => { it('uses USE_GEMINI if GEMINI_API_KEY is set', async () => { process.env['GEMINI_API_KEY'] = 'fake-key'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -130,9 +154,9 @@ describe('validateNonInterActiveAuth', () => { process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true'; process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project'; process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -145,9 +169,9 @@ describe('validateNonInterActiveAuth', () => { it('uses USE_VERTEX_AI if GOOGLE_GENAI_USE_VERTEXAI is true and GOOGLE_API_KEY is set', async () => { process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true'; process.env['GOOGLE_API_KEY'] = 'vertex-api-key'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -163,9 +187,9 @@ describe('validateNonInterActiveAuth', () => { process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true'; process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project'; process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -180,9 +204,9 @@ describe('validateNonInterActiveAuth', () => { process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true'; process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project'; process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -197,9 +221,9 @@ describe('validateNonInterActiveAuth', () => { process.env['GEMINI_API_KEY'] = 'fake-key'; process.env['GOOGLE_CLOUD_PROJECT'] = 'test-project'; process.env['GOOGLE_CLOUD_LOCATION'] = 'us-central1'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -211,9 +235,9 @@ describe('validateNonInterActiveAuth', () => { it('uses configuredAuthType over environment variables', async () => { process.env['GEMINI_API_KEY'] = 'fake-key'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( AuthType.LOGIN_WITH_GOOGLE, undefined, @@ -226,13 +250,13 @@ describe('validateNonInterActiveAuth', () => { it('exits if validateAuthMethod returns error', async () => { // Mock validateAuthMethod to return error vi.spyOn(auth, 'validateAuthMethod').mockReturnValue('Auth error!'); - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), getContentGeneratorConfig: vi .fn() .mockReturnValue({ authType: undefined }), - }; + }); try { await validateNonInteractiveAuth( AuthType.USE_GEMINI, @@ -253,10 +277,9 @@ describe('validateNonInterActiveAuth', () => { const validateAuthMethodSpy = vi .spyOn(auth, 'validateAuthMethod') .mockReturnValue('Auth error!'); - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; - + }); // Even with an invalid auth type, it should not exit // because validation is skipped. await validateNonInteractiveAuth( @@ -274,11 +297,11 @@ describe('validateNonInterActiveAuth', () => { }); it('succeeds if effectiveAuthType matches enforcedAuthType', async () => { - mockSettings.merged.security.auth.enforcedType = AuthType.USE_GEMINI; + mockSettings.merged.security!.auth!.enforcedType = AuthType.USE_GEMINI; process.env['GEMINI_API_KEY'] = 'fake-key'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, - }; + }); await validateNonInteractiveAuth( undefined, undefined, @@ -289,11 +312,12 @@ describe('validateNonInterActiveAuth', () => { }); it('exits if configuredAuthType does not match enforcedAuthType', async () => { - mockSettings.merged.security.auth.enforcedType = AuthType.LOGIN_WITH_GOOGLE; - const nonInteractiveConfig = { + mockSettings.merged.security!.auth!.enforcedType = + AuthType.LOGIN_WITH_GOOGLE; + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), - }; + }); try { await validateNonInteractiveAuth( AuthType.USE_GEMINI, @@ -312,12 +336,13 @@ describe('validateNonInterActiveAuth', () => { }); it('exits if auth from env var does not match enforcedAuthType', async () => { - mockSettings.merged.security.auth.enforcedType = AuthType.LOGIN_WITH_GOOGLE; + mockSettings.merged.security!.auth!.enforcedType = + AuthType.LOGIN_WITH_GOOGLE; process.env['GEMINI_API_KEY'] = 'fake-key'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), - }; + }); try { await validateNonInteractiveAuth( undefined, @@ -337,20 +362,20 @@ describe('validateNonInterActiveAuth', () => { describe('JSON output mode', () => { it('prints JSON error when no auth is configured and exits with code 1', async () => { - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), getContentGeneratorConfig: vi .fn() .mockReturnValue({ authType: undefined }), - }; + }); let thrown: Error | undefined; try { await validateNonInteractiveAuth( undefined, undefined, - nonInteractiveConfig as unknown as Config, + nonInteractiveConfig, mockSettings, ); } catch (e) { @@ -368,21 +393,21 @@ describe('validateNonInterActiveAuth', () => { }); it('prints JSON error when enforced auth mismatches current auth and exits with code 1', async () => { - mockSettings.merged.security.auth.enforcedType = AuthType.USE_GEMINI; - const nonInteractiveConfig = { + mockSettings.merged.security!.auth!.enforcedType = AuthType.USE_GEMINI; + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), getContentGeneratorConfig: vi .fn() .mockReturnValue({ authType: undefined }), - }; + }); let thrown: Error | undefined; try { await validateNonInteractiveAuth( AuthType.LOGIN_WITH_GOOGLE, undefined, - nonInteractiveConfig as unknown as Config, + nonInteractiveConfig, mockSettings, ); } catch (e) { @@ -405,20 +430,20 @@ describe('validateNonInterActiveAuth', () => { vi.spyOn(auth, 'validateAuthMethod').mockReturnValue('Auth error!'); process.env['GEMINI_API_KEY'] = 'fake-key'; - const nonInteractiveConfig = { + const nonInteractiveConfig = createLocalMockConfig({ refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), getContentGeneratorConfig: vi .fn() .mockReturnValue({ authType: undefined }), - }; + }); let thrown: Error | undefined; try { await validateNonInteractiveAuth( AuthType.USE_GEMINI, undefined, - nonInteractiveConfig as unknown as Config, + nonInteractiveConfig, mockSettings, ); } catch (e) { diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 72addd0433..0ea51a3277 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -17,14 +17,9 @@ "node_modules", "dist", // TODO(5691): Fix type errors and remove excludes. - "src/nonInteractiveCli.test.ts", - "src/services/FileCommandLoader.test.ts", - "src/services/prompt-processors/argumentProcessor.test.ts", "src/utils/cleanup.test.ts", "src/utils/handleAutoUpdate.test.ts", "src/utils/startupWarnings.test.ts", - "src/ui/App.test.tsx", - "src/ui/contexts/SessionContext.test.tsx", "src/ui/hooks/slashCommandProcessor.test.ts", "src/ui/hooks/useAtCompletion.test.ts", "src/ui/hooks/useConsoleMessages.test.ts", @@ -35,11 +30,7 @@ "src/ui/hooks/useKeypress.test.ts", "src/ui/hooks/usePhraseCycler.test.ts", "src/ui/hooks/vim.test.ts", - "src/ui/utils/computeStats.test.ts", - "src/ui/themes/theme.test.ts", - "src/validateNonInterActiveAuth.test.ts", - "src/services/prompt-processors/shellProcessor.test.ts", - "src/commands/extensions/examples/**" + "src/ui/utils/computeStats.test.ts" ], "references": [{ "path": "../core" }] }