CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes45downloads
gemini.test.tsx2046 linesDownload Raw Back to src
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import {8  describe,9  it,10  expect,11  vi,12  beforeEach,13  afterEach,14  type MockInstance,15} from 'vitest';16import { readFileSync } from 'node:fs';17import {18  createNonInteractivePromptId,19  main,20  registerLspHotReload,21  setupUnhandledRejectionHandler,22  validateDnsResolutionOrder,23} from './gemini.js';24import { startInteractiveUI } from './ui/startInteractiveUI.js';25import type { CliArgs } from './config/config.js';26import { type LoadedSettings } from './config/settings.js';27import { appEvents, AppEvent } from './utils/events.js';28import type { Config } from '@qwen-code/qwen-code-core';29import { ApprovalMode, OutputFormat } from '@qwen-code/qwen-code-core';30 31const mockWriteStderrLine = vi.hoisted(() => vi.fn());32const mockHandleListExtensions = vi.hoisted(() => vi.fn());33const mockStartEarlyStartupPrefetches = vi.hoisted(() => vi.fn());34const mockStartPostRenderPrefetches = vi.hoisted(() => vi.fn());35const mockRunAcpAgent = vi.hoisted(() => vi.fn());36const lspConfigWatcherMock = vi.hoisted(() => ({37  instances: [] as Array<{38    listener?: (event: unknown) => void | Promise<void>;39    startWatching: ReturnType<typeof vi.fn>;40    stopWatching: ReturnType<typeof vi.fn>;41  }>,42}));43 44describe('gemini import boundary', () => {45  it('does not statically import ACP or noninteractive auth branches', () => {46    const source = readFileSync('src/gemini.tsx', 'utf8');47 48    expect(source).not.toContain(49      "import { runAcpAgent } from './acp-integration/acpAgent.js'",50    );51    expect(source).not.toContain(52      "import { validateNonInteractiveAuth } from './validateNonInterActiveAuth.js'",53    );54    expect(source).not.toContain(55      "import { initializeApp } from './core/initializer.js'",56    );57    expect(source).toMatch(58      /await import\(\s*['"]\.\/acp-integration\/acpAgent\.js['"]\s*\)/,59    );60    expect(source).toMatch(61      /await import\(\s*['"]\.\/validateNonInterActiveAuth\.js['"]\s*\)/,62    );63    expect(source).toMatch(64      /await import\(\s*['"]\.\/core\/initializer\.js['"]\s*\)/,65    );66  });67});68 69// Custom error to identify mock process.exit calls70class MockProcessExitError extends Error {71  constructor(readonly code?: string | number | null | undefined) {72    super('PROCESS_EXIT_MOCKED');73    this.name = 'MockProcessExitError';74  }75}76 77// Mock dependencies78vi.mock('./config/settings.js', async (importOriginal) => {79  const actual = await importOriginal<typeof import('./config/settings.js')>();80  return {81    ...actual,82    loadSettings: vi.fn(),83    createMinimalSettings: vi.fn(),84  };85});86 87vi.mock('./config/config.js', () => ({88  loadCliConfig: vi.fn().mockResolvedValue({89    getSandbox: vi.fn(() => false),90    getQuestion: vi.fn(() => ''),91    isInteractive: () => false,92    isLspEnabled: () => false,93    getLspClient: () => undefined,94    getWarnings: vi.fn(() => []),95    isSafeMode: vi.fn(() => false),96    getModelsConfig: vi.fn(() => ({ getCurrentAuthType: () => null })),97  } as unknown as Config),98  parseArguments: vi.fn().mockResolvedValue({}),99  isDebugMode: vi.fn(() => false),100  buildDisabledSkillNamesProvider: vi.fn(() => () => new Set<string>()),101}));102 103vi.mock('read-package-up', () => ({104  readPackageUp: vi.fn().mockResolvedValue({105    packageJson: { name: 'test-pkg', version: 'test-version' },106    path: '/fake/path/package.json',107  }),108}));109 110vi.mock('update-notifier', () => ({111  default: vi.fn(() => ({112    notify: vi.fn(),113  })),114}));115 116vi.mock('./utils/events.js', async (importOriginal) => {117  const actual = await importOriginal<typeof import('./utils/events.js')>();118  return {119    ...actual,120    appEvents: {121      emit: vi.fn(),122    },123  };124});125 126vi.mock('./utils/sandbox.js', () => ({127  sandbox_command: vi.fn(() => ''), // Default to no sandbox command128  start_sandbox: vi.fn(() => Promise.resolve()), // Mock as an async function that resolves129}));130 131vi.mock('./utils/stdioHelpers.js', () => ({132  writeStderrLine: mockWriteStderrLine,133  writeStdoutLine: vi.fn(),134  clearScreen: vi.fn(),135}));136 137vi.mock('./utils/relaunch.js', () => ({138  relaunchAppInChildProcess: vi.fn(),139  relaunchOnExitCode: vi.fn((fn: () => Promise<number>) => fn()),140}));141 142vi.mock('./config/sandboxConfig.js', () => ({143  loadSandboxConfig: vi.fn(),144}));145 146vi.mock('./core/initializer.js', () => ({147  initializeApp: vi.fn().mockResolvedValue({148    authError: null,149    themeError: null,150    shouldOpenAuthDialog: false,151    geminiMdFileCount: 0,152  }),153}));154 155vi.mock('./startup/startup-prefetch.js', () => ({156  startEarlyStartupPrefetches: (...args: unknown[]) =>157    mockStartEarlyStartupPrefetches(...args),158  startPostRenderPrefetches: (...args: unknown[]) =>159    mockStartPostRenderPrefetches(...args),160}));161 162vi.mock('./acp-integration/acpAgent.js', () => ({163  runAcpAgent: (...args: unknown[]) => mockRunAcpAgent(...args),164}));165 166vi.mock('./commands/extensions/list.js', () => ({167  handleList: mockHandleListExtensions,168}));169 170vi.mock('./ui/AppContainer.js', () => ({171  AppContainer: () => null,172}));173 174// Stub the settings watcher: main() constructs one and calls startWatching()175// in non-bare mode. The real implementation reads settings.user/.workspace176// paths and arms chokidar file watchers, neither of which these main()-flow177// tests supply or want as a side effect.178vi.mock('./config/settingsWatcher.js', () => ({179  SettingsWatcher: class {180    startWatching() {}181    stopWatching() {}182    addChangeListener() {183      return () => {};184    }185  },186}));187 188vi.mock('./config/lsp-config-watcher.js', () => ({189  LspConfigWatcher: class {190    listener?: (event: unknown) => void | Promise<void>;191    startWatching = vi.fn(192      (listener: (event: unknown) => void | Promise<void>) => {193        this.listener = listener;194      },195    );196    stopWatching = vi.fn();197 198    constructor() {199      lspConfigWatcherMock.instances.push(this);200    }201  },202}));203 204function withLspDisabledConfig<T extends object>(205  config: T,206): T & {207  isLspEnabled: () => boolean;208  getLspClient: () => undefined;209} {210  return {211    isLspEnabled: () => false,212    getLspClient: () => undefined,213    ...config,214  };215}216 217describe('gemini.tsx main function', () => {218  let originalEnvGeminiSandbox: string | undefined;219  let originalEnvSandbox: string | undefined;220  let originalEnvQwenCodeSimple: string | undefined;221  let initialUnhandledRejectionListeners: NodeJS.UnhandledRejectionListener[] =222    [];223 224  beforeEach(() => {225    lspConfigWatcherMock.instances.length = 0;226    // Store and clear sandbox-related env variables to ensure a consistent test environment227    originalEnvGeminiSandbox = process.env['QWEN_SANDBOX'];228    originalEnvSandbox = process.env['SANDBOX'];229    originalEnvQwenCodeSimple = process.env['QWEN_CODE_SIMPLE'];230    delete process.env['QWEN_SANDBOX'];231    delete process.env['SANDBOX'];232    delete process.env['QWEN_CODE_SIMPLE'];233 234    initialUnhandledRejectionListeners =235      process.listeners('unhandledRejection');236  });237 238  afterEach(() => {239    // Restore original env variables240    if (originalEnvGeminiSandbox !== undefined) {241      process.env['QWEN_SANDBOX'] = originalEnvGeminiSandbox;242    } else {243      delete process.env['QWEN_SANDBOX'];244    }245    if (originalEnvSandbox !== undefined) {246      process.env['SANDBOX'] = originalEnvSandbox;247    } else {248      delete process.env['SANDBOX'];249    }250    if (originalEnvQwenCodeSimple !== undefined) {251      process.env['QWEN_CODE_SIMPLE'] = originalEnvQwenCodeSimple;252    } else {253      delete process.env['QWEN_CODE_SIMPLE'];254    }255 256    const currentListeners = process.listeners('unhandledRejection');257    const addedListener = currentListeners.find(258      (listener) => !initialUnhandledRejectionListeners.includes(listener),259    );260 261    if (addedListener) {262      process.removeListener('unhandledRejection', addedListener);263    }264    vi.restoreAllMocks();265  });266 267  it('verifies that we dont load the config before relaunchAppInChildProcess', async () => {268    const processExitSpy = vi269      .spyOn(process, 'exit')270      .mockImplementation((code) => {271        throw new MockProcessExitError(code);272      });273    const { relaunchAppInChildProcess } = await import('./utils/relaunch.js');274    const { loadCliConfig } = await import('./config/config.js');275    const { loadSettings } = await import('./config/settings.js');276    const { loadSandboxConfig } = await import('./config/sandboxConfig.js');277    vi.mocked(loadSandboxConfig).mockResolvedValue(undefined);278 279    const callOrder: string[] = [];280    vi.mocked(relaunchAppInChildProcess).mockImplementation(async () => {281      callOrder.push('relaunch');282    });283    vi.mocked(loadCliConfig).mockImplementation(async () => {284      callOrder.push('loadCliConfig');285      return {286        isInteractive: () => false,287        getQuestion: () => '',288        getSandbox: () => false,289        getApprovalMode: () => ApprovalMode.DEFAULT,290        getDebugMode: () => false,291        getListExtensions: () => false,292        getMcpServers: () => ({}),293        getTopTierMcpServers: () => undefined,294        initialize: vi.fn(),295        waitForMcpReady: vi.fn().mockResolvedValue(undefined),296        getIdeMode: () => false,297        getExperimentalZedIntegration: () => false,298        getScreenReader: () => false,299        getGeminiMdFileCount: () => 0,300        getProjectRoot: () => '/',301        getOutputFormat: () => OutputFormat.TEXT,302        getWarnings: () => [],303        isSafeMode: () => false,304        getModelsConfig: () => ({ getCurrentAuthType: () => null }),305        getSessionId: () => 'test-session-id',306      } as unknown as Config;307    });308    vi.mocked(loadSettings).mockReturnValue({309      errors: [],310      merged: {311        advanced: { autoConfigureMemory: true },312        security: { auth: {} },313        ui: {},314      },315      setValue: vi.fn(),316      forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),317      migrationWarnings: [],318      getUserHooks: () => undefined,319      getProjectHooks: () => undefined,320    } as never);321    try {322      await main();323    } catch (e) {324      // Mocked process exit throws an error.325      if (!(e instanceof MockProcessExitError)) throw e;326    }327 328    // It is critical that we call relaunch before loadCliConfig to avoid329    // loading config in the outer process when we are going to relaunch.330    // By ensuring we don't load the config we also ensure we don't trigger any331    // operations that might require loading the config such as such as332    // initializing mcp servers.333    // For the sandbox case we still have to load a partial cli config.334    // we can authorize outside the sandbox.335    expect(callOrder).toEqual(['relaunch', 'loadCliConfig']);336    processExitSpy.mockRestore();337  });338 339  it('handles --list-extensions before sandbox and app config startup', async () => {340    vi.clearAllMocks();341    const processExitSpy = vi342      .spyOn(process, 'exit')343      .mockImplementation((code) => {344        throw new MockProcessExitError(code);345      });346 347    const { loadCliConfig, parseArguments } = await import(348      './config/config.js'349    );350    const { loadSettings } = await import('./config/settings.js');351    const { loadSandboxConfig } = await import('./config/sandboxConfig.js');352 353    vi.mocked(parseArguments).mockResolvedValue({354      listExtensions: true,355    } as unknown as CliArgs);356    vi.mocked(loadSettings).mockReturnValue({357      errors: [],358      merged: {359        advanced: {},360        security: { auth: {} },361        ui: {},362      },363      setValue: vi.fn(),364      forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),365      migrationWarnings: [],366      getUserHooks: () => undefined,367      getProjectHooks: () => undefined,368    } as never);369    mockHandleListExtensions.mockResolvedValue(undefined);370 371    try {372      await main();373    } catch (e) {374      if (!(e instanceof MockProcessExitError)) throw e;375    }376 377    expect(mockHandleListExtensions).toHaveBeenCalledOnce();378    expect(processExitSpy).toHaveBeenCalledWith(0);379    expect(loadSandboxConfig).not.toHaveBeenCalled();380    expect(loadCliConfig).not.toHaveBeenCalled();381 382    processExitSpy.mockRestore();383  });384 385  it('should skip full settings discovery in bare mode', async () => {386    const originalArgv = process.argv;387    process.argv = ['node', 'script.js', '--bare'];388 389    const { loadCliConfig, parseArguments } = await import(390      './config/config.js'391    );392    const { loadSettings, createMinimalSettings } = await import(393      './config/settings.js'394    );395    const { loadSandboxConfig } = await import('./config/sandboxConfig.js');396    const { relaunchAppInChildProcess } = await import('./utils/relaunch.js');397    const nonInteractiveModule = await import('./nonInteractiveCli.js');398    const processExitSpy = vi399      .spyOn(process, 'exit')400      .mockImplementation((code) => {401        throw new MockProcessExitError(code);402      });403 404    const minimalSettings = {405      errors: [],406      merged: {},407      setValue: vi.fn(),408      forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),409      migrationWarnings: [],410      getUserHooks: () => undefined,411      getProjectHooks: () => undefined,412    };413    const configStub = {414      isInteractive: () => false,415      getQuestion: () => 'bare prompt',416      getSandbox: () => false,417      getApprovalMode: () => ApprovalMode.DEFAULT,418      getDebugMode: () => false,419      getListExtensions: () => false,420      getMcpServers: () => ({}),421      getTopTierMcpServers: () => undefined,422      initialize: vi.fn().mockResolvedValue(undefined),423      waitForMcpReady: vi.fn().mockResolvedValue(undefined),424      getIdeMode: () => false,425      getExperimentalZedIntegration: () => false,426      getScreenReader: () => false,427      getGeminiMdFileCount: () => 0,428      getProjectRoot: () => '/',429      getOutputFormat: () => OutputFormat.TEXT,430      getWarnings: () => [],431      isSafeMode: () => false,432      getModelsConfig: () => ({ getCurrentAuthType: () => null }),433      getSessionId: () => 'test-session-id',434    } as unknown as Config;435 436    vi.mocked(parseArguments).mockResolvedValue({437      bare: true,438    } as unknown as CliArgs);439    vi.mocked(createMinimalSettings).mockReturnValue(minimalSettings as never);440    vi.mocked(loadSandboxConfig).mockResolvedValue(undefined);441    vi.mocked(relaunchAppInChildProcess).mockResolvedValue(undefined);442    vi.mocked(loadCliConfig).mockResolvedValue(configStub);443    vi.spyOn(nonInteractiveModule, 'runNonInteractive').mockResolvedValue(0);444 445    try {446      await main();447    } catch (error) {448      if (!(error instanceof MockProcessExitError)) {449        throw error;450      }451    } finally {452      process.argv = originalArgv;453      processExitSpy.mockRestore();454    }455 456    expect(createMinimalSettings).toHaveBeenCalledOnce();457    expect(loadSettings).not.toHaveBeenCalled();458    expect(loadCliConfig).toHaveBeenCalledWith(459      {},460      expect.objectContaining({ bare: true }),461      process.cwd(),462      undefined,463      {464        userHooks: undefined,465        projectHooks: undefined,466      },467      expect.any(Function),468      undefined,469      // settingsWatcher: not started in bare mode470      undefined,471    );472  });473 474  describe('registerLspHotReload', () => {475    it('does not register a watcher when LSP is disabled', () => {476      const registerCleanup = vi.fn();477 478      registerLspHotReload(479        withLspDisabledConfig({480          getProjectRoot: () => '/workspace',481        }) as unknown as Config,482        registerCleanup,483      );484 485      expect(lspConfigWatcherMock.instances).toHaveLength(0);486      expect(registerCleanup).not.toHaveBeenCalled();487    });488 489    it('does not register a watcher when the client cannot reinitialize', () => {490      const registerCleanup = vi.fn();491 492      registerLspHotReload(493        {494          isLspEnabled: () => true,495          getLspClient: () => ({}),496          getProjectRoot: () => '/workspace',497        } as unknown as Config,498        registerCleanup,499      );500 501      expect(lspConfigWatcherMock.instances).toHaveLength(0);502      expect(registerCleanup).not.toHaveBeenCalled();503    });504 505    it('emits an LSP status update after successful reload', async () => {506      const registerCleanup = vi.fn();507      const reinitializeLsp = vi.fn(async () => ({508        reconcile: {509          added: ['clangd'],510          removed: [],511          restarted: [],512          unchanged: [],513          failed: [],514        },515        skipped: [],516      }));517 518      registerLspHotReload(519        {520          isLspEnabled: () => true,521          getLspClient: () => ({ reinitialize: vi.fn() }),522          getProjectRoot: () => '/workspace',523          reinitializeLsp,524        } as unknown as Config,525        registerCleanup,526      );527 528      await lspConfigWatcherMock.instances[0]?.listener?.({529        path: '/workspace/.lsp.json',530        changeType: 'modified',531      });532 533      expect(reinitializeLsp).toHaveBeenCalledOnce();534      expect(appEvents.emit).toHaveBeenCalledWith(AppEvent.LspStatusChanged);535    });536 537    it('emits an LSP status update when reload is skipped by the config', async () => {538      const reinitializeLsp = vi.fn(async () => undefined);539 540      registerLspHotReload(541        {542          isLspEnabled: () => true,543          getLspClient: () => ({ reinitialize: vi.fn() }),544          getProjectRoot: () => '/workspace',545          reinitializeLsp,546        } as unknown as Config,547        vi.fn(),548      );549 550      await lspConfigWatcherMock.instances[0]?.listener?.({551        path: '/workspace/.lsp.json',552        changeType: 'modified',553      });554 555      expect(reinitializeLsp).toHaveBeenCalledOnce();556      expect(appEvents.emit).not.toHaveBeenCalledWith(557        AppEvent.LogError,558        expect.any(String),559      );560      expect(appEvents.emit).toHaveBeenCalledWith(AppEvent.LspStatusChanged);561    });562 563    it('emits a user-visible error and rejects when reload fails', async () => {564      const reinitializeLsp = vi.fn(async () => {565        throw new Error('invalid lsp json');566      });567 568      registerLspHotReload(569        {570          isLspEnabled: () => true,571          getLspClient: () => ({ reinitialize: vi.fn() }),572          getProjectRoot: () => '/workspace',573          reinitializeLsp,574        } as unknown as Config,575        vi.fn(),576      );577 578      await expect(579        lspConfigWatcherMock.instances[0]?.listener?.({580          path: '/workspace/.lsp.json',581          changeType: 'modified',582        }),583      ).rejects.toThrow('invalid lsp json');584 585      expect(appEvents.emit).toHaveBeenCalledWith(586        AppEvent.LogError,587        'Failed to reload LSP server settings: invalid lsp json. Some LSP servers may have been partially updated. Run with --debug for details.',588      );589    });590 591    it('emits a user-visible error and rejects when reload has failed servers', async () => {592      const reinitializeLsp = vi.fn(async () => ({593        reconcile: {594          added: [],595          removed: [],596          restarted: [],597          unchanged: [],598          failed: ['clangd'],599        },600        skipped: [],601      }));602 603      registerLspHotReload(604        {605          isLspEnabled: () => true,606          getLspClient: () => ({ reinitialize: vi.fn() }),607          getProjectRoot: () => '/workspace',608          reinitializeLsp,609        } as unknown as Config,610        vi.fn(),611      );612 613      await expect(614        lspConfigWatcherMock.instances[0]?.listener?.({615          path: '/workspace/.lsp.json',616          changeType: 'modified',617        }),618      ).rejects.toThrow('LSP reload partially completed');619 620      expect(appEvents.emit).toHaveBeenCalledWith(621        AppEvent.LogError,622        'LSP reload partially completed: changed=<none>, failed=clangd. Run with --debug for details.',623      );624      expect(appEvents.emit).toHaveBeenCalledWith(AppEvent.LspStatusChanged);625    });626 627    it('surfaces invalid config without reinitializing LSP', async () => {628      const reinitializeLsp = vi.fn();629 630      registerLspHotReload(631        {632          isLspEnabled: () => true,633          getLspClient: () => ({ reinitialize: vi.fn() }),634          getProjectRoot: () => '/workspace',635          reinitializeLsp,636        } as unknown as Config,637        vi.fn(),638      );639 640      await lspConfigWatcherMock.instances[0]?.listener?.({641        path: '/workspace/.lsp.json',642        changeType: 'invalid',643        error:644          'Invalid JSON in .lsp.json; existing LSP runtime state is unchanged.',645      });646 647      expect(reinitializeLsp).not.toHaveBeenCalled();648      expect(appEvents.emit).toHaveBeenCalledWith(649        AppEvent.LogError,650        'Invalid JSON in .lsp.json; existing LSP runtime state is unchanged.',651      );652    });653  });654 655  it('writes non-interactive warnings discovered during config initialization', async () => {656    const originalNoRelaunch = process.env['QWEN_CODE_NO_RELAUNCH'];657    const originalIsTTY = Object.getOwnPropertyDescriptor(658      process.stdin,659      'isTTY',660    );661    process.env['QWEN_CODE_NO_RELAUNCH'] = 'true';662    Object.defineProperty(process.stdin, 'isTTY', {663      value: true,664      configurable: true,665    });666 667    const processExitSpy = vi668      .spyOn(process, 'exit')669      .mockImplementation((code) => {670        throw new MockProcessExitError(code);671      });672    const { loadCliConfig, parseArguments } = await import(673      './config/config.js'674    );675    const { loadSettings } = await import('./config/settings.js');676    const cleanupModule = await import('./utils/cleanup.js');677    const validatorModule = await import('./validateNonInterActiveAuth.js');678    const nonInteractiveModule = await import('./nonInteractiveCli.js');679    const initializerModule = await import('./core/initializer.js');680    const startupWarningsModule = await import('./utils/startupWarnings.js');681    const userStartupWarningsModule = await import(682      './utils/userStartupWarnings.js'683    );684 685    mockWriteStderrLine.mockClear();686    vi.mocked(cleanupModule.runExitCleanup).mockResolvedValue(undefined);687    vi.spyOn(initializerModule, 'initializeApp').mockResolvedValue({688      authError: null,689      themeError: null,690      shouldOpenAuthDialog: false,691      geminiMdFileCount: 0,692    });693    vi.spyOn(startupWarningsModule, 'getStartupWarnings').mockResolvedValue([]);694    vi.spyOn(695      userStartupWarningsModule,696      'getUserStartupWarnings',697    ).mockResolvedValue([]);698    vi.spyOn(nonInteractiveModule, 'runNonInteractive').mockResolvedValue(0);699 700    let initialized = false;701    const configStub = {702      isInteractive: () => false,703      getQuestion: () => 'hello',704      getSandbox: () => false,705      getApprovalMode: () => ApprovalMode.DEFAULT,706      getDebugMode: () => false,707      getListExtensions: () => false,708      getMcpServers: () => ({}),709      getTopTierMcpServers: () => undefined,710      initialize: vi.fn().mockImplementation(async () => {711        initialized = true;712      }),713      waitForMcpReady: vi.fn().mockResolvedValue(undefined),714      getFailedMcpServerNames: () => [],715      getIdeMode: () => false,716      getExperimentalZedIntegration: () => false,717      getScreenReader: () => false,718      getGeminiMdFileCount: () => 0,719      getProjectRoot: () => '/',720      getOutputFormat: () => OutputFormat.TEXT,721      getWarnings: () => (initialized ? ['late memory warning'] : []),722      isSafeMode: () => false,723      getModelsConfig: () => ({ getCurrentAuthType: () => null }),724      getContentGeneratorConfig: () => undefined,725      getUsageStatisticsEnabled: () => true,726      getSessionId: () => 'test-session-id',727      getProxy: () => undefined,728    } as unknown as Config;729 730    vi.mocked(parseArguments).mockResolvedValue({731      extensions: [],732    } as unknown as CliArgs);733    vi.mocked(loadSettings).mockReturnValue({734      errors: [],735      merged: {736        advanced: {},737        security: { auth: {} },738        ui: {},739      },740      setValue: vi.fn(),741      forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),742      migrationWarnings: [],743      getUserHooks: () => undefined,744      getProjectHooks: () => undefined,745    } as never);746    vi.mocked(loadCliConfig).mockResolvedValue(configStub);747    vi.spyOn(validatorModule, 'validateNonInteractiveAuth').mockResolvedValue(748      configStub,749    );750 751    try {752      await main();753    } catch (error) {754      if (!(error instanceof MockProcessExitError)) {755        throw error;756      }757    } finally {758      processExitSpy.mockRestore();759      if (originalIsTTY) {760        Object.defineProperty(process.stdin, 'isTTY', originalIsTTY);761      } else {762        delete (process.stdin as { isTTY?: unknown }).isTTY;763      }764      if (originalNoRelaunch !== undefined) {765        process.env['QWEN_CODE_NO_RELAUNCH'] = originalNoRelaunch;766      } else {767        delete process.env['QWEN_CODE_NO_RELAUNCH'];768      }769    }770 771    expect(mockWriteStderrLine).toHaveBeenCalledWith('late memory warning');772    expect(initializerModule.initializeApp).toHaveBeenCalledWith(773      configStub,774      expect.any(Object),775      { deferIdeConnection: false },776    );777  });778 779  it('creates non-interactive prompt ids that preserve session correlation', () => {780    expect(createNonInteractivePromptId('test-session-id')).toBe(781      'test-session-id########0',782    );783  });784 785  const runSandboxRelaunch = async (786    argv: string[],787    sessionId = '123e4567-e89b-12d3-a456-426614174000',788  ): Promise<string[]> => {789    const originalArgv = process.argv;790    process.argv = argv;791    const processExitSpy = vi792      .spyOn(process, 'exit')793      .mockImplementation((code) => {794        throw new MockProcessExitError(code);795      });796 797    const { loadCliConfig, parseArguments } = await import(798      './config/config.js'799    );800    const { loadSettings } = await import('./config/settings.js');801    const { loadSandboxConfig } = await import('./config/sandboxConfig.js');802    const { start_sandbox } = await import('./utils/sandbox.js');803 804    vi.mocked(start_sandbox).mockClear();805    vi.mocked(parseArguments).mockResolvedValue({806      debug: true,807      prompt: 'hello',808      extensions: [],809    } as unknown as CliArgs);810    vi.mocked(loadSettings).mockReturnValue({811      errors: [],812      merged: {813        advanced: {},814        security: { auth: {} },815        ui: {},816      },817      setValue: vi.fn(),818      forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),819      migrationWarnings: [],820      getUserHooks: () => undefined,821      getProjectHooks: () => undefined,822    } as never);823    vi.mocked(loadSandboxConfig).mockResolvedValue({824      command: 'sandbox-exec',825      image: '',826    });827    vi.mocked(loadCliConfig).mockResolvedValue({828      getModelsConfig: () => ({ getCurrentAuthType: () => null }),829      getSessionId: () => sessionId,830    } as unknown as Config);831 832    try {833      await main();834    } catch (error) {835      if (!(error instanceof MockProcessExitError)) {836        throw error;837      }838    } finally {839      process.argv = originalArgv;840      processExitSpy.mockRestore();841    }842 843    expect(start_sandbox).toHaveBeenCalledOnce();844    return vi.mocked(start_sandbox).mock.calls[0]![3]!;845  };846 847  it('passes the outer session ID into the sandbox child process', async () => {848    const sessionId = '123e4567-e89b-12d3-a456-426614174000';849    const sandboxArgs = await runSandboxRelaunch(850      ['node', 'script.js', '--debug', '-p', 'hello'],851      sessionId,852    );853 854    const idx = sandboxArgs.indexOf('--sandbox-session-id');855    expect(idx).not.toBe(-1);856    expect(sandboxArgs[idx + 1]).toBe(sessionId);857    expect(sandboxArgs).not.toContain('--session-id');858  });859 860  it('does not pass an empty session ID into the sandbox child process', async () => {861    const sandboxArgs = await runSandboxRelaunch(862      ['node', 'script.js', '--debug', '-p', 'hello'],863      '',864    );865 866    expect(sandboxArgs).not.toContain('--sandbox-session-id');867    expect(sandboxArgs).not.toContain('--session-id');868  });869 870  it.each([871    ['--continue', ['node', 'script.js', '--debug', '--continue']],872    ['-c', ['node', 'script.js', '--debug', '-c']],873    ['--resume', ['node', 'script.js', '--debug', '--resume', 'session-id']],874    ['-r', ['node', 'script.js', '--debug', '-r', 'session-id']],875    [876      '--session-id',877      [878        'node',879        'script.js',880        '--debug',881        '--session-id',882        '123e4567-e89b-12d3-a456-426614174999',883      ],884    ],885  ])(886    'does not inject sandbox session ID when argv contains %s',887    async (_flag, argv) => {888      const sandboxArgs = await runSandboxRelaunch(argv);889 890      expect(sandboxArgs).not.toContain('--sandbox-session-id');891    },892  );893 894  it('inserts the sandbox session ID before the argument separator', async () => {895    const sessionId = '123e4567-e89b-12d3-a456-426614174000';896    const sandboxArgs = await runSandboxRelaunch(897      ['node', 'script.js', '--debug', '--', '--not-a-cli-flag'],898      sessionId,899    );900 901    expect(sandboxArgs).toEqual([902      'node',903      'script.js',904      '--debug',905      '--sandbox-session-id',906      sessionId,907      '--',908      '--not-a-cli-flag',909    ]);910  });911 912  it('should log unhandled promise rejections and open debug console on first error', async () => {913    const processExitSpy = vi914      .spyOn(process, 'exit')915      .mockImplementation((code) => {916        throw new MockProcessExitError(code);917      });918    const appEventsMock = vi.mocked(appEvents);919    const rejectionError = new Error('Test unhandled rejection');920 921    setupUnhandledRejectionHandler();922    // Simulate an unhandled rejection.923    // We are not using Promise.reject here as vitest will catch it.924    // Instead we will dispatch the event manually.925    process.emit('unhandledRejection', rejectionError, Promise.resolve());926 927    // We need to wait for the rejection handler to be called.928    await new Promise(process.nextTick);929 930    expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvent.OpenDebugConsole);931    expect(appEventsMock.emit).toHaveBeenCalledWith(932      AppEvent.LogError,933      expect.stringContaining('Unhandled Promise Rejection'),934    );935    expect(appEventsMock.emit).toHaveBeenCalledWith(936      AppEvent.LogError,937      expect.stringContaining('Please file a bug report using the /bug tool.'),938    );939 940    // Simulate a second rejection941    const secondRejectionError = new Error('Second test unhandled rejection');942    process.emit('unhandledRejection', secondRejectionError, Promise.resolve());943    await new Promise(process.nextTick);944 945    // Ensure emit was only called once for OpenDebugConsole946    const openDebugConsoleCalls = appEventsMock.emit.mock.calls.filter(947      (call) => call[0] === AppEvent.OpenDebugConsole,948    );949    expect(openDebugConsoleCalls.length).toBe(1);950 951    // Avoid the process.exit error from being thrown.952    processExitSpy.mockRestore();953  });954 955  it('invokes runNonInteractiveStreamJson and performs cleanup in stream-json mode', async () => {956    const originalIsTTY = Object.getOwnPropertyDescriptor(957      process.stdin,958      'isTTY',959    );960    const originalIsRaw = Object.getOwnPropertyDescriptor(961      process.stdin,962      'isRaw',963    );964    Object.defineProperty(process.stdin, 'isTTY', {965      value: false, // 在 stream-json 模式下应为 false966      configurable: true,967    });968    Object.defineProperty(process.stdin, 'isRaw', {969      value: false,970      configurable: true,971    });972 973    const processExitSpy = vi974      .spyOn(process, 'exit')975      .mockImplementation((code) => {976        throw new MockProcessExitError(code);977      });978 979    const { loadCliConfig, parseArguments } = await import(980      './config/config.js'981    );982    const { loadSettings } = await import('./config/settings.js');983    const cleanupModule = await import('./utils/cleanup.js');984    const validatorModule = await import('./validateNonInterActiveAuth.js');985    const streamJsonModule = await import('./nonInteractive/session.js');986    const initializerModule = await import('./core/initializer.js');987    const startupWarningsModule = await import('./utils/startupWarnings.js');988    const userStartupWarningsModule = await import(989      './utils/userStartupWarnings.js'990    );991 992    vi.mocked(cleanupModule.cleanupCheckpoints).mockResolvedValue(undefined);993    vi.mocked(cleanupModule.registerCleanup).mockImplementation(() => {});994    const runExitCleanupMock = vi.mocked(cleanupModule.runExitCleanup);995    runExitCleanupMock.mockResolvedValue(undefined);996    vi.spyOn(initializerModule, 'initializeApp').mockResolvedValue({997      authError: null,998      themeError: null,999      shouldOpenAuthDialog: false,1000      geminiMdFileCount: 0,1001    });1002    vi.spyOn(startupWarningsModule, 'getStartupWarnings').mockResolvedValue([]);1003    vi.spyOn(1004      userStartupWarningsModule,1005      'getUserStartupWarnings',1006    ).mockResolvedValue([]);1007 1008    const validatedConfig = { validated: true } as unknown as Config;1009    const validateAuthSpy = vi1010      .spyOn(validatorModule, 'validateNonInteractiveAuth')1011      .mockResolvedValue(validatedConfig);1012    const runStreamJsonSpy = vi1013      .spyOn(streamJsonModule, 'runNonInteractiveStreamJson')1014      .mockResolvedValue(undefined);1015 1016    vi.mocked(loadSettings).mockReturnValue({1017      errors: [],1018      merged: {1019        advanced: {},1020        security: { auth: {} },1021        ui: {},1022      },1023      setValue: vi.fn(),1024      forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),1025      migrationWarnings: [],1026      getUserHooks: () => undefined,1027      getProjectHooks: () => undefined,1028    } as never);1029 1030    vi.mocked(parseArguments).mockResolvedValue({1031      extensions: [],1032    } as never);1033 1034    const configStub = {1035      isInteractive: () => false,1036      getQuestion: () => '  hello stream  ',1037      getSandbox: () => false,1038      getApprovalMode: () => ApprovalMode.DEFAULT,1039      getDebugMode: () => false,1040      getListExtensions: () => false,1041      getMcpServers: () => ({}),1042      getTopTierMcpServers: () => undefined,1043      initialize: vi.fn().mockResolvedValue(undefined),1044      waitForMcpReady: vi.fn().mockResolvedValue(undefined),1045      getIdeMode: () => false,1046      getExperimentalZedIntegration: () => false,1047      getScreenReader: () => false,1048      getGeminiMdFileCount: () => 0,1049      getProjectRoot: () => '/',1050      getInputFormat: () => 'stream-json',1051      getContentGeneratorConfig: () => ({ authType: 'test-auth' }),1052      getWarnings: () => [],1053      isSafeMode: () => false,1054      getModelsConfig: () => ({ getCurrentAuthType: () => null }),1055      getUsageStatisticsEnabled: () => true,1056      getSessionId: () => 'test-session-id',1057      getOutputFormat: () => OutputFormat.TEXT,1058    } as unknown as Config;1059 1060    vi.mocked(loadCliConfig).mockResolvedValue(configStub);1061 1062    process.env['SANDBOX'] = '1';1063    try {1064      await main();1065    } catch (error) {1066      if (!(error instanceof MockProcessExitError)) {1067        throw error;1068      }1069    } finally {1070      processExitSpy.mockRestore();1071      if (originalIsTTY) {1072        Object.defineProperty(process.stdin, 'isTTY', originalIsTTY);1073      } else {1074        delete (process.stdin as { isTTY?: unknown }).isTTY;1075      }1076      if (originalIsRaw) {1077        Object.defineProperty(process.stdin, 'isRaw', originalIsRaw);1078      } else {1079        delete (process.stdin as { isRaw?: unknown }).isRaw;1080      }1081      delete process.env['SANDBOX'];1082    }1083 1084    expect(runStreamJsonSpy).toHaveBeenCalledTimes(1);1085    const [configArg, inputArg, settingsArg] = runStreamJsonSpy.mock.calls[0];1086    expect(configArg).toBe(validatedConfig);1087    expect(inputArg).toBe('hello stream');1088    // Regression guard: PR-A's progressive-MCP refactor previously1089    // dropped the `settings` argument here, which silently fell back to1090    // `createMinimalSettings()` inside `runNonInteractiveStreamJson`.1091    // The parallel `runNonInteractive` path still received settings, so1092    // stream-json sessions lost any user-configured permission /1093    // approval / hook setup.1094    expect(settingsArg).toBeDefined();1095 1096    expect(validateAuthSpy).toHaveBeenCalledWith(1097      undefined,1098      configStub,1099      expect.any(Object),1100    );1101    expect(initializerModule.initializeApp).toHaveBeenCalledWith(1102      configStub,1103      expect.any(Object),1104      { deferIdeConnection: false },1105    );1106    expect(runExitCleanupMock).toHaveBeenCalledTimes(1);1107  });1108});1109 1110describe('gemini.tsx main function kitty protocol', () => {1111  let originalEnvNoRelaunch: string | undefined;1112  let setRawModeSpy: MockInstance<1113    (mode: boolean) => NodeJS.ReadStream & { fd: 0 }1114  >;1115  let initialSigintListeners: NodeJS.SignalsListener[];1116  let initialSigtermListeners: NodeJS.SignalsListener[];1117 1118  beforeEach(() => {1119    // Set no relaunch in tests since process spawning causing issues in tests1120    originalEnvNoRelaunch = process.env['QWEN_CODE_NO_RELAUNCH'];1121    process.env['QWEN_CODE_NO_RELAUNCH'] = 'true';1122    initialSigintListeners = process.listeners(1123      'SIGINT',1124    ) as NodeJS.SignalsListener[];1125    initialSigtermListeners = process.listeners(1126      'SIGTERM',1127    ) as NodeJS.SignalsListener[];1128 1129    // eslint-disable-next-line @typescript-eslint/no-explicit-any1130    if (!(process.stdin as any).setRawMode) {1131      // eslint-disable-next-line @typescript-eslint/no-explicit-any1132      (process.stdin as any).setRawMode = vi.fn();1133    }1134    setRawModeSpy = vi.spyOn(process.stdin, 'setRawMode');1135 1136    Object.defineProperty(process.stdin, 'isTTY', {1137      value: true,1138      configurable: true,1139    });1140    Object.defineProperty(process.stdin, 'isRaw', {1141      value: false,1142      configurable: true,1143    });1144  });1145 1146  afterEach(() => {1147    for (const listener of process.listeners('SIGINT')) {1148      if (!initialSigintListeners.includes(listener)) {1149        process.removeListener('SIGINT', listener as NodeJS.SignalsListener);1150      }1151    }1152    for (const listener of process.listeners('SIGTERM')) {1153      if (!initialSigtermListeners.includes(listener)) {1154        process.removeListener('SIGTERM', listener as NodeJS.SignalsListener);1155      }1156    }1157 1158    // Restore original env variables1159    if (originalEnvNoRelaunch !== undefined) {1160      process.env['QWEN_CODE_NO_RELAUNCH'] = originalEnvNoRelaunch;1161    } else {1162      delete process.env['QWEN_CODE_NO_RELAUNCH'];1163    }1164    vi.restoreAllMocks();1165  });1166 1167  it('should call setRawMode and detectAndEnableKittyProtocol when isInteractive is true', async () => {1168    const { detectAndEnableKittyProtocol } = await import(1169      './ui/utils/kittyProtocolDetector.js'1170    );1171    const { loadCliConfig, parseArguments } = await import(1172      './config/config.js'1173    );1174    const { loadSettings } = await import('./config/settings.js');1175    const initializerModule = await import('./core/initializer.js');1176    const initializeAppSpy = vi1177      .spyOn(initializerModule, 'initializeApp')1178      .mockResolvedValue({1179        authError: null,1180        themeError: null,1181        shouldOpenAuthDialog: false,1182        geminiMdFileCount: 0,1183      });1184    vi.mocked(loadCliConfig).mockResolvedValue({1185      isInteractive: () => true,1186      getQuestion: () => '',1187      getSandbox: () => false,1188      getDebugMode: () => false,1189      getListExtensions: () => false,1190      getMcpServers: () => ({}),1191      getTopTierMcpServers: () => undefined,1192      initialize: vi.fn(),1193      waitForMcpReady: vi.fn().mockResolvedValue(undefined),1194      getIdeMode: () => false,1195      getExperimentalZedIntegration: () => false,1196      getScreenReader: () => false,1197      getGeminiMdFileCount: () => 0,1198      getWarnings: () => [],1199      isSafeMode: () => false,1200      getModelsConfig: () => ({ getCurrentAuthType: () => null }),

Showing the first 1,200 of 2046 lines. Download the file for the rest.

basant307/AI_Governance_Project · CoolFace