CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
config.test.ts4123 linesDownload Raw Back to config
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';8import * as os from 'node:os';9import * as path from 'node:path';10import {11  ToolNames,12  DEFAULT_QWEN_MODEL,13  OutputFormat,14  NativeLspService,15  Storage,16} from '@qwen-code/qwen-code-core';17import { loadCliConfig, parseArguments, type CliArgs } from './config.js';18import type { Settings } from './settings.js';19import * as ServerConfig from '@qwen-code/qwen-code-core';20import { isWorkspaceTrusted } from './trustedFolders.js';21import { resetMcpApprovalsForTesting } from './mcpApprovals.js';22 23const mockWriteStderrLine = vi.hoisted(() => vi.fn());24const mockWriteStdoutLine = vi.hoisted(() => vi.fn());25const mockSessionServiceInstance = vi.hoisted(() => ({26  loadLastSession: vi.fn(),27  loadSession: vi.fn(),28  forkSession: vi.fn(),29  sessionExists: vi.fn(),30}));31const mockSessionServiceCtor = vi.hoisted(() =>32  vi.fn(() => mockSessionServiceInstance),33);34const mockConfigConstructorParams = vi.hoisted(() => vi.fn());35 36vi.mock('../utils/stdioHelpers.js', () => ({37  writeStderrLine: mockWriteStderrLine,38  writeStdoutLine: mockWriteStdoutLine,39  clearScreen: vi.fn(),40}));41 42const createNativeLspServiceInstance = () => ({43  discoverAndPrepare: vi.fn(),44  start: vi.fn(),45  definitions: vi.fn().mockResolvedValue([]),46  references: vi.fn().mockResolvedValue([]),47  workspaceSymbols: vi.fn().mockResolvedValue([]),48  hover: vi.fn().mockResolvedValue(null),49  documentSymbols: vi.fn().mockResolvedValue([]),50  implementations: vi.fn().mockResolvedValue([]),51  prepareCallHierarchy: vi.fn().mockResolvedValue([]),52  incomingCalls: vi.fn().mockResolvedValue([]),53  outgoingCalls: vi.fn().mockResolvedValue([]),54  diagnostics: vi.fn().mockResolvedValue([]),55  workspaceDiagnostics: vi.fn().mockResolvedValue([]),56  codeActions: vi.fn().mockResolvedValue([]),57  applyWorkspaceEdit: vi.fn().mockResolvedValue(false),58  getStatusSnapshot: vi.fn().mockReturnValue({59    enabled: true,60    configuredServers: 1,61    readyServers: 1,62    failedServers: 0,63    inProgressServers: 0,64    notStartedServers: 0,65    servers: [66      {67        name: 'typescript',68        status: 'READY',69        languages: ['typescript'],70        transport: 'stdio',71      },72    ],73  }),74});75 76vi.mock('./trustedFolders.js', () => ({77  isWorkspaceTrusted: vi78    .fn()79    .mockReturnValue({ isTrusted: true, source: 'file' }), // Default to trusted80}));81 82const nativeLspServiceMock = vi.mocked(NativeLspService);83const getLastLspInstance = () => {84  const results = nativeLspServiceMock.mock.results;85  if (results.length === 0) {86    return undefined;87  }88  return results[results.length - 1]?.value as ReturnType<89    typeof createNativeLspServiceInstance90  >;91};92 93vi.mock('fs', async (importOriginal) => {94  const actualFs = await importOriginal<typeof import('fs')>();95  const pathMod = await import('node:path');96  const mockHome = '/mock/home/user';97  const MOCK_CWD1 = process.cwd();98  const MOCK_CWD2 = pathMod.resolve(pathMod.sep, 'home', 'user', 'project');99 100  const mockPaths = new Set([101    MOCK_CWD1,102    MOCK_CWD2,103    pathMod.resolve(pathMod.sep, 'cli', 'path1'),104    pathMod.resolve(pathMod.sep, 'settings', 'path1'),105    pathMod.join(mockHome, 'settings', 'path2'),106    pathMod.join(MOCK_CWD2, 'cli', 'path2'),107    pathMod.join(MOCK_CWD2, 'settings', 'path3'),108  ]);109 110  return {111    ...actualFs,112    mkdirSync: vi.fn(),113    writeFileSync: vi.fn(),114    existsSync: vi.fn((p) => mockPaths.has(p.toString())),115    statSync: vi.fn((p) => {116      if (mockPaths.has(p.toString())) {117        return { isDirectory: () => true } as unknown as import('fs').Stats;118      }119      return (actualFs as typeof import('fs')).statSync(p as unknown as string);120    }),121    realpathSync: vi.fn((p) => p),122  };123});124 125vi.mock('os', async (importOriginal) => {126  const actualOs = await importOriginal<typeof os>();127  return {128    ...actualOs,129    homedir: vi.fn(() => '/mock/home/user'),130  };131});132 133vi.mock('open', () => ({134  default: vi.fn(),135}));136 137vi.mock('read-package-up', () => ({138  readPackageUp: vi.fn(() =>139    Promise.resolve({140      packageJson: {141        version: 'test-version',142        config: { sandboxImageUri: 'pkg-default-image' },143      },144    }),145  ),146}));147 148vi.mock('command-exists', () => ({149  default: {150    sync: vi.fn(() => true),151  },152}));153 154vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {155  const actualServer = await importOriginal<typeof ServerConfig>();156  const SkillManagerMock = vi.fn();157  SkillManagerMock.prototype.startWatching = vi158    .fn()159    .mockResolvedValue(undefined);160  SkillManagerMock.prototype.stopWatching = vi.fn();161  SkillManagerMock.prototype.listSkills = vi.fn().mockResolvedValue([]);162  SkillManagerMock.prototype.addChangeListener = vi.fn();163  SkillManagerMock.prototype.removeChangeListener = vi.fn();164  class ConfigWithParamCapture extends actualServer.Config {165    constructor(...args: ConstructorParameters<typeof actualServer.Config>) {166      mockConfigConstructorParams(args[0]);167      super(...args);168    }169  }170  return {171    ...actualServer,172    Config: ConfigWithParamCapture,173    NativeLspService: vi174      .fn()175      .mockImplementation(() => createNativeLspServiceInstance()),176    SessionService: mockSessionServiceCtor,177    SkillManager: SkillManagerMock,178    IdeClient: {179      getInstance: vi.fn().mockResolvedValue({180        getConnectionStatus: vi.fn(),181        initialize: vi.fn(),182        shutdown: vi.fn(),183      }),184    },185    loadEnvironment: vi.fn(),186    loadServerHierarchicalMemory: vi.fn(187      (cwd, dirs, debug, fileService, extensionPaths, _maxDirs) =>188        Promise.resolve({189          memoryContent: extensionPaths?.join(',') || '',190          fileCount: extensionPaths?.length || 0,191          ruleCount: 0,192          conditionalRules: [],193          projectRoot: cwd || '/tmp',194        }),195    ),196    DEFAULT_MEMORY_FILE_FILTERING_OPTIONS: {197      respectGitIgnore: false,198      respectQwenIgnore: true,199    },200    DEFAULT_FILE_FILTERING_OPTIONS: {201      respectGitIgnore: true,202      respectQwenIgnore: true,203    },204  };205});206 207describe('parseArguments', () => {208  const originalArgv = process.argv;209 210  afterEach(() => {211    process.argv = originalArgv;212  });213 214  it('should throw an error when both --prompt and --prompt-interactive are used together', async () => {215    process.argv = [216      'node',217      'script.js',218      '--prompt',219      'test prompt',220      '--prompt-interactive',221      'interactive prompt',222    ];223 224    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {225      throw new Error('process.exit called');226    });227    mockWriteStderrLine.mockClear();228 229    await expect(parseArguments()).rejects.toThrow('process.exit called');230 231    expect(mockWriteStderrLine).toHaveBeenCalledWith(232      expect.stringContaining(233        'Cannot use both --prompt (-p) and --prompt-interactive (-i) together',234      ),235    );236 237    mockExit.mockRestore();238  });239 240  it('should throw an error when using short flags -p and -i together', async () => {241    process.argv = [242      'node',243      'script.js',244      '-p',245      'test prompt',246      '-i',247      'interactive prompt',248    ];249 250    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {251      throw new Error('process.exit called');252    });253    mockWriteStderrLine.mockClear();254 255    await expect(parseArguments()).rejects.toThrow('process.exit called');256 257    expect(mockWriteStderrLine).toHaveBeenCalledWith(258      expect.stringContaining(259        'Cannot use both --prompt (-p) and --prompt-interactive (-i) together',260      ),261    );262 263    mockExit.mockRestore();264  });265 266  it('should allow --prompt without --prompt-interactive', async () => {267    process.argv = ['node', 'script.js', '--prompt', 'test prompt'];268    const argv = await parseArguments();269    expect(argv.prompt).toBe('test prompt');270    expect(argv.promptInteractive).toBeUndefined();271  });272 273  it('should allow --prompt-interactive without --prompt', async () => {274    process.argv = [275      'node',276      'script.js',277      '--prompt-interactive',278      'interactive prompt',279    ];280    const argv = await parseArguments();281    expect(argv.promptInteractive).toBe('interactive prompt');282    expect(argv.prompt).toBeUndefined();283  });284 285  it('should allow -i flag as alias for --prompt-interactive', async () => {286    process.argv = ['node', 'script.js', '-i', 'interactive prompt'];287    const argv = await parseArguments();288    expect(argv.promptInteractive).toBe('interactive prompt');289    expect(argv.prompt).toBeUndefined();290  });291 292  it('parses --insecure as a boolean flag (default false)', async () => {293    process.argv = ['node', 'script.js'];294    const defaultArgv = await parseArguments();295    expect(defaultArgv.insecure).toBe(false);296 297    process.argv = ['node', 'script.js', '--insecure'];298    const argv = await parseArguments();299    expect(argv.insecure).toBe(true);300  });301 302  it('rejects --json-schema combined with --acp', async () => {303    // ACP runs an independent turn loop (runAcpAgent) that doesn't honour304    // the synthetic structured_output terminal contract. The yargs check305    // must reject the combination at parse time so users get an actionable306    // error instead of silently watching the run never terminate.307    process.argv = [308      'node',309      'script.js',310      '--acp',311      '--json-schema',312      '{"type":"object"}',313    ];314 315    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {316      throw new Error('process.exit called');317    });318    mockWriteStderrLine.mockClear();319 320    await expect(parseArguments()).rejects.toThrow('process.exit called');321 322    expect(mockWriteStderrLine).toHaveBeenCalledWith(323      expect.stringContaining('--json-schema cannot be used with --acp'),324    );325 326    mockExit.mockRestore();327  });328 329  it('rejects --json-schema combined with --experimental-acp (deprecated alias)', async () => {330    // --experimental-acp is the deprecated alias; the same mutual-331    // exclusion logic must apply or users get the silent-no-terminate332    // behaviour the --acp check was added to prevent.333    process.argv = [334      'node',335      'script.js',336      '--experimental-acp',337      '--json-schema',338      '{"type":"object"}',339    ];340 341    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {342      throw new Error('process.exit called');343    });344    mockWriteStderrLine.mockClear();345 346    await expect(parseArguments()).rejects.toThrow('process.exit called');347 348    expect(mockWriteStderrLine).toHaveBeenCalledWith(349      expect.stringContaining('--json-schema cannot be used with --acp'),350    );351 352    mockExit.mockRestore();353  });354 355  it('rejects --json-schema combined with --prompt-interactive (-i)', async () => {356    // The interactive flow doesn't honour the synthetic-tool terminal357    // contract — `structured_output` would just print "accepted" and358    // leave the chat alive. The yargs check must reject this at parse359    // time so users get an actionable message instead of a silently360    // misbehaving run.361    process.argv = [362      'node',363      'script.js',364      '-i',365      'do work then submit',366      '--json-schema',367      '{"type":"object"}',368    ];369 370    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {371      throw new Error('process.exit called');372    });373    mockWriteStderrLine.mockClear();374 375    await expect(parseArguments()).rejects.toThrow('process.exit called');376 377    expect(mockWriteStderrLine).toHaveBeenCalledWith(378      expect.stringContaining(379        'structured output only terminates the non-interactive flow',380      ),381    );382 383    mockExit.mockRestore();384  });385 386  it('rejects --json-schema combined with --input-format stream-json', async () => {387    // The "first valid structured_output call ends the session"388    // contract is incompatible with the long-lived stream-json input389    // protocol. Also load-bearing: gemini.tsx's390    // `process.exit(process.exitCode ?? 0)` plumbing in the stream-json391    // branch explicitly relies on this rejection holding. Pair with392    // --output-format stream-json because input/output formats must393    // match (a separate yargs check fires first otherwise).394    process.argv = [395      'node',396      'script.js',397      '--input-format',398      'stream-json',399      '--output-format',400      'stream-json',401      '--json-schema',402      '{"type":"object"}',403    ];404 405    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {406      throw new Error('process.exit called');407    });408    mockWriteStderrLine.mockClear();409 410    await expect(parseArguments()).rejects.toThrow('process.exit called');411 412    expect(mockWriteStderrLine).toHaveBeenCalledWith(413      expect.stringContaining('first structured_output call ends the session'),414    );415 416    mockExit.mockRestore();417  });418 419  it('should parse --system-prompt', async () => {420    process.argv = [421      'node',422      'script.js',423      '--system-prompt',424      'You are a test system prompt.',425    ];426    const argv = await parseArguments();427    expect(argv.systemPrompt).toBe('You are a test system prompt.');428    expect(argv.appendSystemPrompt).toBeUndefined();429  });430 431  it('should parse --append-system-prompt', async () => {432    process.argv = [433      'node',434      'script.js',435      '--append-system-prompt',436      'Be extra concise.',437    ];438    const argv = await parseArguments();439    expect(argv.appendSystemPrompt).toBe('Be extra concise.');440    expect(argv.systemPrompt).toBeUndefined();441  });442 443  it('should allow -r flag as alias for --resume', async () => {444    process.argv = [445      'node',446      'script.js',447      '-r',448      '123e4567-e89b-12d3-a456-426614174000',449    ];450    const argv = await parseArguments();451    expect(argv.resume).toBe('123e4567-e89b-12d3-a456-426614174000');452  });453 454  it('should allow -c flag as alias for --continue', async () => {455    process.argv = ['node', 'script.js', '-c'];456    const argv = await parseArguments();457    expect(argv.continue).toBe(true);458  });459 460  it('should parse --fork-session with --resume', async () => {461    process.argv = [462      'node',463      'script.js',464      '--resume',465      '123e4567-e89b-12d3-a456-426614174000',466      '--fork-session',467    ];468    const argv = await parseArguments();469    expect(argv.resume).toBe('123e4567-e89b-12d3-a456-426614174000');470    expect(argv.forkSession).toBe(true);471  });472 473  it('should parse --fork-session with the --resume picker form', async () => {474    process.argv = ['node', 'script.js', '--resume', '--fork-session'];475    const argv = await parseArguments();476    // Empty string is the existing yargs shape for picker form: --resume477    // without an explicit session ID.478    expect(argv.resume).toBe('');479    expect(argv.forkSession).toBe(true);480  });481 482  it('should reject --fork-session without --resume or --continue', async () => {483    process.argv = ['node', 'script.js', '--fork-session'];484    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {485      throw new Error('process.exit called');486    });487    mockWriteStderrLine.mockClear();488 489    await expect(parseArguments()).rejects.toThrow('process.exit called');490 491    expect(mockWriteStderrLine).toHaveBeenCalledWith(492      expect.stringContaining(493        '--fork-session must be used with --resume or --continue',494      ),495    );496 497    mockExit.mockRestore();498  });499 500  it('should convert positional query argument to prompt by default', async () => {501    process.argv = ['node', 'script.js', 'Hi Gemini'];502    const argv = await parseArguments();503    expect(argv.query).toBe('Hi Gemini');504    expect(argv.prompt).toBe('Hi Gemini');505    expect(argv.promptInteractive).toBeUndefined();506  });507 508  it('should map @path to prompt (one-shot) when it starts with @', async () => {509    process.argv = ['node', 'script.js', '@path ./file.md'];510    const argv = await parseArguments();511    expect(argv.query).toBe('@path ./file.md');512    expect(argv.prompt).toBe('@path ./file.md');513    expect(argv.promptInteractive).toBeUndefined();514  });515 516  it('should map @path to prompt even when config flags are present', async () => {517    // @path queries should now go to one-shot mode regardless of other flags518    process.argv = [519      'node',520      'script.js',521      '@path',522      './file.md',523      '--model',524      'gemini-1.5-pro',525    ];526    const argv = await parseArguments();527    expect(argv.query).toBe('@path ./file.md');528    expect(argv.prompt).toBe('@path ./file.md'); // Should map to one-shot529    expect(argv.promptInteractive).toBeUndefined();530    expect(argv.model).toBe('gemini-1.5-pro');531  });532 533  it('maps unquoted positional @path + arg to prompt (one-shot)', async () => {534    // Simulate: gemini @path ./file.md535    process.argv = ['node', 'script.js', '@path', './file.md'];536    const argv = await parseArguments();537    // After normalization, query is a single string538    expect(argv.query).toBe('@path ./file.md');539    // And it's mapped to one-shot prompt when no -p/-i flags are set540    expect(argv.prompt).toBe('@path ./file.md');541    expect(argv.promptInteractive).toBeUndefined();542  });543 544  it('should handle multiple @path arguments in a single command (one-shot)', async () => {545    // Simulate: gemini @path ./file1.md @path ./file2.md546    process.argv = [547      'node',548      'script.js',549      '@path',550      './file1.md',551      '@path',552      './file2.md',553    ];554    const argv = await parseArguments();555    // After normalization, all arguments are joined with spaces556    expect(argv.query).toBe('@path ./file1.md @path ./file2.md');557    // And it's mapped to one-shot prompt558    expect(argv.prompt).toBe('@path ./file1.md @path ./file2.md');559    expect(argv.promptInteractive).toBeUndefined();560  });561 562  it('should handle mixed quoted and unquoted @path arguments (one-shot)', async () => {563    // Simulate: gemini "@path ./file1.md" @path ./file2.md "additional text"564    process.argv = [565      'node',566      'script.js',567      '@path ./file1.md',568      '@path',569      './file2.md',570      'additional text',571    ];572    const argv = await parseArguments();573    // After normalization, all arguments are joined with spaces574    expect(argv.query).toBe(575      '@path ./file1.md @path ./file2.md additional text',576    );577    // And it's mapped to one-shot prompt578    expect(argv.prompt).toBe(579      '@path ./file1.md @path ./file2.md additional text',580    );581    expect(argv.promptInteractive).toBeUndefined();582  });583 584  it('should map @path to prompt with ambient flags (debug, telemetry)', async () => {585    // Ambient flags like debug, telemetry should NOT affect routing586    process.argv = [587      'node',588      'script.js',589      '@path',590      './file.md',591      '--debug',592      '--telemetry',593    ];594    const argv = await parseArguments();595    expect(argv.query).toBe('@path ./file.md');596    expect(argv.prompt).toBe('@path ./file.md'); // Should map to one-shot597    expect(argv.promptInteractive).toBeUndefined();598    expect(argv.debug).toBe(true);599    expect(argv.telemetry).toBe(true);600  });601 602  it('should map any @command to prompt (one-shot)', async () => {603    // Test that all @commands now go to one-shot mode604    const testCases = [605      '@path ./file.md',606      '@include src/',607      '@search pattern',608      '@web query',609      '@git status',610    ];611 612    for (const testQuery of testCases) {613      process.argv = ['node', 'script.js', testQuery];614      const argv = await parseArguments();615      expect(argv.query).toBe(testQuery);616      expect(argv.prompt).toBe(testQuery);617      expect(argv.promptInteractive).toBeUndefined();618    }619  });620 621  it('should handle @command with leading whitespace', async () => {622    // Test that trim() + routing handles leading whitespace correctly623    process.argv = ['node', 'script.js', '  @path ./file.md'];624    const argv = await parseArguments();625    expect(argv.query).toBe('  @path ./file.md');626    expect(argv.prompt).toBe('  @path ./file.md');627    expect(argv.promptInteractive).toBeUndefined();628  });629 630  it('should throw an error when both --yolo and --approval-mode are used together', async () => {631    process.argv = [632      'node',633      'script.js',634      '--yolo',635      '--approval-mode',636      'default',637    ];638 639    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {640      throw new Error('process.exit called');641    });642    mockWriteStderrLine.mockClear();643 644    await expect(parseArguments()).rejects.toThrow('process.exit called');645 646    expect(mockWriteStderrLine).toHaveBeenCalledWith(647      expect.stringContaining(648        'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.',649      ),650    );651 652    mockExit.mockRestore();653  });654 655  it('should throw an error when using short flags -y and --approval-mode together', async () => {656    process.argv = ['node', 'script.js', '-y', '--approval-mode', 'yolo'];657 658    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {659      throw new Error('process.exit called');660    });661    mockWriteStderrLine.mockClear();662 663    await expect(parseArguments()).rejects.toThrow('process.exit called');664 665    expect(mockWriteStderrLine).toHaveBeenCalledWith(666      expect.stringContaining(667        'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.',668      ),669    );670 671    mockExit.mockRestore();672  });673 674  it('should allow --system-prompt and --append-system-prompt together', async () => {675    process.argv = [676      'node',677      'script.js',678      '--system-prompt',679      'Override prompt',680      '--append-system-prompt',681      'Append prompt',682    ];683 684    const argv = await parseArguments();685    expect(argv.systemPrompt).toBe('Override prompt');686    expect(argv.appendSystemPrompt).toBe('Append prompt');687  });688 689  it('should throw an error when include-partial-messages is used without stream-json output', async () => {690    process.argv = ['node', 'script.js', '--include-partial-messages'];691 692    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {693      throw new Error('process.exit called');694    });695    mockWriteStderrLine.mockClear();696 697    await expect(parseArguments()).rejects.toThrow('process.exit called');698 699    expect(mockWriteStderrLine).toHaveBeenCalledWith(700      expect.stringContaining(701        '--include-partial-messages requires --output-format stream-json',702      ),703    );704 705    mockExit.mockRestore();706  });707 708  it('should reject --json-schema with no prompt source when stdin is a TTY', async () => {709    // True interactive invocation with no prompt anywhere → fail fast.710    process.argv = ['node', 'script.js', '--json-schema', '{"type":"object"}'];711 712    const originalIsTTY = process.stdin.isTTY;713    process.stdin.isTTY = true;714    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {715      throw new Error('process.exit called');716    });717    mockWriteStderrLine.mockClear();718 719    try {720      await expect(parseArguments()).rejects.toThrow('process.exit called');721      expect(mockWriteStderrLine).toHaveBeenCalledWith(722        expect.stringContaining(723          '--json-schema only applies to non-interactive mode',724        ),725      );726    } finally {727      mockExit.mockRestore();728      process.stdin.isTTY = originalIsTTY;729    }730  });731 732  it('should accept --json-schema with no -p / positional when stdin is piped', async () => {733    // `echo "..." | qwen --json-schema ...` — input arrives via the734    // pipe, so the prompt-presence check must not block the run.735    process.argv = ['node', 'script.js', '--json-schema', '{"type":"object"}'];736 737    const originalIsTTY = process.stdin.isTTY;738    process.stdin.isTTY = false;739    try {740      const argv = await parseArguments();741      expect(argv.jsonSchema).toBe('{"type":"object"}');742      expect(argv.prompt).toBeUndefined();743    } finally {744      process.stdin.isTTY = originalIsTTY;745    }746  });747 748  it('should throw when --json-schema is combined with --input-format stream-json', async () => {749    // stream-json input runs through runNonInteractiveStreamJson which750    // doesn't honor the structured-output single-shot termination751    // contract — reject the combination at parse time so the user sees752    // the mismatch immediately.753    process.argv = [754      'node',755      'script.js',756      '-p',757      'hi',758      '--output-format',759      'stream-json',760      '--input-format',761      'stream-json',762      '--json-schema',763      '{"type":"object"}',764    ];765 766    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {767      throw new Error('process.exit called');768    });769    mockWriteStderrLine.mockClear();770 771    await expect(parseArguments()).rejects.toThrow('process.exit called');772 773    expect(mockWriteStderrLine).toHaveBeenCalledWith(774      expect.stringContaining(775        '--json-schema cannot be used with --input-format stream-json',776      ),777    );778 779    mockExit.mockRestore();780  });781 782  it('should parse stream-json formats and include-partial-messages flag', async () => {783    process.argv = [784      'node',785      'script.js',786      '--output-format',787      'stream-json',788      '--input-format',789      'stream-json',790      '--include-partial-messages',791    ];792 793    const argv = await parseArguments();794 795    expect(argv.outputFormat).toBe('stream-json');796    expect(argv.inputFormat).toBe('stream-json');797    expect(argv.includePartialMessages).toBe(true);798  });799 800  it('should allow --approval-mode without --yolo', async () => {801    process.argv = ['node', 'script.js', '--approval-mode', 'auto-edit'];802    const argv = await parseArguments();803    expect(argv.approvalMode).toBe('auto-edit');804    expect(argv.yolo).toBe(false);805  });806 807  it('should allow --yolo without --approval-mode', async () => {808    process.argv = ['node', 'script.js', '--yolo'];809    const argv = await parseArguments();810    expect(argv.yolo).toBe(true);811    expect(argv.approvalMode).toBeUndefined();812  });813 814  it('should accept desktop as a channel identifier', async () => {815    process.argv = ['node', 'script.js', '--channel', 'desktop'];816    const argv = await parseArguments();817    expect(argv.channel).toBe('desktop');818  });819 820  it('should default ACP mode to the ACP channel when no channel is provided', async () => {821    process.argv = ['node', 'script.js', '--acp'];822    const argv = await parseArguments();823    expect(argv.channel).toBe('ACP');824  });825 826  it('keeps an explicit --channel when combined with --acp (the desktop invocation)', async () => {827    process.argv = ['node', 'script.js', '--acp', '--channel', 'desktop'];828    const argv = await parseArguments();829    // The `!result['channel']` guard must not override an explicitly provided830    // channel with the ACP default.831    expect(argv.channel).toBe('desktop');832    expect(argv.acp).toBe(true);833  });834 835  it('should reject invalid --approval-mode values', async () => {836    process.argv = ['node', 'script.js', '--approval-mode', 'invalid'];837 838    const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {839      throw new Error('process.exit called');840    });841    mockWriteStderrLine.mockClear();842 843    await expect(parseArguments()).rejects.toThrow('process.exit called');844 845    expect(mockWriteStderrLine).toHaveBeenCalledWith(846      expect.stringContaining('Invalid values:'),847    );848 849    mockExit.mockRestore();850  });851 852  it('should support comma-separated values for --allowed-tools', async () => {853    process.argv = [854      'node',855      'script.js',856      '--allowed-tools',857      'read_file,ShellTool(git status)',858    ];859    const argv = await parseArguments();860    expect(argv.allowedTools).toEqual(['read_file', 'ShellTool(git status)']);861  });862 863  it('should support comma-separated values for --allowed-mcp-server-names', async () => {864    process.argv = [865      'node',866      'script.js',867      '--allowed-mcp-server-names',868      'server1,server2',869    ];870    const argv = await parseArguments();871    expect(argv.allowedMcpServerNames).toEqual(['server1', 'server2']);872  });873 874  it('should support comma-separated values for --extensions', async () => {875    process.argv = ['node', 'script.js', '--extensions', 'ext1,ext2'];876    const argv = await parseArguments();877    expect(argv.extensions).toEqual(['ext1', 'ext2']);878  });879 880  it('should parse --bare', async () => {881    process.argv = ['node', 'script.js', '--bare'];882    const argv = await parseArguments();883    expect(argv.bare).toBe(true);884  });885 886  describe('--fallback-model flag', () => {887    it('parses a single fallback model', async () => {888      process.argv = ['node', 'script.js', '--fallback-model', 'qwen-plus'];889      const argv = await parseArguments();890      expect(argv.fallbackModel).toEqual(['qwen-plus']);891    });892 893    it('parses repeated --fallback-model flags', async () => {894      process.argv = [895        'node',896        'script.js',897        '--fallback-model',898        'qwen-plus',899        '--fallback-model',900        'qwen-turbo',901      ];902      const argv = await parseArguments();903      expect(argv.fallbackModel).toEqual(['qwen-plus', 'qwen-turbo']);904    });905 906    it('splits comma-separated values in a single flag', async () => {907      process.argv = [908        'node',909        'script.js',910        '--fallback-model',911        'qwen-plus,qwen-turbo',912      ];913      const argv = await parseArguments();914      expect(argv.fallbackModel).toEqual(['qwen-plus', 'qwen-turbo']);915    });916 917    it('combines repeated flags with comma-separated values', async () => {918      process.argv = [919        'node',920        'script.js',921        '--fallback-model',922        'qwen-plus,qwen-turbo',923        '--fallback-model',924        'qwen-max',925      ];926      const argv = await parseArguments();927      expect(argv.fallbackModel).toEqual([928        'qwen-plus',929        'qwen-turbo',930        'qwen-max',931      ]);932    });933 934    it('trims whitespace around comma-separated values', async () => {935      process.argv = [936        'node',937        'script.js',938        '--fallback-model',939        ' qwen-plus , qwen-turbo ',940      ];941      const argv = await parseArguments();942      expect(argv.fallbackModel).toEqual(['qwen-plus', 'qwen-turbo']);943    });944 945    it('defaults to undefined when not provided', async () => {946      process.argv = ['node', 'script.js'];947      const argv = await parseArguments();948      expect(argv.fallbackModel).toBeUndefined();949    });950  });951});952 953describe('loadCliConfig', () => {954  const originalArgv = process.argv;955 956  beforeEach(() => {957    vi.resetAllMocks();958    nativeLspServiceMock.mockReset();959    nativeLspServiceMock.mockImplementation(960      () => createNativeLspServiceInstance() as unknown as NativeLspService,961    );962    mockSessionServiceCtor.mockImplementation(() => mockSessionServiceInstance);963    mockSessionServiceInstance.loadLastSession.mockResolvedValue(undefined);964    mockSessionServiceInstance.loadSession.mockResolvedValue(undefined);965    mockSessionServiceInstance.forkSession.mockResolvedValue({966      filePath: '/mock/fork.jsonl',967      copiedCount: 1,968    });969    mockSessionServiceInstance.sessionExists.mockResolvedValue(false);970    vi.mocked(os.homedir).mockReturnValue('/mock/home/user');971    vi.stubEnv('GEMINI_API_KEY', 'test-api-key');972    resetMcpApprovalsForTesting();973  });974 975  afterEach(() => {976    process.argv = originalArgv;977    vi.unstubAllEnvs();978    resetMcpApprovalsForTesting();979    vi.restoreAllMocks();980  });981 982  it('should reset context file names to QWEN.md and AGENTS.md by default', async () => {983    process.argv = ['node', 'script.js'];984    const argv = await parseArguments();985    const settings: Settings = {};986    const setGeminiMdFilenameSpy = vi.spyOn(987      ServerConfig,988      'setGeminiMdFilename',989    );990 991    await loadCliConfig(settings, argv);992 993    expect(setGeminiMdFilenameSpy).toHaveBeenCalledTimes(1);994    expect(setGeminiMdFilenameSpy).toHaveBeenCalledWith([995      ServerConfig.DEFAULT_CONTEXT_FILENAME,996      ServerConfig.AGENT_CONTEXT_FILENAME,997    ]);998  });999 1000  it('should use configured context file name when settings.context.fileName is set', async () => {1001    process.argv = ['node', 'script.js'];1002    const argv = await parseArguments();1003    const settings: Settings = {1004      context: {1005        fileName: 'CUSTOM_AGENTS.md',1006      },1007    };1008    const setGeminiMdFilenameSpy = vi.spyOn(1009      ServerConfig,1010      'setGeminiMdFilename',1011    );1012 1013    await loadCliConfig(settings, argv);1014 1015    expect(setGeminiMdFilenameSpy).toHaveBeenCalledTimes(1);1016    expect(setGeminiMdFilenameSpy).toHaveBeenCalledWith('CUSTOM_AGENTS.md');1017  });1018 1019  it('should propagate stream-json formats to config', async () => {1020    process.argv = [1021      'node',1022      'script.js',1023      '--output-format',1024      'stream-json',1025      '--input-format',1026      'stream-json',1027      '--include-partial-messages',1028    ];1029    const argv = await parseArguments();1030    const settings: Settings = {};1031    const config = await loadCliConfig(settings, argv);1032 1033    expect(config.getOutputFormat()).toBe('stream-json');1034    expect(config.getInputFormat()).toBe('stream-json');1035    expect(config.getIncludePartialMessages()).toBe(true);1036  });1037 1038  it('should prefer CLI fallback models over settings fallback models', async () => {1039    process.argv = ['node', 'script.js', '--fallback-model', 'cli-a,cli-b'];1040    const argv = await parseArguments();1041    const config = await loadCliConfig({ modelFallbacks: 'settings-a' }, argv);1042 1043    expect(config.getModelFallbacks()).toEqual(['cli-a', 'cli-b']);1044  });1045 1046  it('should use settings fallback models when the CLI flag is absent', async () => {1047    process.argv = ['node', 'script.js'];1048    const argv = await parseArguments();1049    const config = await loadCliConfig(1050      { modelFallbacks: ' settings-a , settings-b ' },1051      argv,1052    );1053 1054    expect(config.getModelFallbacks()).toEqual(['settings-a', 'settings-b']);1055  });1056 1057  it('passes agents.maxParallelAgents from settings to core config', async () => {1058    process.argv = ['node', 'script.js'];1059    const argv = await parseArguments();1060    const config = await loadCliConfig(1061      { agents: { maxParallelAgents: 2 } },1062      argv,1063    );1064 1065    expect(config.getAgentsSettings().maxParallelAgents).toBe(2);1066  });1067 1068  it('should ignore blank settings fallback models', async () => {1069    process.argv = ['node', 'script.js'];1070    const argv = await parseArguments();1071    const config = await loadCliConfig({ modelFallbacks: '   ' }, argv);1072 1073    expect(config.getModelFallbacks()).toEqual([]);1074  });1075 1076  it('should enable runtime sleep prevention by default', async () => {1077    process.argv = ['node', 'script.js'];1078    const argv = await parseArguments();1079    const config = await loadCliConfig({}, argv);1080 1081    expect(config.getPreventSystemSleepEnabled()).toBe(true);1082  });1083 1084  describe('--insecure flag', () => {1085    const savedEnv: Record<string, string | undefined> = {};1086    let errorSpy: ReturnType<typeof vi.spyOn>;1087 1088    beforeEach(() => {1089      for (const key of ['QWEN_TLS_INSECURE', 'NODE_TLS_REJECT_UNAUTHORIZED']) {1090        savedEnv[key] = process.env[key];1091        delete process.env[key];1092      }1093      // Silence (and capture) the intentional MITM warning loadCliConfig emits.1094      errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});1095    });1096 1097    afterEach(() => {1098      errorSpy.mockRestore();1099      for (const [key, value] of Object.entries(savedEnv)) {1100        if (value === undefined) delete process.env[key];1101        else process.env[key] = value;1102      }1103    });1104 1105    it('sets QWEN_TLS_INSECURE=1 and NODE_TLS_REJECT_UNAUTHORIZED=0 when --insecure is passed', async () => {1106      process.argv = ['node', 'script.js', '--insecure'];1107      const argv = await parseArguments();1108      await loadCliConfig({}, argv);1109      expect(process.env['QWEN_TLS_INSECURE']).toBe('1');1110      expect(process.env['NODE_TLS_REJECT_UNAUTHORIZED']).toBe('0');1111      expect(errorSpy).toHaveBeenCalled();1112    });1113 1114    it('leaves TLS env vars unset without --insecure', async () => {1115      process.argv = ['node', 'script.js'];1116      const argv = await parseArguments();1117      await loadCliConfig({}, argv);1118      expect(process.env['QWEN_TLS_INSECURE']).toBeUndefined();1119      expect(process.env['NODE_TLS_REJECT_UNAUTHORIZED']).toBeUndefined();1120      expect(errorSpy).not.toHaveBeenCalled();1121    });1122 1123    it('propagates a pre-set QWEN_TLS_INSECURE to NODE_TLS_REJECT_UNAUTHORIZED=0', async () => {1124      process.env['QWEN_TLS_INSECURE'] = '1';1125      process.argv = ['node', 'script.js'];1126      const argv = await parseArguments();1127      await loadCliConfig({}, argv);1128      expect(process.env['NODE_TLS_REJECT_UNAUTHORIZED']).toBe('0');1129      expect(errorSpy).toHaveBeenCalled();1130    });1131 1132    it('skips re-assignment and warning when NODE_TLS_REJECT_UNAUTHORIZED is already 0', async () => {1133      process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';1134      process.argv = ['node', 'script.js', '--insecure'];1135      const argv = await parseArguments();1136      await loadCliConfig({}, argv);1137      expect(process.env['NODE_TLS_REJECT_UNAUTHORIZED']).toBe('0');1138      expect(errorSpy).not.toHaveBeenCalled();1139    });1140  });1141 1142  it('should propagate runtime sleep prevention setting', async () => {1143    process.argv = ['node', 'script.js'];1144    const argv = await parseArguments();1145    const config = await loadCliConfig(1146      {1147        general: {1148          preventSystemSleep: false,1149        },1150      },1151      argv,1152    );1153 1154    expect(config.getPreventSystemSleepEnabled()).toBe(false);1155  });1156 1157  it('should propagate artifact auto-open setting', async () => {1158    process.argv = ['node', 'script.js'];1159    const argv = await parseArguments();1160    const config = await loadCliConfig(1161      {1162        artifact: {1163          autoOpen: false,1164        },1165      },1166      argv,1167    );1168 1169    expect(config.shouldAutoOpenArtifact()).toBe(false);1170  });1171 1172  it('places session-injected (ACP/IDE) MCP servers at the top precedence tier', async () => {1173    process.argv = ['node', 'script.js'];1174    const argv = await parseArguments();1175    const settings: Settings = {1176      mcpServers: {1177        shared: { command: 'settings-cmd' },1178        'settings-only': { command: 'settings-only-cmd' },1179      },1180    };1181    const sessionMcpServers = {1182      shared: new ServerConfig.MCPServerConfig('session-cmd'),1183      'ide-only': new ServerConfig.MCPServerConfig('ide-cmd'),1184    };1185 1186    const config = await loadCliConfig(1187      settings,1188      argv,1189      process.cwd(),1190      undefined,1191      undefined,1192      undefined,1193      sessionMcpServers,1194    );1195 1196    const servers = config.getMcpServers() ?? {};1197    // Session source wins a name clash with settings.1198    expect(servers['shared'].command).toBe('session-cmd');1199    // Both session-only and settings-only servers survive.1200    expect(servers['ide-only'].command).toBe('ide-cmd');

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

basant307/AI_Governance_Project · CoolFace