/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
import { main, setupUnhandledRejectionHandler, validateDnsResolutionOrder, startInteractiveUI, getNodeMemoryArgs, } from './gemini.js';
import os from 'node:os';
import v8 from 'node:v8';
import {} from './config/settings.js';
import { appEvents, AppEvent } from './utils/events.js';
import { debugLogger, } from '@google/gemini-cli-core';
import { act } from 'react';
import {} from './core/initializer.js';
const performance = vi.hoisted(() => ({
    now: vi.fn(),
}));
vi.stubGlobal('performance', performance);
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        recordSlowRender: vi.fn(),
        writeToStdout: vi.fn((...args) => process.stdout.write(...args)),
        patchStdio: vi.fn(() => () => { }),
        createInkStdio: vi.fn(() => ({
            stdout: {
                write: vi.fn((...args) => process.stdout.write(...args)),
                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(),
    };
});
vi.mock('ink', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        render: vi.fn((_node, options) => {
            if (options.alternateBuffer) {
                options.stdout.write('\x1b[?7l');
            }
            // Simulate rendering time for recordSlowRender test
            const start = performance.now();
            const end = performance.now();
            if (options.onRender) {
                options.onRender({ renderTime: end - start });
            }
            return {
                unmount: vi.fn(),
                rerender: vi.fn(),
                cleanup: vi.fn(),
                waitUntilExit: vi.fn(),
            };
        }),
    };
});
// Custom error to identify mock process.exit calls
class MockProcessExitError extends Error {
    code;
    constructor(code) {
        super('PROCESS_EXIT_MOCKED');
        this.code = code;
        this.name = 'MockProcessExitError';
    }
}
// Mock dependencies
vi.mock('./config/settings.js', () => ({
    loadSettings: vi.fn().mockReturnValue({
        merged: {
            advanced: {},
            security: { auth: {} },
            ui: {},
        },
        setValue: vi.fn(),
        forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
        errors: [],
    }),
    migrateDeprecatedSettings: vi.fn(),
    SettingScope: {
        User: 'user',
        Workspace: 'workspace',
        System: 'system',
        SystemDefaults: 'system-defaults',
    },
}));
vi.mock('./config/config.js', () => ({
    loadCliConfig: vi.fn().mockResolvedValue({
        getSandbox: vi.fn(() => false),
        getQuestion: vi.fn(() => ''),
        isInteractive: () => false,
    }),
    parseArguments: vi.fn().mockResolvedValue({}),
    isDebugMode: vi.fn(() => false),
}));
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();
    return {
        ...actual,
        appEvents: {
            emit: vi.fn(),
        },
    };
});
vi.mock('./utils/sandbox.js', () => ({
    sandbox_command: vi.fn(() => ''), // Default to no sandbox command
    start_sandbox: vi.fn(() => Promise.resolve()), // Mock as an async function that resolves
}));
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(),
}));
describe('gemini.tsx main function', () => {
    let originalEnvGeminiSandbox;
    let originalEnvSandbox;
    let initialUnhandledRejectionListeners = [];
    beforeEach(() => {
        // Store and clear sandbox-related env variables to ensure a consistent test environment
        originalEnvGeminiSandbox = process.env['GEMINI_SANDBOX'];
        originalEnvSandbox = process.env['SANDBOX'];
        delete process.env['GEMINI_SANDBOX'];
        delete process.env['SANDBOX'];
        initialUnhandledRejectionListeners =
            process.listeners('unhandledRejection');
    });
    afterEach(() => {
        // Restore original env variables
        if (originalEnvGeminiSandbox !== undefined) {
            process.env['GEMINI_SANDBOX'] = originalEnvGeminiSandbox;
        }
        else {
            delete process.env['GEMINI_SANDBOX'];
        }
        if (originalEnvSandbox !== undefined) {
            process.env['SANDBOX'] = originalEnvSandbox;
        }
        else {
            delete process.env['SANDBOX'];
        }
        const currentListeners = process.listeners('unhandledRejection');
        const addedListener = currentListeners.find((listener) => !initialUnhandledRejectionListeners.includes(listener));
        if (addedListener) {
            process.removeListener('unhandledRejection', addedListener);
        }
        vi.restoreAllMocks();
    });
    it('verifies that we dont load the config before relaunchAppInChildProcess', async () => {
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        const { relaunchAppInChildProcess } = await import('./utils/relaunch.js');
        const { loadCliConfig } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
        vi.mocked(loadSandboxConfig).mockResolvedValue(undefined);
        const callOrder = [];
        vi.mocked(relaunchAppInChildProcess).mockImplementation(async () => {
            callOrder.push('relaunch');
        });
        vi.mocked(loadCliConfig).mockImplementation(async () => {
            callOrder.push('loadCliConfig');
            return {
                isInteractive: () => false,
                getQuestion: () => '',
                getSandbox: () => false,
                getDebugMode: () => false,
                getListExtensions: () => false,
                getListSessions: () => false,
                getDeleteSession: () => undefined,
                getMcpServers: () => ({}),
                getMcpClientManager: vi.fn(),
                initialize: vi.fn(),
                getIdeMode: () => false,
                getExperimentalZedIntegration: () => false,
                getScreenReader: () => false,
                getGeminiMdFileCount: () => 0,
                getProjectRoot: () => '/',
                getPolicyEngine: vi.fn(),
                getMessageBus: () => ({
                    subscribe: vi.fn(),
                }),
                getToolRegistry: vi.fn(),
                getContentGeneratorConfig: vi.fn(),
                getModel: () => 'gemini-pro',
                getEmbeddingModel: () => 'embedding-001',
                getApprovalMode: () => 'default',
                getCoreTools: () => [],
                getTelemetryEnabled: () => false,
                getTelemetryLogPromptsEnabled: () => false,
                getFileFilteringRespectGitIgnore: () => true,
                getOutputFormat: () => 'text',
                getExtensions: () => [],
                getUsageStatisticsEnabled: () => false,
            };
        });
        vi.mocked(loadSettings).mockReturnValue({
            errors: [],
            merged: {
                advanced: { autoConfigureMemory: true },
                security: { auth: {} },
                ui: {},
            },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
        });
        try {
            await main();
        }
        catch (e) {
            // Mocked process exit throws an error.
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        // It is critical that we call relaunch before loadCliConfig to avoid
        // loading config in the outer process when we are going to relaunch.
        // By ensuring we don't load the config we also ensure we don't trigger any
        // operations that might require loading the config such as such as
        // initializing mcp servers.
        // For the sandbox case we still have to load a partial cli config.
        // we can authorize outside the sandbox.
        expect(callOrder).toEqual(['relaunch', 'loadCliConfig']);
        processExitSpy.mockRestore();
    });
    it('should log unhandled promise rejections and open debug console on first error', async () => {
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        const appEventsMock = vi.mocked(appEvents);
        const debugLoggerErrorSpy = vi.spyOn(debugLogger, 'error');
        const rejectionError = new Error('Test unhandled rejection');
        setupUnhandledRejectionHandler();
        // Simulate an unhandled rejection.
        // We are not using Promise.reject here as vitest will catch it.
        // Instead we will dispatch the event manually.
        process.emit('unhandledRejection', rejectionError, Promise.resolve());
        // We need to wait for the rejection handler to be called.
        await new Promise(process.nextTick);
        expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvent.OpenDebugConsole);
        expect(debugLoggerErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Unhandled Promise Rejection'));
        expect(debugLoggerErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Please file a bug report using the /bug tool.'));
        // Simulate a second rejection
        const secondRejectionError = new Error('Second test unhandled rejection');
        process.emit('unhandledRejection', secondRejectionError, Promise.resolve());
        await new Promise(process.nextTick);
        // Ensure emit was only called once for OpenDebugConsole
        const openDebugConsoleCalls = appEventsMock.emit.mock.calls.filter((call) => call[0] === AppEvent.OpenDebugConsole);
        expect(openDebugConsoleCalls.length).toBe(1);
        // Avoid the process.exit error from being thrown.
        processExitSpy.mockRestore();
    });
});
describe('setWindowTitle', () => {
    it('should set window title when hideWindowTitle is false', async () => {
        // setWindowTitle is not exported, but we can test its effect if we had a way to call it.
        // Since we can't easily call it directly without exporting it, we skip direct testing
        // and rely on startInteractiveUI tests which call it.
    });
});
describe('initializeOutputListenersAndFlush', () => {
    afterEach(() => {
        vi.restoreAllMocks();
    });
    it('should flush backlogs and setup listeners if no listeners exist', async () => {
        const { coreEvents } = await import('@google/gemini-cli-core');
        const { initializeOutputListenersAndFlush } = await import('./gemini.js');
        // Mock listenerCount to return 0
        vi.spyOn(coreEvents, 'listenerCount').mockReturnValue(0);
        const drainSpy = vi.spyOn(coreEvents, 'drainBacklogs');
        initializeOutputListenersAndFlush();
        expect(drainSpy).toHaveBeenCalled();
        // We can't easily check if listeners were added without access to the internal state of coreEvents,
        // but we can verify that drainBacklogs was called.
    });
});
describe('getNodeMemoryArgs', () => {
    let osTotalMemSpy;
    let v8GetHeapStatisticsSpy;
    beforeEach(() => {
        osTotalMemSpy = vi.spyOn(os, 'totalmem');
        v8GetHeapStatisticsSpy = vi.spyOn(v8, 'getHeapStatistics');
        delete process.env['GEMINI_CLI_NO_RELAUNCH'];
    });
    afterEach(() => {
        vi.restoreAllMocks();
    });
    it('should return empty array if GEMINI_CLI_NO_RELAUNCH is set', () => {
        process.env['GEMINI_CLI_NO_RELAUNCH'] = 'true';
        expect(getNodeMemoryArgs(false)).toEqual([]);
    });
    it('should return empty array if current heap limit is sufficient', () => {
        osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024); // 16GB
        v8GetHeapStatisticsSpy.mockReturnValue({
            heap_size_limit: 8 * 1024 * 1024 * 1024, // 8GB
        });
        // Target is 50% of 16GB = 8GB. Current is 8GB. No relaunch needed.
        expect(getNodeMemoryArgs(false)).toEqual([]);
    });
    it('should return memory args if current heap limit is insufficient', () => {
        osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024); // 16GB
        v8GetHeapStatisticsSpy.mockReturnValue({
            heap_size_limit: 4 * 1024 * 1024 * 1024, // 4GB
        });
        // Target is 50% of 16GB = 8GB. Current is 4GB. Relaunch needed.
        expect(getNodeMemoryArgs(false)).toEqual(['--max-old-space-size=8192']);
    });
    it('should log debug info when isDebugMode is true', () => {
        const debugSpy = vi.spyOn(debugLogger, 'debug');
        osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024);
        v8GetHeapStatisticsSpy.mockReturnValue({
            heap_size_limit: 4 * 1024 * 1024 * 1024,
        });
        getNodeMemoryArgs(true);
        expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining('Current heap size'));
        expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining('Need to relaunch with more memory'));
    });
});
describe('gemini.tsx main function kitty protocol', () => {
    let originalEnvNoRelaunch;
    let setRawModeSpy;
    beforeEach(() => {
        // Set no relaunch in tests since process spawning causing issues in tests
        originalEnvNoRelaunch = process.env['GEMINI_CLI_NO_RELAUNCH'];
        process.env['GEMINI_CLI_NO_RELAUNCH'] = 'true';
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        if (!process.stdin.setRawMode) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            process.stdin.setRawMode = vi.fn();
        }
        setRawModeSpy = vi.spyOn(process.stdin, 'setRawMode');
        Object.defineProperty(process.stdin, 'isTTY', {
            value: true,
            configurable: true,
        });
        Object.defineProperty(process.stdin, 'isRaw', {
            value: false,
            configurable: true,
        });
    });
    afterEach(() => {
        // Restore original env variables
        if (originalEnvNoRelaunch !== undefined) {
            process.env['GEMINI_CLI_NO_RELAUNCH'] = originalEnvNoRelaunch;
        }
        else {
            delete process.env['GEMINI_CLI_NO_RELAUNCH'];
        }
        vi.restoreAllMocks();
    });
    it('should call setRawMode and detectAndEnableKittyProtocol when isInteractive is true', async () => {
        const { detectAndEnableKittyProtocol } = await import('./ui/utils/kittyProtocolDetector.js');
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        vi.mocked(loadCliConfig).mockResolvedValue({
            isInteractive: () => true,
            getQuestion: () => '',
            getSandbox: () => false,
            getDebugMode: () => false,
            getListExtensions: () => false,
            getListSessions: () => false,
            getDeleteSession: () => undefined,
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            initialize: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({
                subscribe: vi.fn(),
            }),
            getToolRegistry: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getModel: () => 'gemini-pro',
            getEmbeddingModel: () => 'embedding-001',
            getApprovalMode: () => 'default',
            getCoreTools: () => [],
            getTelemetryEnabled: () => false,
            getTelemetryLogPromptsEnabled: () => false,
            getFileFilteringRespectGitIgnore: () => true,
            getOutputFormat: () => 'text',
            getExtensions: () => [],
            getUsageStatisticsEnabled: () => false,
        });
        vi.mocked(loadSettings).mockReturnValue({
            errors: [],
            merged: {
                advanced: {},
                security: { auth: {} },
                ui: {},
            },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
        });
        vi.mocked(parseArguments).mockResolvedValue({
            model: undefined,
            sandbox: undefined,
            debug: undefined,
            prompt: undefined,
            promptInteractive: undefined,
            query: undefined,
            yolo: undefined,
            approvalMode: undefined,
            allowedMcpServerNames: undefined,
            allowedTools: undefined,
            experimentalAcp: undefined,
            extensions: undefined,
            listExtensions: undefined,
            includeDirectories: undefined,
            screenReader: undefined,
            useSmartEdit: undefined,
            useWriteTodos: undefined,
            resume: undefined,
            listSessions: undefined,
            deleteSession: undefined,
            outputFormat: undefined,
            fakeResponses: undefined,
            recordResponses: undefined,
        });
        await act(async () => {
            await main();
        });
        expect(setRawModeSpy).toHaveBeenCalledWith(true);
        expect(detectAndEnableKittyProtocol).toHaveBeenCalledTimes(1);
    });
    it.each([
        { flag: 'listExtensions' },
        { flag: 'listSessions' },
        { flag: 'deleteSession', value: 'session-id' },
    ])('should handle --$flag flag', async ({ flag, value }) => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        const { listSessions, deleteSession } = await import('./utils/sessions.js');
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        vi.mocked(loadSettings).mockReturnValue({
            merged: {
                advanced: {},
                security: { auth: {} },
                ui: {},
            },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        const mockConfig = {
            isInteractive: () => false,
            getQuestion: () => '',
            getSandbox: () => false,
            getDebugMode: () => false,
            getListExtensions: () => flag === 'listExtensions',
            getListSessions: () => flag === 'listSessions',
            getDeleteSession: () => (flag === 'deleteSession' ? value : undefined),
            getExtensions: () => [{ name: 'ext1' }],
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({ subscribe: vi.fn() }),
            initialize: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getProjectRoot: () => '/',
        };
        vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
        vi.mock('./utils/sessions.js', () => ({
            listSessions: vi.fn(),
            deleteSession: vi.fn(),
        }));
        const debugLoggerLogSpy = vi
            .spyOn(debugLogger, 'log')
            .mockImplementation(() => { });
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        if (flag === 'listExtensions') {
            expect(debugLoggerLogSpy).toHaveBeenCalledWith(expect.stringContaining('ext1'));
        }
        else if (flag === 'listSessions') {
            expect(listSessions).toHaveBeenCalledWith(mockConfig);
        }
        else if (flag === 'deleteSession') {
            expect(deleteSession).toHaveBeenCalledWith(mockConfig, value);
        }
        expect(processExitSpy).toHaveBeenCalledWith(0);
        processExitSpy.mockRestore();
    });
    it('should handle sandbox activation', async () => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
        const { start_sandbox } = await import('./utils/sandbox.js');
        const { relaunchOnExitCode } = await import('./utils/relaunch.js');
        const { loadSettings } = await import('./config/settings.js');
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadSettings).mockReturnValue({
            merged: {
                advanced: {},
                security: { auth: {} },
                ui: {},
            },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        const mockConfig = {
            isInteractive: () => false,
            getQuestion: () => '',
            getSandbox: () => true,
            getDebugMode: () => false,
            getListExtensions: () => false,
            getListSessions: () => false,
            getDeleteSession: () => undefined,
            getExtensions: () => [],
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({ subscribe: vi.fn() }),
            initialize: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getProjectRoot: () => '/',
            refreshAuth: vi.fn(),
        };
        vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
        vi.mocked(loadSandboxConfig).mockResolvedValue({}); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(relaunchOnExitCode).mockImplementation(async (fn) => {
            await fn();
        });
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        expect(start_sandbox).toHaveBeenCalled();
        expect(processExitSpy).toHaveBeenCalledWith(0);
        processExitSpy.mockRestore();
    });
    it('should exit with error when --prompt-interactive is used with piped input', async () => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        const core = await import('@google/gemini-cli-core');
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        const writeToStderrSpy = vi
            .spyOn(core, 'writeToStderr')
            .mockImplementation(() => true);
        vi.mocked(loadSettings).mockReturnValue({
            merged: { advanced: {}, security: { auth: {} }, ui: {} },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: true,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadCliConfig).mockResolvedValue({
            isInteractive: () => false,
            getQuestion: () => '',
            getSandbox: () => false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        // Mock stdin to be non-TTY
        Object.defineProperty(process.stdin, 'isTTY', {
            value: false,
            configurable: true,
        });
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        expect(writeToStderrSpy).toHaveBeenCalledWith(expect.stringContaining('Error: The --prompt-interactive flag cannot be used'));
        expect(processExitSpy).toHaveBeenCalledWith(1);
        processExitSpy.mockRestore();
        writeToStderrSpy.mockRestore();
        Object.defineProperty(process.stdin, 'isTTY', {
            value: true,
            configurable: true,
        }); // Restore TTY
    });
    it('should log warning when theme is not found', async () => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        const { themeManager } = await import('./ui/themes/theme-manager.js');
        const debugLoggerWarnSpy = vi
            .spyOn(debugLogger, 'warn')
            .mockImplementation(() => { });
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        vi.mocked(loadSettings).mockReturnValue({
            merged: {
                advanced: {},
                security: { auth: {} },
                ui: { theme: 'non-existent-theme' },
            },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadCliConfig).mockResolvedValue({
            isInteractive: () => false,
            getQuestion: () => 'test',
            getSandbox: () => false,
            getDebugMode: () => false,
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({ subscribe: vi.fn() }),
            initialize: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getProjectRoot: () => '/',
            getListExtensions: () => false,
            getListSessions: () => false,
            getDeleteSession: () => undefined,
            getToolRegistry: vi.fn(),
            getExtensions: () => [],
            getModel: () => 'gemini-pro',
            getEmbeddingModel: () => 'embedding-001',
            getApprovalMode: () => 'default',
            getCoreTools: () => [],
            getTelemetryEnabled: () => false,
            getTelemetryLogPromptsEnabled: () => false,
            getFileFilteringRespectGitIgnore: () => true,
            getOutputFormat: () => 'text',
            getUsageStatisticsEnabled: () => false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.spyOn(themeManager, 'setActiveTheme').mockReturnValue(false);
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        expect(debugLoggerWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Warning: Theme "non-existent-theme" not found.'));
        processExitSpy.mockRestore();
    });
    it('should handle session selector error', async () => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        vi.mock('./utils/sessionUtils.js', () => ({
            SessionSelector: class {
                resolveSession = vi
                    .fn()
                    .mockRejectedValue(new Error('Session not found'));
            },
        }));
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        const consoleErrorSpy = vi
            .spyOn(console, 'error')
            .mockImplementation(() => { });
        vi.mocked(loadSettings).mockReturnValue({
            merged: { advanced: {}, security: { auth: {} }, ui: { theme: 'test' } },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: false,
            resume: 'session-id',
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadCliConfig).mockResolvedValue({
            isInteractive: () => true,
            getQuestion: () => '',
            getSandbox: () => false,
            getDebugMode: () => false,
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({ subscribe: vi.fn() }),
            initialize: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getProjectRoot: () => '/',
            getListExtensions: () => false,
            getListSessions: () => false,
            getDeleteSession: () => undefined,
            getToolRegistry: vi.fn(),
            getExtensions: () => [],
            getModel: () => 'gemini-pro',
            getEmbeddingModel: () => 'embedding-001',
            getApprovalMode: () => 'default',
            getCoreTools: () => [],
            getTelemetryEnabled: () => false,
            getTelemetryLogPromptsEnabled: () => false,
            getFileFilteringRespectGitIgnore: () => true,
            getOutputFormat: () => 'text',
            getUsageStatisticsEnabled: () => false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Error resuming session: Session not found'));
        expect(processExitSpy).toHaveBeenCalledWith(1);
        processExitSpy.mockRestore();
        consoleErrorSpy.mockRestore();
    });
    it.skip('should log error when cleanupExpiredSessions fails', async () => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        const { cleanupExpiredSessions } = await import('./utils/sessionCleanup.js');
        vi.mocked(cleanupExpiredSessions).mockRejectedValue(new Error('Cleanup failed'));
        const debugLoggerErrorSpy = vi
            .spyOn(debugLogger, 'error')
            .mockImplementation(() => { });
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        vi.mocked(loadSettings).mockReturnValue({
            merged: { advanced: {}, security: { auth: {} }, ui: {} },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadCliConfig).mockResolvedValue({
            isInteractive: () => false,
            getQuestion: () => 'test',
            getSandbox: () => false,
            getDebugMode: () => false,
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({ subscribe: vi.fn() }),
            initialize: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getProjectRoot: () => '/',
            getListExtensions: () => false,
            getListSessions: () => false,
            getDeleteSession: () => undefined,
            getToolRegistry: vi.fn(),
            getExtensions: () => [],
            getModel: () => 'gemini-pro',
            getEmbeddingModel: () => 'embedding-001',
            getApprovalMode: () => 'default',
            getCoreTools: () => [],
            getTelemetryEnabled: () => false,
            getTelemetryLogPromptsEnabled: () => false,
            getFileFilteringRespectGitIgnore: () => true,
            getOutputFormat: () => 'text',
            getUsageStatisticsEnabled: () => false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        // The mock is already set up at the top of the test
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        expect(debugLoggerErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to cleanup expired sessions: Cleanup failed'));
        expect(processExitSpy).toHaveBeenCalledWith(0); // Should not exit on cleanup failure
        processExitSpy.mockRestore();
    });
    it('should handle refreshAuth failure', async () => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        const debugLoggerErrorSpy = vi
            .spyOn(debugLogger, 'error')
            .mockImplementation(() => { });
        vi.mocked(loadSettings).mockReturnValue({
            merged: {
                advanced: {},
                security: { auth: { selectedType: 'google' } },
                ui: {},
            },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadSandboxConfig).mockResolvedValue({}); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadCliConfig).mockResolvedValue({
            isInteractive: () => true,
            getQuestion: () => '',
            getSandbox: () => false,
            getDebugMode: () => false,
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({ subscribe: vi.fn() }),
            initialize: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getProjectRoot: () => '/',
            getListExtensions: () => false,
            getListSessions: () => false,
            getDeleteSession: () => undefined,
            getToolRegistry: vi.fn(),
            getExtensions: () => [],
            getModel: () => 'gemini-pro',
            getEmbeddingModel: () => 'embedding-001',
            getApprovalMode: () => 'default',
            getCoreTools: () => [],
            getTelemetryEnabled: () => false,
            getTelemetryLogPromptsEnabled: () => false,
            getFileFilteringRespectGitIgnore: () => true,
            getOutputFormat: () => 'text',
            getUsageStatisticsEnabled: () => false,
            refreshAuth: vi.fn().mockRejectedValue(new Error('Auth refresh failed')),
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        expect(debugLoggerErrorSpy).toHaveBeenCalledWith('Error authenticating:', expect.any(Error));
        expect(processExitSpy).toHaveBeenCalledWith(1);
        processExitSpy.mockRestore();
    });
    it('should read from stdin in non-interactive mode', async () => {
        const { loadCliConfig, parseArguments } = await import('./config/config.js');
        const { loadSettings } = await import('./config/settings.js');
        const { readStdin } = await import('./utils/readStdin.js');
        const processExitSpy = vi
            .spyOn(process, 'exit')
            .mockImplementation((code) => {
            throw new MockProcessExitError(code);
        });
        vi.mocked(loadSettings).mockReturnValue({
            merged: { advanced: {}, security: { auth: {} }, ui: {} },
            setValue: vi.fn(),
            forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
            errors: [],
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(parseArguments).mockResolvedValue({
            promptInteractive: false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mocked(loadCliConfig).mockResolvedValue({
            isInteractive: () => false,
            getQuestion: () => 'test-question',
            getSandbox: () => false,
            getDebugMode: () => false,
            getPolicyEngine: vi.fn(),
            getMessageBus: () => ({ subscribe: vi.fn() }),
            initialize: vi.fn(),
            getContentGeneratorConfig: vi.fn(),
            getMcpServers: () => ({}),
            getMcpClientManager: vi.fn(),
            getIdeMode: () => false,
            getExperimentalZedIntegration: () => false,
            getScreenReader: () => false,
            getGeminiMdFileCount: () => 0,
            getProjectRoot: () => '/',
            getListExtensions: () => false,
            getListSessions: () => false,
            getDeleteSession: () => undefined,
            getToolRegistry: vi.fn(),
            getExtensions: () => [],
            getModel: () => 'gemini-pro',
            getEmbeddingModel: () => 'embedding-001',
            getApprovalMode: () => 'default',
            getCoreTools: () => [],
            getTelemetryEnabled: () => false,
            getTelemetryLogPromptsEnabled: () => false,
            getFileFilteringRespectGitIgnore: () => true,
            getOutputFormat: () => 'text',
            getUsageStatisticsEnabled: () => false,
        }); // eslint-disable-line @typescript-eslint/no-explicit-any
        vi.mock('./utils/readStdin.js', () => ({
            readStdin: vi.fn().mockResolvedValue('stdin-data'),
        }));
        const runNonInteractiveSpy = vi.hoisted(() => vi.fn());
        vi.mock('./nonInteractiveCli.js', () => ({
            runNonInteractive: runNonInteractiveSpy,
        }));
        runNonInteractiveSpy.mockClear();
        vi.mock('./validateNonInterActiveAuth.js', () => ({
            validateNonInteractiveAuth: vi.fn().mockResolvedValue({}),
        }));
        // Mock stdin to be non-TTY
        Object.defineProperty(process.stdin, 'isTTY', {
            value: false,
            configurable: true,
        });
        try {
            await main();
        }
        catch (e) {
            if (!(e instanceof MockProcessExitError))
                throw e;
        }
        expect(readStdin).toHaveBeenCalled();
        // In this test setup, runNonInteractive might be called on the mocked module,
        // but we need to ensure we are checking the correct spy instance.
        // Since vi.mock is hoisted, runNonInteractiveSpy is defined early.
        expect(runNonInteractiveSpy).toHaveBeenCalled();
        const callArgs = runNonInteractiveSpy.mock.calls[0][0];
        expect(callArgs.input).toBe('test-question');
        expect(processExitSpy).toHaveBeenCalledWith(0);
        processExitSpy.mockRestore();
        Object.defineProperty(process.stdin, 'isTTY', {
            value: true,
            configurable: true,
        });
    });
});
describe('validateDnsResolutionOrder', () => {
    let debugLoggerWarnSpy;
    beforeEach(() => {
        debugLoggerWarnSpy = vi
            .spyOn(debugLogger, 'warn')
            .mockImplementation(() => { });
    });
    afterEach(() => {
        vi.restoreAllMocks();
    });
    it('should return "ipv4first" when the input is "ipv4first"', () => {
        expect(validateDnsResolutionOrder('ipv4first')).toBe('ipv4first');
        expect(debugLoggerWarnSpy).not.toHaveBeenCalled();
    });
    it('should return "verbatim" when the input is "verbatim"', () => {
        expect(validateDnsResolutionOrder('verbatim')).toBe('verbatim');
        expect(debugLoggerWarnSpy).not.toHaveBeenCalled();
    });
    it('should return the default "ipv4first" when the input is undefined', () => {
        expect(validateDnsResolutionOrder(undefined)).toBe('ipv4first');
        expect(debugLoggerWarnSpy).not.toHaveBeenCalled();
    });
    it('should return the default "ipv4first" and log a warning for an invalid string', () => {
        expect(validateDnsResolutionOrder('invalid-value')).toBe('ipv4first');
        expect(debugLoggerWarnSpy).toHaveBeenCalledExactlyOnceWith('Invalid value for dnsResolutionOrder in settings: "invalid-value". Using default "ipv4first".');
    });
});
describe('startInteractiveUI', () => {
    // Mock dependencies
    const mockConfig = {
        getProjectRoot: () => '/root',
        getScreenReader: () => false,
        getDebugMode: () => false,
    };
    const mockSettings = {
        merged: {
            ui: {
                hideWindowTitle: false,
                useAlternateBuffer: true,
            },
        },
    };
    const mockStartupWarnings = ['warning1'];
    const mockWorkspaceRoot = '/root';
    const mockInitializationResult = {
        authError: null,
        themeError: null,
        shouldOpenAuthDialog: false,
        geminiMdFileCount: 0,
    };
    vi.mock('./utils/version.js', () => ({
        getCliVersion: vi.fn(() => Promise.resolve('1.0.0')),
    }));
    vi.mock('./ui/utils/kittyProtocolDetector.js', () => ({
        detectAndEnableKittyProtocol: vi.fn(() => Promise.resolve(true)),
        isKittyProtocolSupported: vi.fn(() => true),
        isKittyProtocolEnabled: vi.fn(() => true),
    }));
    vi.mock('./ui/utils/updateCheck.js', () => ({
        checkForUpdates: vi.fn(() => Promise.resolve(null)),
    }));
    vi.mock('./utils/cleanup.js', () => ({
        cleanupCheckpoints: vi.fn(() => Promise.resolve()),
        registerCleanup: vi.fn(),
        runExitCleanup: vi.fn(),
        registerSyncCleanup: vi.fn(),
    }));
    afterEach(() => {
        vi.restoreAllMocks();
    });
    async function startTestInteractiveUI(config, settings, startupWarnings, workspaceRoot, resumedSessionData, initializationResult) {
        await act(async () => {
            await startInteractiveUI(config, settings, startupWarnings, workspaceRoot, resumedSessionData, initializationResult);
        });
    }
    it('should render the UI with proper React context and exitOnCtrlC disabled', async () => {
        const { render } = await import('ink');
        const renderSpy = vi.mocked(render);
        await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
        // Verify render was called with correct options
        expect(renderSpy).toHaveBeenCalledTimes(1);
        const [reactElement, options] = renderSpy.mock.calls[0];
        // Verify render options
        expect(options).toEqual(expect.objectContaining({
            alternateBuffer: true,
            exitOnCtrlC: false,
            incrementalRendering: true,
            isScreenReaderEnabled: false,
            onRender: expect.any(Function),
            patchConsole: false,
        }));
        // Verify React element structure is valid (but don't deep dive into JSX internals)
        expect(reactElement).toBeDefined();
    });
    it('should enable mouse events when alternate buffer is enabled', async () => {
        const { enableMouseEvents } = await import('@google/gemini-cli-core');
        await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
        expect(enableMouseEvents).toHaveBeenCalled();
    });
    it('should patch console', async () => {
        const { ConsolePatcher } = await import('./ui/utils/ConsolePatcher.js');
        const patchSpy = vi.spyOn(ConsolePatcher.prototype, 'patch');
        await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
        expect(patchSpy).toHaveBeenCalled();
    });
    it('should perform all startup tasks in correct order', async () => {
        const { getCliVersion } = await import('./utils/version.js');
        const { checkForUpdates } = await import('./ui/utils/updateCheck.js');
        const { registerCleanup } = await import('./utils/cleanup.js');
        await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
        // Verify all startup tasks were called
        expect(getCliVersion).toHaveBeenCalledTimes(1);
        expect(registerCleanup).toHaveBeenCalledTimes(3);
        // Verify cleanup handler is registered with unmount function
        const cleanupFn = vi.mocked(registerCleanup).mock.calls[0][0];
        expect(typeof cleanupFn).toBe('function');
        // checkForUpdates should be called asynchronously (not waited for)
        // We need a small delay to let it execute
        await new Promise((resolve) => setTimeout(resolve, 0));
        expect(checkForUpdates).toHaveBeenCalledTimes(1);
    });
    it('should not recordSlowRender when less than threshold', async () => {
        const { recordSlowRender } = await import('@google/gemini-cli-core');
        performance.now.mockReturnValueOnce(0);
        await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
        expect(recordSlowRender).not.toHaveBeenCalled();
    });
    it('should call recordSlowRender when more than threshold', async () => {
        const { recordSlowRender } = await import('@google/gemini-cli-core');
        performance.now.mockReturnValueOnce(0);
        performance.now.mockReturnValueOnce(300);
        await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
        expect(recordSlowRender).toHaveBeenCalledWith(mockConfig, 300);
    });
    it.each([
        {
            screenReader: true,
            expectedCalls: [],
            name: 'should not disable line wrapping in screen reader mode',
        },
        {
            screenReader: false,
            expectedCalls: [['\x1b[?7l']],
            name: 'should disable line wrapping when not in screen reader mode',
        },
    ])('$name', async ({ screenReader, expectedCalls }) => {
        const writeSpy = vi
            .spyOn(process.stdout, 'write')
            .mockImplementation(() => true);
        const mockConfigWithScreenReader = {
            ...mockConfig,
            getScreenReader: () => screenReader,
        };
        await startTestInteractiveUI(mockConfigWithScreenReader, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
        if (expectedCalls.length > 0) {
            expect(writeSpy).toHaveBeenCalledWith(expectedCalls[0][0]);
        }
        else {
            expect(writeSpy).not.toHaveBeenCalledWith('\x1b[?7l');
        }
        writeSpy.mockRestore();
    });
});
//# sourceMappingURL=gemini.test.js.map