2025-05-14 12:37:17 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-09-02 12:01:22 -04:00
import { describe , it , expect , vi , beforeEach , afterEach } from 'vitest' ;
2025-08-25 22:11:27 +02:00
import * as os from 'node:os' ;
import * as path from 'node:path' ;
2025-09-12 15:57:07 -04:00
import {
2025-10-24 13:20:17 -07:00
DEFAULT_FILE_FILTERING_OPTIONS ,
2025-09-18 08:41:12 -07:00
OutputFormat ,
2025-10-17 21:07:26 -04:00
SHELL_TOOL_NAME ,
2025-10-19 20:53:53 -04:00
WRITE_FILE_TOOL_NAME ,
EDIT_TOOL_NAME ,
2025-12-01 22:43:14 +05:30
WEB_FETCH_TOOL_NAME ,
2025-10-28 09:04:30 -07:00
type ExtensionLoader ,
2025-11-20 10:44:02 -08:00
debugLogger ,
2025-12-29 14:22:42 -05:00
ApprovalMode ,
2025-09-12 15:57:07 -04:00
} from '@google/gemini-cli-core' ;
2025-10-17 22:39:53 -07:00
import { loadCliConfig , parseArguments , type CliArgs } from './config.js' ;
2026-01-15 09:26:10 -08:00
import { type Settings , createTestMergedSettings } from './settings.js' ;
2025-06-25 05:41:11 -07:00
import * as ServerConfig from '@google/gemini-cli-core' ;
2026-01-15 09:26:10 -08:00
2025-08-13 11:06:31 -07:00
import { isWorkspaceTrusted } from './trustedFolders.js' ;
2025-10-28 09:04:30 -07:00
import { ExtensionManager } from './extension-manager.js' ;
2025-11-19 09:22:17 -07:00
import { RESUME_LATEST } from '../utils/sessionUtils.js' ;
2025-08-13 11:06:31 -07:00
vi . mock ( './trustedFolders.js' , ( ) = > ( {
2025-12-23 19:53:43 +05:30
isWorkspaceTrusted : vi.fn ( ( ) = > ( { isTrusted : true , source : 'file' } ) ) , // Default to trusted
2025-08-13 11:06:31 -07:00
} ) ) ;
2025-05-14 12:37:17 -07:00
2025-10-06 13:34:00 -06:00
vi . mock ( './sandboxConfig.js' , ( ) = > ( {
2025-12-23 19:53:43 +05:30
loadSandboxConfig : vi.fn ( async ( ) = > undefined ) ,
2025-10-06 13:34:00 -06:00
} ) ) ;
2026-01-21 12:38:20 -05:00
vi . mock ( '../commands/utils.js' , ( ) = > ( {
exitCli : vi.fn ( ) ,
} ) ) ;
2025-08-20 10:55:47 +09:00
vi . mock ( 'fs' , async ( importOriginal ) = > {
const actualFs = await importOriginal < typeof import ( 'fs' ) > ( ) ;
2025-08-25 22:11:27 +02:00
const pathMod = await import ( 'node:path' ) ;
2025-11-06 20:01:30 -08:00
const mockHome = pathMod . resolve ( pathMod . sep , 'mock' , 'home' , 'user' ) ;
2025-08-20 10:55:47 +09:00
const MOCK_CWD1 = process . cwd ( ) ;
const MOCK_CWD2 = pathMod . resolve ( pathMod . sep , 'home' , 'user' , 'project' ) ;
const mockPaths = new Set ( [
MOCK_CWD1 ,
MOCK_CWD2 ,
pathMod . resolve ( pathMod . sep , 'cli' , 'path1' ) ,
pathMod . resolve ( pathMod . sep , 'settings' , 'path1' ) ,
pathMod . join ( mockHome , 'settings' , 'path2' ) ,
pathMod . join ( MOCK_CWD2 , 'cli' , 'path2' ) ,
pathMod . join ( MOCK_CWD2 , 'settings' , 'path3' ) ,
] ) ;
return {
. . . actualFs ,
2026-01-26 16:57:27 -05:00
mkdirSync : vi.fn ( ( p ) = > {
mockPaths . add ( p . toString ( ) ) ;
} ) ,
2025-08-20 10:55:47 +09:00
writeFileSync : vi.fn ( ) ,
existsSync : vi.fn ( ( p ) = > mockPaths . has ( p . toString ( ) ) ) ,
statSync : vi.fn ( ( p ) = > {
if ( mockPaths . has ( p . toString ( ) ) ) {
return { isDirectory : ( ) = > true } as unknown as import ( 'fs' ) . Stats ;
}
2025-12-12 17:43:43 -08:00
return actualFs . statSync ( p as unknown as string ) ;
2025-08-20 10:55:47 +09:00
} ) ,
realpathSync : vi.fn ( ( p ) = > p ) ,
} ;
} ) ;
2025-05-14 12:37:17 -07:00
vi . mock ( 'os' , async ( importOriginal ) = > {
const actualOs = await importOriginal < typeof os > ( ) ;
return {
. . . actualOs ,
2025-11-06 20:01:30 -08:00
homedir : vi.fn ( ( ) = > path . resolve ( path . sep , 'mock' , 'home' , 'user' ) ) ,
2025-05-14 12:37:17 -07:00
} ;
} ) ;
2025-06-16 11:12:42 -07:00
vi . mock ( 'open' , ( ) = > ( {
default : vi . fn ( ) ,
} ) ) ;
2025-05-30 22:18:01 +00:00
vi . mock ( 'read-package-up' , ( ) = > ( {
readPackageUp : vi.fn ( ( ) = >
Promise . resolve ( { packageJson : { version : 'test-version' } } ) ,
) ,
} ) ) ;
2025-06-25 05:41:11 -07:00
vi . mock ( '@google/gemini-cli-core' , async ( ) = > {
const actualServer = await vi . importActual < typeof ServerConfig > (
'@google/gemini-cli-core' ,
) ;
2025-05-30 22:18:01 +00:00
return {
. . . actualServer ,
2025-07-30 21:26:31 +00:00
IdeClient : {
2025-08-28 16:09:01 +02:00
getInstance : vi.fn ( ) . mockResolvedValue ( {
2025-07-30 21:26:31 +00:00
getConnectionStatus : vi.fn ( ) ,
initialize : vi.fn ( ) ,
shutdown : vi.fn ( ) ,
} ) ,
} ,
2025-05-30 22:18:01 +00:00
loadEnvironment : vi.fn ( ) ,
2025-06-13 20:25:59 -04:00
loadServerHierarchicalMemory : vi.fn (
2025-10-28 09:04:30 -07:00
(
cwd ,
dirs ,
debug ,
fileService ,
extensionLoader : ExtensionLoader ,
_maxDirs ,
) = > {
const extensionPaths = extensionLoader
. getExtensions ( )
. flatMap ( ( e ) = > e . contextFiles ) ;
return Promise . resolve ( {
memoryContent : extensionPaths.join ( ',' ) || '' ,
2025-06-13 20:25:59 -04:00
fileCount : extensionPaths?.length || 0 ,
2025-11-14 19:06:30 -08:00
filePaths : extensionPaths ,
2025-10-28 09:04:30 -07:00
} ) ;
} ,
2025-05-30 22:18:01 +00:00
) ,
2025-07-20 00:55:33 -07:00
DEFAULT_MEMORY_FILE_FILTERING_OPTIONS : {
respectGitIgnore : false ,
respectGeminiIgnore : true ,
2026-01-27 17:19:13 -08:00
customIgnoreFilePaths : [ ] ,
2025-07-20 00:55:33 -07:00
} ,
DEFAULT_FILE_FILTERING_OPTIONS : {
respectGitIgnore : true ,
respectGeminiIgnore : true ,
2026-01-27 17:19:13 -08:00
customIgnoreFilePaths : [ ] ,
2025-07-20 00:55:33 -07:00
} ,
2026-01-04 00:19:00 -05:00
createPolicyEngineConfig : vi.fn ( async ( ) = > ( {
rules : [ ] ,
checkers : [ ] ,
defaultDecision : ServerConfig.PolicyDecision.ASK_USER ,
approvalMode : ServerConfig.ApprovalMode.DEFAULT ,
} ) ) ,
2025-05-30 22:18:01 +00:00
} ;
} ) ;
2026-01-21 09:58:23 -08:00
vi . mock ( './extension-manager.js' , ( ) = > {
const ExtensionManager = vi . fn ( ) ;
ExtensionManager . prototype . loadExtensions = vi . fn ( ) ;
ExtensionManager . prototype . getExtensions = vi . fn ( ) . mockReturnValue ( [ ] ) ;
return { ExtensionManager } ;
} ) ;
2025-10-28 09:04:30 -07:00
2025-10-27 16:31:01 -07:00
// Global setup to ensure clean environment for all tests in this file
const originalArgv = process . argv ;
const originalGeminiModel = process . env [ 'GEMINI_MODEL' ] ;
2025-07-11 16:52:56 -07:00
2025-10-27 16:31:01 -07:00
beforeEach ( ( ) = > {
delete process . env [ 'GEMINI_MODEL' ] ;
2026-01-21 09:58:23 -08:00
// Restore ExtensionManager mocks by re-assigning them
ExtensionManager . prototype . getExtensions = vi . fn ( ) . mockReturnValue ( [ ] ) ;
ExtensionManager . prototype . loadExtensions = vi
. fn ( )
. mockResolvedValue ( undefined ) ;
2025-10-27 16:31:01 -07:00
} ) ;
2025-07-11 16:52:56 -07:00
2025-10-27 16:31:01 -07:00
afterEach ( ( ) = > {
process . argv = originalArgv ;
if ( originalGeminiModel !== undefined ) {
process . env [ 'GEMINI_MODEL' ] = originalGeminiModel ;
} else {
delete process . env [ 'GEMINI_MODEL' ] ;
}
} ) ;
describe ( 'parseArguments' , ( ) = > {
2025-11-20 20:57:59 -08:00
it . each ( [
{
description : 'long flags' ,
argv : [
'node' ,
'script.js' ,
'--prompt' ,
'test prompt' ,
'--prompt-interactive' ,
'interactive prompt' ,
] ,
} ,
{
description : 'short flags' ,
argv : [
'node' ,
'script.js' ,
'-p' ,
'test prompt' ,
'-i' ,
'interactive prompt' ,
] ,
} ,
] ) (
'should throw an error when using conflicting prompt flags ($description)' ,
async ( { argv } ) = > {
process . argv = argv ;
const mockExit = vi . spyOn ( process , 'exit' ) . mockImplementation ( ( ) = > {
throw new Error ( 'process.exit called' ) ;
} ) ;
const mockConsoleError = vi
. spyOn ( console , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2026-01-15 09:26:10 -08:00
await expect ( parseArguments ( createTestMergedSettings ( ) ) ) . rejects . toThrow (
2025-11-20 20:57:59 -08:00
'process.exit called' ,
) ;
expect ( mockConsoleError ) . toHaveBeenCalledWith (
expect . stringContaining (
'Cannot use both --prompt (-p) and --prompt-interactive (-i) together' ,
) ,
) ;
mockExit . mockRestore ( ) ;
mockConsoleError . mockRestore ( ) ;
} ,
) ;
2025-08-12 15:10:22 -07:00
2025-11-20 20:57:59 -08:00
it . each ( [
{
description : 'should allow --prompt without --prompt-interactive' ,
argv : [ 'node' , 'script.js' , '--prompt' , 'test prompt' ] ,
expected : { prompt : 'test prompt' , promptInteractive : undefined } ,
} ,
{
description : 'should allow --prompt-interactive without --prompt' ,
argv : [ 'node' , 'script.js' , '--prompt-interactive' , 'interactive prompt' ] ,
expected : { prompt : undefined , promptInteractive : 'interactive prompt' } ,
} ,
{
description : 'should allow -i flag as alias for --prompt-interactive' ,
argv : [ 'node' , 'script.js' , '-i' , 'interactive prompt' ] ,
expected : { prompt : undefined , promptInteractive : 'interactive prompt' } ,
} ,
] ) ( '$description' , async ( { argv , expected } ) = > {
process . argv = argv ;
2026-01-15 09:26:10 -08:00
const parsedArgs = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-11-20 20:57:59 -08:00
expect ( parsedArgs . prompt ) . toBe ( expected . prompt ) ;
expect ( parsedArgs . promptInteractive ) . toBe ( expected . promptInteractive ) ;
2025-08-12 15:10:22 -07:00
} ) ;
2025-11-20 20:57:59 -08:00
describe ( 'positional arguments and @commands' , ( ) = > {
it . each ( [
{
description :
'should convert positional query argument to prompt by default' ,
argv : [ 'node' , 'script.js' , 'Hi Gemini' ] ,
expectedQuery : 'Hi Gemini' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description :
'should map @path to prompt (one-shot) when it starts with @' ,
argv : [ 'node' , 'script.js' , '@path ./file.md' ] ,
expectedQuery : '@path ./file.md' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description :
'should map @path to prompt even when config flags are present' ,
argv : [
'node' ,
'script.js' ,
'@path' ,
'./file.md' ,
'--model' ,
'gemini-2.5-pro' ,
] ,
expectedQuery : '@path ./file.md' ,
expectedModel : 'gemini-2.5-pro' ,
debug : false ,
} ,
{
description :
'maps unquoted positional @path + arg to prompt (one-shot)' ,
argv : [ 'node' , 'script.js' , '@path' , './file.md' ] ,
expectedQuery : '@path ./file.md' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description :
'should handle multiple @path arguments in a single command (one-shot)' ,
argv : [
'node' ,
'script.js' ,
'@path' ,
'./file1.md' ,
'@path' ,
'./file2.md' ,
] ,
expectedQuery : '@path ./file1.md @path ./file2.md' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description :
'should handle mixed quoted and unquoted @path arguments (one-shot)' ,
argv : [
'node' ,
'script.js' ,
'@path ./file1.md' ,
'@path' ,
'./file2.md' ,
'additional text' ,
] ,
expectedQuery : '@path ./file1.md @path ./file2.md additional text' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description : 'should map @path to prompt with ambient flags (debug)' ,
argv : [ 'node' , 'script.js' , '@path' , './file.md' , '--debug' ] ,
expectedQuery : '@path ./file.md' ,
expectedModel : undefined ,
debug : true ,
} ,
{
description : 'should map @include to prompt (one-shot)' ,
argv : [ 'node' , 'script.js' , '@include src/' ] ,
expectedQuery : '@include src/' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description : 'should map @search to prompt (one-shot)' ,
argv : [ 'node' , 'script.js' , '@search pattern' ] ,
expectedQuery : '@search pattern' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description : 'should map @web to prompt (one-shot)' ,
argv : [ 'node' , 'script.js' , '@web query' ] ,
expectedQuery : '@web query' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description : 'should map @git to prompt (one-shot)' ,
argv : [ 'node' , 'script.js' , '@git status' ] ,
expectedQuery : '@git status' ,
expectedModel : undefined ,
debug : false ,
} ,
{
description : 'should handle @command with leading whitespace' ,
argv : [ 'node' , 'script.js' , ' @path ./file.md' ] ,
expectedQuery : ' @path ./file.md' ,
expectedModel : undefined ,
debug : false ,
} ,
] ) (
'$description' ,
async ( { argv , expectedQuery , expectedModel , debug } ) = > {
process . argv = argv ;
2026-01-15 09:26:10 -08:00
const parsedArgs = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-11-20 20:57:59 -08:00
expect ( parsedArgs . query ) . toBe ( expectedQuery ) ;
expect ( parsedArgs . prompt ) . toBe ( expectedQuery ) ;
expect ( parsedArgs . promptInteractive ) . toBeUndefined ( ) ;
if ( expectedModel ) {
expect ( parsedArgs . model ) . toBe ( expectedModel ) ;
}
if ( debug ) {
expect ( parsedArgs . debug ) . toBe ( true ) ;
}
} ,
2025-08-12 15:10:22 -07:00
) ;
2026-01-23 05:08:53 +05:30
it ( 'should include a startup message when converting positional query to interactive prompt' , async ( ) = > {
const originalIsTTY = process . stdin . isTTY ;
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' , 'hello' ] ;
try {
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
expect ( argv . startupMessages ) . toContain (
'Positional arguments now default to interactive mode. To run in non-interactive mode, use the --prompt (-p) flag.' ,
) ;
} finally {
process . stdin . isTTY = originalIsTTY ;
}
} ) ;
2025-08-12 15:10:22 -07:00
} ) ;
2025-11-20 20:57:59 -08:00
it . each ( [
{
description : 'long flags' ,
argv : [ 'node' , 'script.js' , '--yolo' , '--approval-mode' , 'default' ] ,
} ,
{
description : 'short flags' ,
argv : [ 'node' , 'script.js' , '-y' , '--approval-mode' , 'yolo' ] ,
} ,
] ) (
'should throw an error when using conflicting yolo/approval-mode flags ($description)' ,
async ( { argv } ) = > {
process . argv = argv ;
const mockExit = vi . spyOn ( process , 'exit' ) . mockImplementation ( ( ) = > {
throw new Error ( 'process.exit called' ) ;
} ) ;
const mockConsoleError = vi
. spyOn ( console , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2026-01-15 09:26:10 -08:00
await expect ( parseArguments ( createTestMergedSettings ( ) ) ) . rejects . toThrow (
2025-11-20 20:57:59 -08:00
'process.exit called' ,
) ;
expect ( mockConsoleError ) . toHaveBeenCalledWith (
expect . stringContaining (
'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.' ,
) ,
) ;
mockExit . mockRestore ( ) ;
mockConsoleError . mockRestore ( ) ;
} ,
) ;
2025-08-12 15:10:22 -07:00
2025-11-20 20:57:59 -08:00
it . each ( [
{
description : 'should allow --approval-mode without --yolo' ,
argv : [ 'node' , 'script.js' , '--approval-mode' , 'auto_edit' ] ,
expected : { approvalMode : 'auto_edit' , yolo : false } ,
} ,
{
description : 'should allow --yolo without --approval-mode' ,
argv : [ 'node' , 'script.js' , '--yolo' ] ,
expected : { approvalMode : undefined , yolo : true } ,
} ,
] ) ( '$description' , async ( { argv , expected } ) = > {
process . argv = argv ;
2026-01-15 09:26:10 -08:00
const parsedArgs = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-11-20 20:57:59 -08:00
expect ( parsedArgs . approvalMode ) . toBe ( expected . approvalMode ) ;
expect ( parsedArgs . yolo ) . toBe ( expected . yolo ) ;
2025-08-12 15:10:22 -07:00
} ) ;
it ( 'should reject invalid --approval-mode values' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'invalid' ] ;
const mockExit = vi . spyOn ( process , 'exit' ) . mockImplementation ( ( ) = > {
throw new Error ( 'process.exit called' ) ;
} ) ;
const mockConsoleError = vi
. spyOn ( console , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2025-11-20 10:44:02 -08:00
const debugErrorSpy = vi
. spyOn ( debugLogger , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2025-08-12 15:10:22 -07:00
2026-01-15 09:26:10 -08:00
await expect ( parseArguments ( createTestMergedSettings ( ) ) ) . rejects . toThrow (
2025-08-25 17:02:10 +00:00
'process.exit called' ,
) ;
2025-08-12 15:10:22 -07:00
2025-11-20 10:44:02 -08:00
expect ( debugErrorSpy ) . toHaveBeenCalledWith (
2025-08-12 15:10:22 -07:00
expect . stringContaining ( 'Invalid values:' ) ,
) ;
2025-11-20 10:44:02 -08:00
expect ( mockConsoleError ) . toHaveBeenCalled ( ) ;
2025-08-12 15:10:22 -07:00
mockExit . mockRestore ( ) ;
mockConsoleError . mockRestore ( ) ;
2025-11-20 10:44:02 -08:00
debugErrorSpy . mockRestore ( ) ;
2025-08-12 15:10:22 -07:00
} ) ;
2025-09-19 14:50:59 -07:00
2025-11-25 10:53:17 -07:00
it ( 'should allow resuming a session without prompt argument in non-interactive mode (expecting stdin)' , async ( ) = > {
2025-11-10 18:31:00 -07:00
const originalIsTTY = process . stdin . isTTY ;
process . stdin . isTTY = false ;
process . argv = [ 'node' , 'script.js' , '--resume' , 'session-id' ] ;
try {
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-11-25 10:53:17 -07:00
expect ( argv . resume ) . toBe ( 'session-id' ) ;
2025-11-10 18:31:00 -07:00
} finally {
process . stdin . isTTY = originalIsTTY ;
}
} ) ;
2025-11-19 09:22:17 -07:00
it ( 'should return RESUME_LATEST constant when --resume is passed without a value' , async ( ) = > {
const originalIsTTY = process . stdin . isTTY ;
process . stdin . isTTY = true ; // Make it interactive to avoid validation error
process . argv = [ 'node' , 'script.js' , '--resume' ] ;
try {
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-11-19 09:22:17 -07:00
expect ( argv . resume ) . toBe ( RESUME_LATEST ) ;
expect ( argv . resume ) . toBe ( 'latest' ) ;
} finally {
process . stdin . isTTY = originalIsTTY ;
}
} ) ;
2025-09-19 14:50:59 -07:00
it ( 'should support comma-separated values for --allowed-tools' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--allowed-tools' ,
'read_file,ShellTool(git status)' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-19 14:50:59 -07:00
expect ( argv . allowedTools ) . toEqual ( [ 'read_file' , 'ShellTool(git status)' ] ) ;
} ) ;
2025-09-20 12:49:50 -07:00
it ( 'should support comma-separated values for --allowed-mcp-server-names' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--allowed-mcp-server-names' ,
'server1,server2' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-20 12:49:50 -07:00
expect ( argv . allowedMcpServerNames ) . toEqual ( [ 'server1' , 'server2' ] ) ;
} ) ;
it ( 'should support comma-separated values for --extensions' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--extensions' , 'ext1,ext2' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-20 12:49:50 -07:00
expect ( argv . extensions ) . toEqual ( [ 'ext1' , 'ext2' ] ) ;
} ) ;
2025-10-15 15:00:23 -07:00
it ( 'should correctly parse positional arguments when flags with arguments are present' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
2025-10-16 16:54:59 -07:00
'--model' ,
'test-model-string' ,
2025-10-15 15:00:23 -07:00
'my-positional-arg' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-16 16:54:59 -07:00
expect ( argv . model ) . toBe ( 'test-model-string' ) ;
2025-10-15 15:00:23 -07:00
expect ( argv . query ) . toBe ( 'my-positional-arg' ) ;
} ) ;
it ( 'should handle long positional prompts with multiple flags' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'-e' ,
'none' ,
'--approval-mode=auto_edit' ,
'--allowed-tools=ShellTool' ,
'--allowed-tools=ShellTool(whoami)' ,
'--allowed-tools=ShellTool(wc)' ,
'Use whoami to write a poem in file poem.md about my username in pig latin and use wc to tell me how many lines are in the poem you wrote.' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-15 15:00:23 -07:00
expect ( argv . extensions ) . toEqual ( [ 'none' ] ) ;
expect ( argv . approvalMode ) . toBe ( 'auto_edit' ) ;
expect ( argv . allowedTools ) . toEqual ( [
'ShellTool' ,
'ShellTool(whoami)' ,
'ShellTool(wc)' ,
] ) ;
expect ( argv . query ) . toBe (
'Use whoami to write a poem in file poem.md about my username in pig latin and use wc to tell me how many lines are in the poem you wrote.' ,
) ;
} ) ;
2026-01-21 12:38:20 -05:00
it ( 'should set isCommand to true for mcp command' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , 'mcp' , 'list' ] ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
expect ( argv . isCommand ) . toBe ( true ) ;
} ) ;
it ( 'should set isCommand to true for extensions command' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , 'extensions' , 'list' ] ;
// Extensions command uses experimental settings
const settings = createTestMergedSettings ( {
experimental : { extensionManagement : true } ,
} ) ;
const argv = await parseArguments ( settings ) ;
expect ( argv . isCommand ) . toBe ( true ) ;
} ) ;
it ( 'should set isCommand to true for skills command' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , 'skills' , 'list' ] ;
// Skills command enabled by default or via experimental
const settings = createTestMergedSettings ( {
2026-01-27 23:56:04 -05:00
skills : { enabled : true } ,
2026-01-21 12:38:20 -05:00
} ) ;
const argv = await parseArguments ( settings ) ;
expect ( argv . isCommand ) . toBe ( true ) ;
} ) ;
it ( 'should set isCommand to true for hooks command' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , 'hooks' , 'migrate' ] ;
2026-01-30 10:15:48 -05:00
// Hooks command enabled via hooksConfig settings
2026-01-21 12:38:20 -05:00
const settings = createTestMergedSettings ( {
2026-01-30 10:15:48 -05:00
hooksConfig : { enabled : true } ,
2026-01-21 12:38:20 -05:00
} ) ;
const argv = await parseArguments ( settings ) ;
expect ( argv . isCommand ) . toBe ( true ) ;
} ) ;
2025-07-11 16:52:56 -07:00
} ) ;
2025-05-30 22:18:01 +00:00
describe ( 'loadCliConfig' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
2025-06-22 09:26:48 -05:00
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-05-30 22:18:01 +00:00
} ) ;
afterEach ( ( ) = > {
2025-08-17 12:43:21 -04:00
vi . unstubAllEnvs ( ) ;
2025-05-30 22:18:01 +00:00
vi . restoreAllMocks ( ) ;
} ) ;
2025-08-23 12:43:03 +08:00
describe ( 'Proxy configuration' , ( ) = > {
const originalProxyEnv : { [ key : string ] : string | undefined } = { } ;
const proxyEnvVars = [
'HTTP_PROXY' ,
'HTTPS_PROXY' ,
'http_proxy' ,
'https_proxy' ,
] ;
beforeEach ( ( ) = > {
for ( const key of proxyEnvVars ) {
originalProxyEnv [ key ] = process . env [ key ] ;
delete process . env [ key ] ;
}
} ) ;
afterEach ( ( ) = > {
for ( const key of proxyEnvVars ) {
if ( originalProxyEnv [ key ] ) {
process . env [ key ] = originalProxyEnv [ key ] ;
} else {
delete process . env [ key ] ;
}
}
} ) ;
it ( ` should leave proxy to empty by default ` , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-23 12:43:03 +08:00
expect ( config . getProxy ( ) ) . toBeFalsy ( ) ;
} ) ;
2025-07-18 02:57:37 +08:00
2025-08-23 12:43:03 +08:00
const proxy_url = 'http://localhost:7890' ;
const testCases = [
{
input : {
env_name : 'https_proxy' ,
proxy_url ,
} ,
expected : proxy_url ,
2025-07-18 02:57:37 +08:00
} ,
2025-08-23 12:43:03 +08:00
{
input : {
env_name : 'http_proxy' ,
proxy_url ,
} ,
expected : proxy_url ,
2025-07-18 02:57:37 +08:00
} ,
2025-08-23 12:43:03 +08:00
{
input : {
env_name : 'HTTPS_PROXY' ,
proxy_url ,
} ,
expected : proxy_url ,
2025-07-18 02:57:37 +08:00
} ,
2025-08-23 12:43:03 +08:00
{
input : {
env_name : 'HTTP_PROXY' ,
proxy_url ,
} ,
expected : proxy_url ,
2025-07-18 02:57:37 +08:00
} ,
2025-08-23 12:43:03 +08:00
] ;
testCases . forEach ( ( { input , expected } ) = > {
it ( ` should set proxy to ${ expected } according to environment variable [ ${ input . env_name } ] ` , async ( ) = > {
vi . stubEnv ( input . env_name , input . proxy_url ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-23 12:43:03 +08:00
expect ( config . getProxy ( ) ) . toBe ( expected ) ;
} ) ;
} ) ;
2025-07-18 02:57:37 +08:00
} ) ;
2025-10-24 13:20:17 -07:00
it ( 'should use default fileFilter options when unconfigured' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-10-24 13:20:17 -07:00
expect ( config . getFileFilteringRespectGitIgnore ( ) ) . toBe (
DEFAULT_FILE_FILTERING_OPTIONS . respectGitIgnore ,
) ;
expect ( config . getFileFilteringRespectGeminiIgnore ( ) ) . toBe (
DEFAULT_FILE_FILTERING_OPTIONS . respectGeminiIgnore ,
) ;
2026-01-27 17:19:13 -08:00
expect ( config . getCustomIgnoreFilePaths ( ) ) . toEqual (
DEFAULT_FILE_FILTERING_OPTIONS . customIgnoreFilePaths ,
) ;
2025-12-29 14:22:42 -05:00
expect ( config . getApprovalMode ( ) ) . toBe ( ApprovalMode . DEFAULT ) ;
2025-12-02 16:05:54 -08:00
} ) ;
2026-01-21 12:38:20 -05:00
it ( 'should be non-interactive when isCommand is set' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , 'mcp' , 'list' ] ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
argv . isCommand = true ; // explicitly set it as if middleware ran (it does in parseArguments but we want to be sure for this isolated test if we were mocking argv)
// reset tty for this test
process . stdin . isTTY = true ;
const settings = createTestMergedSettings ( ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . isInteractive ( ) ) . toBe ( false ) ;
} ) ;
2025-05-30 22:18:01 +00:00
} ) ;
2025-05-14 12:37:17 -07:00
describe ( 'Hierarchical Memory Loading (config.ts) - Placeholder Suite' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
2026-01-21 09:58:23 -08:00
// Restore ExtensionManager mocks that were reset
ExtensionManager . prototype . getExtensions = vi . fn ( ) . mockReturnValue ( [ ] ) ;
ExtensionManager . prototype . loadExtensions = vi
. fn ( )
. mockResolvedValue ( undefined ) ;
2025-06-22 09:26:48 -05:00
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-05-14 12:37:17 -07:00
// Other common mocks would be reset here.
} ) ;
afterEach ( ( ) = > {
vi . restoreAllMocks ( ) ;
} ) ;
2025-06-11 13:34:35 -07:00
it ( 'should pass extension context file paths to loadServerHierarchicalMemory' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [
2025-06-11 13:34:35 -07:00
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext1' ,
2025-10-08 07:31:41 -07:00
name : 'ext1' ,
2025-10-21 16:55:16 -04:00
id : 'ext1-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
2025-06-13 13:57:00 -07:00
contextFiles : [ '/path/to/ext1/GEMINI.md' ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-06-11 13:34:35 -07:00
} ,
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext2' ,
2025-10-08 07:31:41 -07:00
name : 'ext2' ,
2025-10-21 16:55:16 -04:00
id : 'ext2-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
2025-06-13 13:57:00 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-06-11 13:34:35 -07:00
} ,
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext3' ,
2025-10-08 07:31:41 -07:00
name : 'ext3' ,
2025-10-21 16:55:16 -04:00
id : 'ext3-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
2025-06-13 13:57:00 -07:00
contextFiles : [
'/path/to/ext3/context1.md' ,
'/path/to/ext3/context2.md' ,
] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-06-11 13:34:35 -07:00
} ,
2025-10-28 09:04:30 -07:00
] ) ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
await loadCliConfig ( settings , 'session-id' , argv ) ;
2025-06-11 13:34:35 -07:00
expect ( ServerConfig . loadServerHierarchicalMemory ) . toHaveBeenCalledWith (
expect . any ( String ) ,
2025-08-06 02:01:01 +09:00
[ ] ,
2025-06-11 13:34:35 -07:00
false ,
2025-06-13 20:25:59 -04:00
expect . any ( Object ) ,
2025-10-28 09:04:30 -07:00
expect . any ( ExtensionManager ) ,
2025-08-29 14:12:36 -04:00
true ,
2025-07-31 22:06:50 +05:30
'tree' ,
2026-01-15 09:26:10 -08:00
expect . objectContaining ( {
respectGitIgnore : true ,
2025-07-20 00:55:33 -07:00
respectGeminiIgnore : true ,
2026-01-15 09:26:10 -08:00
} ) ,
200 , // maxDirs
2025-06-11 13:34:35 -07:00
) ;
2025-05-14 12:37:17 -07:00
} ) ;
2026-01-21 09:58:23 -08:00
it ( 'should pass includeDirectories to loadServerHierarchicalMemory when loadMemoryFromIncludeDirectories is true' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
const includeDir = path . resolve ( path . sep , 'path' , 'to' , 'include' ) ;
const settings = createTestMergedSettings ( {
context : {
includeDirectories : [ includeDir ] ,
loadMemoryFromIncludeDirectories : true ,
} ,
} ) ;
const argv = await parseArguments ( settings ) ;
await loadCliConfig ( settings , 'session-id' , argv ) ;
expect ( ServerConfig . loadServerHierarchicalMemory ) . toHaveBeenCalledWith (
expect . any ( String ) ,
[ includeDir ] ,
false ,
expect . any ( Object ) ,
expect . any ( ExtensionManager ) ,
true ,
'tree' ,
expect . objectContaining ( {
respectGitIgnore : true ,
respectGeminiIgnore : true ,
} ) ,
200 ,
) ;
} ) ;
it ( 'should NOT pass includeDirectories to loadServerHierarchicalMemory when loadMemoryFromIncludeDirectories is false' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
const settings = createTestMergedSettings ( {
context : {
includeDirectories : [ '/path/to/include' ] ,
loadMemoryFromIncludeDirectories : false ,
} ,
} ) ;
const argv = await parseArguments ( settings ) ;
await loadCliConfig ( settings , 'session-id' , argv ) ;
expect ( ServerConfig . loadServerHierarchicalMemory ) . toHaveBeenCalledWith (
expect . any ( String ) ,
[ ] ,
false ,
expect . any ( Object ) ,
expect . any ( ExtensionManager ) ,
true ,
'tree' ,
expect . objectContaining ( {
respectGitIgnore : true ,
respectGeminiIgnore : true ,
} ) ,
200 ,
) ;
} ) ;
2025-05-14 12:37:17 -07:00
} ) ;
2025-06-13 14:51:29 -07:00
describe ( 'mergeMcpServers' , ( ) = > {
it ( 'should not modify the original settings object' , async ( ) = > {
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
2025-06-13 14:51:29 -07:00
mcpServers : {
'test-server' : {
url : 'http://localhost:8080' ,
} ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [
2025-06-13 14:51:29 -07:00
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext1' ,
2025-10-08 07:31:41 -07:00
name : 'ext1' ,
2025-10-21 16:55:16 -04:00
id : 'ext1-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
mcpServers : {
'ext1-server' : {
url : 'http://localhost:8081' ,
2025-06-13 14:51:29 -07:00
} ,
} ,
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-06-13 14:51:29 -07:00
} ,
2025-10-28 09:04:30 -07:00
] ) ;
2025-06-13 14:51:29 -07:00
const originalSettings = JSON . parse ( JSON . stringify ( settings ) ) ;
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-06-13 14:51:29 -07:00
expect ( settings ) . toEqual ( originalSettings ) ;
} ) ;
} ) ;
2025-07-01 16:13:46 -07:00
describe ( 'mergeExcludeTools' , ( ) = > {
2025-11-07 12:18:35 -08:00
const defaultExcludes = new Set ( [
2025-10-19 20:53:53 -04:00
SHELL_TOOL_NAME ,
EDIT_TOOL_NAME ,
WRITE_FILE_TOOL_NAME ,
2025-12-01 22:43:14 +05:30
WEB_FETCH_TOOL_NAME ,
2025-11-07 12:18:35 -08:00
] ) ;
2025-08-07 14:19:06 -07:00
const originalIsTTY = process . stdin . isTTY ;
beforeEach ( ( ) = > {
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-07 14:19:06 -07:00
process . stdin . isTTY = true ;
} ) ;
afterEach ( ( ) = > {
process . stdin . isTTY = originalIsTTY ;
} ) ;
2025-07-01 16:13:46 -07:00
it ( 'should merge excludeTools from settings and extensions' , async ( ) = > {
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
tools : { exclude : [ 'tool1' , 'tool2' ] } ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [
2025-07-01 16:13:46 -07:00
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext1' ,
2025-10-08 07:31:41 -07:00
name : 'ext1' ,
2025-10-21 16:55:16 -04:00
id : 'ext1-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
excludeTools : [ 'tool3' , 'tool4' ] ,
2025-07-01 16:13:46 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-07-01 16:13:46 -07:00
} ,
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext2' ,
2025-10-08 07:31:41 -07:00
name : 'ext2' ,
2025-10-21 16:55:16 -04:00
id : 'ext2-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
excludeTools : [ 'tool5' ] ,
2025-07-01 16:13:46 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-07-01 16:13:46 -07:00
} ,
2025-10-28 09:04:30 -07:00
] ) ;
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-07-11 16:52:56 -07:00
const config = await loadCliConfig (
settings ,
2025-10-28 09:04:30 -07:00
2025-07-11 16:52:56 -07:00
'test-session' ,
argv ,
) ;
2025-07-01 16:13:46 -07:00
expect ( config . getExcludeTools ( ) ) . toEqual (
2025-11-07 12:18:35 -08:00
new Set ( [ 'tool1' , 'tool2' , 'tool3' , 'tool4' , 'tool5' ] ) ,
2025-07-01 16:13:46 -07:00
) ;
expect ( config . getExcludeTools ( ) ) . toHaveLength ( 5 ) ;
} ) ;
it ( 'should handle overlapping excludeTools between settings and extensions' , async ( ) = > {
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
tools : { exclude : [ 'tool1' , 'tool2' ] } ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [
2025-07-01 16:13:46 -07:00
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext1' ,
2025-10-08 07:31:41 -07:00
name : 'ext1' ,
2025-10-21 16:55:16 -04:00
id : 'ext1-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
excludeTools : [ 'tool2' , 'tool3' ] ,
2025-07-01 16:13:46 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-07-01 16:13:46 -07:00
} ,
2025-10-28 09:04:30 -07:00
] ) ;
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-07-01 16:13:46 -07:00
expect ( config . getExcludeTools ( ) ) . toEqual (
2025-11-07 12:18:35 -08:00
new Set ( [ 'tool1' , 'tool2' , 'tool3' ] ) ,
2025-07-01 16:13:46 -07:00
) ;
expect ( config . getExcludeTools ( ) ) . toHaveLength ( 3 ) ;
} ) ;
it ( 'should handle overlapping excludeTools between extensions' , async ( ) = > {
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
tools : { exclude : [ 'tool1' ] } ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [
2025-07-01 16:13:46 -07:00
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext1' ,
2025-10-08 07:31:41 -07:00
name : 'ext1' ,
2025-10-21 16:55:16 -04:00
id : 'ext1-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
excludeTools : [ 'tool2' , 'tool3' ] ,
2025-07-01 16:13:46 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-07-01 16:13:46 -07:00
} ,
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext2' ,
2025-10-08 07:31:41 -07:00
name : 'ext2' ,
2025-10-21 16:55:16 -04:00
id : 'ext2-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
excludeTools : [ 'tool3' , 'tool4' ] ,
2025-07-01 16:13:46 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-07-01 16:13:46 -07:00
} ,
2025-10-28 09:04:30 -07:00
] ) ;
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-07-01 16:13:46 -07:00
expect ( config . getExcludeTools ( ) ) . toEqual (
2025-11-07 12:18:35 -08:00
new Set ( [ 'tool1' , 'tool2' , 'tool3' , 'tool4' ] ) ,
2025-07-01 16:13:46 -07:00
) ;
expect ( config . getExcludeTools ( ) ) . toHaveLength ( 4 ) ;
} ) ;
2025-08-07 14:19:06 -07:00
it ( 'should return an empty array when no excludeTools are specified and it is interactive' , async ( ) = > {
process . stdin . isTTY = true ;
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( ) ;
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-07 12:18:35 -08:00
expect ( config . getExcludeTools ( ) ) . toEqual ( new Set ( [ ] ) ) ;
2025-07-01 16:13:46 -07:00
} ) ;
2025-08-07 14:19:06 -07:00
it ( 'should return default excludes when no excludeTools are specified and it is not interactive' , async ( ) = > {
process . stdin . isTTY = false ;
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( ) ;
2025-08-07 14:19:06 -07:00
process . argv = [ 'node' , 'script.js' , '-p' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-07 14:19:06 -07:00
expect ( config . getExcludeTools ( ) ) . toEqual ( defaultExcludes ) ;
} ) ;
2025-07-01 16:13:46 -07:00
it ( 'should handle settings with excludeTools but no extensions' , async ( ) = > {
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
tools : { exclude : [ 'tool1' , 'tool2' ] } ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-07 12:18:35 -08:00
expect ( config . getExcludeTools ( ) ) . toEqual ( new Set ( [ 'tool1' , 'tool2' ] ) ) ;
2025-07-01 16:13:46 -07:00
expect ( config . getExcludeTools ( ) ) . toHaveLength ( 2 ) ;
} ) ;
it ( 'should handle extensions with excludeTools but no settings' , async ( ) = > {
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [
2025-07-01 16:13:46 -07:00
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext' ,
2025-10-08 07:31:41 -07:00
name : 'ext1' ,
2025-10-21 16:55:16 -04:00
id : 'ext1-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
excludeTools : [ 'tool1' , 'tool2' ] ,
2025-07-01 16:13:46 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-07-01 16:13:46 -07:00
} ,
2025-10-28 09:04:30 -07:00
] ) ;
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-07 12:18:35 -08:00
expect ( config . getExcludeTools ( ) ) . toEqual ( new Set ( [ 'tool1' , 'tool2' ] ) ) ;
2025-07-01 16:13:46 -07:00
expect ( config . getExcludeTools ( ) ) . toHaveLength ( 2 ) ;
} ) ;
it ( 'should not modify the original settings object' , async ( ) = > {
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
tools : { exclude : [ 'tool1' ] } ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [
2025-07-01 16:13:46 -07:00
{
2025-08-23 13:35:00 +09:00
path : '/path/to/ext' ,
2025-10-08 07:31:41 -07:00
name : 'ext1' ,
2025-10-21 16:55:16 -04:00
id : 'ext1-id' ,
2025-10-08 07:31:41 -07:00
version : '1.0.0' ,
excludeTools : [ 'tool2' ] ,
2025-07-01 16:13:46 -07:00
contextFiles : [ ] ,
2025-10-08 07:31:41 -07:00
isActive : true ,
2025-07-01 16:13:46 -07:00
} ,
2025-10-28 09:04:30 -07:00
] ) ;
2025-07-01 16:13:46 -07:00
const originalSettings = JSON . parse ( JSON . stringify ( settings ) ) ;
2025-07-11 16:52:56 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-07-01 16:13:46 -07:00
expect ( settings ) . toEqual ( originalSettings ) ;
} ) ;
} ) ;
2025-07-07 09:45:58 -07:00
2025-08-12 15:10:22 -07:00
describe ( 'Approval mode tool exclusion logic' , ( ) = > {
const originalIsTTY = process . stdin . isTTY ;
beforeEach ( ( ) = > {
process . stdin . isTTY = false ; // Ensure non-interactive mode
2025-10-09 15:05:54 -07:00
vi . mocked ( isWorkspaceTrusted ) . mockReturnValue ( {
isTrusted : true ,
source : undefined ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-12 15:10:22 -07:00
} ) ;
afterEach ( ( ) = > {
process . stdin . isTTY = originalIsTTY ;
} ) ;
it ( 'should exclude all interactive tools in non-interactive mode with default approval mode' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '-p' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-12 15:10:22 -07:00
const excludedTools = config . getExcludeTools ( ) ;
2025-10-17 21:07:26 -04:00
expect ( excludedTools ) . toContain ( SHELL_TOOL_NAME ) ;
2025-10-19 20:53:53 -04:00
expect ( excludedTools ) . toContain ( EDIT_TOOL_NAME ) ;
expect ( excludedTools ) . toContain ( WRITE_FILE_TOOL_NAME ) ;
2025-08-12 15:10:22 -07:00
} ) ;
it ( 'should exclude all interactive tools in non-interactive mode with explicit default approval mode' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--approval-mode' ,
'default' ,
'-p' ,
'test' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-08-12 15:10:22 -07:00
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-12 15:10:22 -07:00
const excludedTools = config . getExcludeTools ( ) ;
2025-10-17 21:07:26 -04:00
expect ( excludedTools ) . toContain ( SHELL_TOOL_NAME ) ;
2025-10-19 20:53:53 -04:00
expect ( excludedTools ) . toContain ( EDIT_TOOL_NAME ) ;
expect ( excludedTools ) . toContain ( WRITE_FILE_TOOL_NAME ) ;
2025-08-12 15:10:22 -07:00
} ) ;
it ( 'should exclude only shell tools in non-interactive mode with auto_edit approval mode' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--approval-mode' ,
'auto_edit' ,
'-p' ,
'test' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-08-12 15:10:22 -07:00
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-12 15:10:22 -07:00
const excludedTools = config . getExcludeTools ( ) ;
2025-10-17 21:07:26 -04:00
expect ( excludedTools ) . toContain ( SHELL_TOOL_NAME ) ;
2025-10-19 20:53:53 -04:00
expect ( excludedTools ) . not . toContain ( EDIT_TOOL_NAME ) ;
expect ( excludedTools ) . not . toContain ( WRITE_FILE_TOOL_NAME ) ;
2025-08-12 15:10:22 -07:00
} ) ;
it ( 'should exclude no interactive tools in non-interactive mode with yolo approval mode' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--approval-mode' ,
'yolo' ,
'-p' ,
'test' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-08-12 15:10:22 -07:00
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-12 15:10:22 -07:00
const excludedTools = config . getExcludeTools ( ) ;
2025-10-17 21:07:26 -04:00
expect ( excludedTools ) . not . toContain ( SHELL_TOOL_NAME ) ;
2025-10-19 20:53:53 -04:00
expect ( excludedTools ) . not . toContain ( EDIT_TOOL_NAME ) ;
expect ( excludedTools ) . not . toContain ( WRITE_FILE_TOOL_NAME ) ;
2025-08-12 15:10:22 -07:00
} ) ;
2026-01-15 17:00:19 -05:00
it ( 'should exclude all interactive tools in non-interactive mode with plan approval mode' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--approval-mode' ,
'plan' ,
'-p' ,
'test' ,
] ;
const settings = createTestMergedSettings ( {
experimental : {
plan : true ,
} ,
} ) ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
const excludedTools = config . getExcludeTools ( ) ;
expect ( excludedTools ) . toContain ( SHELL_TOOL_NAME ) ;
expect ( excludedTools ) . toContain ( EDIT_TOOL_NAME ) ;
expect ( excludedTools ) . toContain ( WRITE_FILE_TOOL_NAME ) ;
} ) ;
2025-08-12 15:10:22 -07:00
it ( 'should exclude no interactive tools in non-interactive mode with legacy yolo flag' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' , '-p' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-08-12 15:10:22 -07:00
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-12 15:10:22 -07:00
const excludedTools = config . getExcludeTools ( ) ;
2025-10-17 21:07:26 -04:00
expect ( excludedTools ) . not . toContain ( SHELL_TOOL_NAME ) ;
2025-10-19 20:53:53 -04:00
expect ( excludedTools ) . not . toContain ( EDIT_TOOL_NAME ) ;
expect ( excludedTools ) . not . toContain ( WRITE_FILE_TOOL_NAME ) ;
2025-08-12 15:10:22 -07:00
} ) ;
it ( 'should not exclude interactive tools in interactive mode regardless of approval mode' , async ( ) = > {
process . stdin . isTTY = true ; // Interactive mode
const testCases = [
{ args : [ 'node' , 'script.js' ] } , // default
{ args : [ 'node' , 'script.js' , '--approval-mode' , 'default' ] } ,
{ args : [ 'node' , 'script.js' , '--approval-mode' , 'auto_edit' ] } ,
{ args : [ 'node' , 'script.js' , '--approval-mode' , 'yolo' ] } ,
{ args : [ 'node' , 'script.js' , '--yolo' ] } ,
] ;
for ( const testCase of testCases ) {
process . argv = testCase . args ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-08-12 15:10:22 -07:00
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-12 15:10:22 -07:00
const excludedTools = config . getExcludeTools ( ) ;
2025-10-17 21:07:26 -04:00
expect ( excludedTools ) . not . toContain ( SHELL_TOOL_NAME ) ;
2025-10-19 20:53:53 -04:00
expect ( excludedTools ) . not . toContain ( EDIT_TOOL_NAME ) ;
expect ( excludedTools ) . not . toContain ( WRITE_FILE_TOOL_NAME ) ;
2025-08-12 15:10:22 -07:00
}
} ) ;
it ( 'should merge approval mode exclusions with settings exclusions in auto_edit mode' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--approval-mode' ,
'auto_edit' ,
'-p' ,
'test' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
tools : { exclude : [ 'custom_tool' ] } ,
} ) ;
2025-08-12 15:10:22 -07:00
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-12 15:10:22 -07:00
const excludedTools = config . getExcludeTools ( ) ;
expect ( excludedTools ) . toContain ( 'custom_tool' ) ; // From settings
2025-10-17 21:07:26 -04:00
expect ( excludedTools ) . toContain ( SHELL_TOOL_NAME ) ; // From approval mode
2025-10-19 20:53:53 -04:00
expect ( excludedTools ) . not . toContain ( EDIT_TOOL_NAME ) ; // Should be allowed in auto_edit
expect ( excludedTools ) . not . toContain ( WRITE_FILE_TOOL_NAME ) ; // Should be allowed in auto_edit
2025-08-12 15:10:22 -07:00
} ) ;
2025-10-22 11:57:10 -07:00
it ( 'should throw an error if YOLO mode is attempted when disableYoloMode is true' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-10-22 11:57:10 -07:00
security : {
disableYoloMode : true ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-22 11:57:10 -07:00
2025-10-28 09:04:30 -07:00
await expect ( loadCliConfig ( settings , 'test-session' , argv ) ) . rejects . toThrow (
2026-01-30 13:05:22 -05:00
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli' ,
2025-10-22 11:57:10 -07:00
) ;
} ) ;
2025-08-12 15:10:22 -07:00
it ( 'should throw an error for invalid approval mode values in loadCliConfig' , async ( ) = > {
// Create a mock argv with an invalid approval mode that bypasses argument parsing validation
const invalidArgv : Partial < CliArgs > & { approvalMode : string } = {
approvalMode : 'invalid_mode' ,
promptInteractive : '' ,
prompt : '' ,
yolo : false ,
} ;
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( ) ;
2025-08-12 15:10:22 -07:00
await expect (
2025-10-28 09:04:30 -07:00
loadCliConfig ( settings , 'test-session' , invalidArgv as CliArgs ) ,
2025-08-12 15:10:22 -07:00
) . rejects . toThrow (
2026-01-15 17:00:19 -05:00
'Invalid approval mode: invalid_mode. Valid values are: yolo, auto_edit, plan, default' ,
2025-08-12 15:10:22 -07:00
) ;
} ) ;
} ) ;
2025-07-07 12:47:27 -07:00
describe ( 'loadCliConfig with allowed-mcp-server-names' , ( ) = > {
2025-07-07 09:45:58 -07:00
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-07-07 09:45:58 -07:00
} ) ;
afterEach ( ( ) = > {
2025-08-17 12:43:21 -04:00
vi . unstubAllEnvs ( ) ;
2025-07-07 09:45:58 -07:00
vi . restoreAllMocks ( ) ;
} ) ;
2026-01-15 09:26:10 -08:00
const baseSettings = createTestMergedSettings ( {
2025-07-07 09:45:58 -07:00
mcpServers : {
server1 : { url : 'http://localhost:8080' } ,
server2 : { url : 'http://localhost:8081' } ,
server3 : { url : 'http://localhost:8082' } ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-07-07 09:45:58 -07:00
it ( 'should allow all MCP servers if the flag is not provided' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( baseSettings , 'test-session' , argv ) ;
2025-07-07 09:45:58 -07:00
expect ( config . getMcpServers ( ) ) . toEqual ( baseSettings . mcpServers ) ;
} ) ;
it ( 'should allow only the specified MCP server' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
2025-07-07 12:47:27 -07:00
'--allowed-mcp-server-names' ,
2025-07-07 09:45:58 -07:00
'server1' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( baseSettings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'server1' ] ) ;
2025-07-07 09:45:58 -07:00
} ) ;
it ( 'should allow multiple specified MCP servers' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
2025-07-07 12:47:27 -07:00
'--allowed-mcp-server-names' ,
2025-07-09 11:38:38 -07:00
'server1' ,
'--allowed-mcp-server-names' ,
'server3' ,
2025-07-07 09:45:58 -07:00
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( baseSettings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'server1' , 'server3' ] ) ;
2025-07-07 09:45:58 -07:00
} ) ;
it ( 'should handle server names that do not exist' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
2025-07-07 12:47:27 -07:00
'--allowed-mcp-server-names' ,
2025-07-09 11:38:38 -07:00
'server1' ,
'--allowed-mcp-server-names' ,
'server4' ,
2025-07-07 09:45:58 -07:00
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( baseSettings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'server1' , 'server4' ] ) ;
2025-07-07 09:45:58 -07:00
} ) ;
2025-07-09 11:38:38 -07:00
it ( 'should allow no MCP servers if the flag is provided but empty' , async ( ) = > {
2025-07-07 12:47:27 -07:00
process . argv = [ 'node' , 'script.js' , '--allowed-mcp-server-names' , '' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( baseSettings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ '' ] ) ;
2025-07-07 09:45:58 -07:00
} ) ;
2025-07-15 20:45:24 +00:00
it ( 'should read allowMCPServers from settings' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-07-15 20:45:24 +00:00
. . . baseSettings ,
2025-08-27 18:39:45 -07:00
mcp : { allowed : [ 'server1' , 'server2' ] } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'server1' , 'server2' ] ) ;
2025-07-15 20:45:24 +00:00
} ) ;
it ( 'should read excludeMCPServers from settings' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-07-15 20:45:24 +00:00
. . . baseSettings ,
2025-08-27 18:39:45 -07:00
mcp : { excluded : [ 'server1' , 'server2' ] } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getBlockedMcpServers ( ) ) . toEqual ( [ 'server1' , 'server2' ] ) ;
2025-09-29 06:53:19 -07:00
} ) ;
2025-07-15 20:45:24 +00:00
2025-08-25 16:21:47 +02:00
it ( 'should override allowMCPServers with excludeMCPServers if overlapping' , async ( ) = > {
2025-07-15 20:45:24 +00:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-07-15 20:45:24 +00:00
. . . baseSettings ,
2025-08-27 18:39:45 -07:00
mcp : {
excluded : [ 'server1' ] ,
allowed : [ 'server1' , 'server2' ] ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'server1' , 'server2' ] ) ;
expect ( config . getBlockedMcpServers ( ) ) . toEqual ( [ 'server1' ] ) ;
2025-07-15 20:45:24 +00:00
} ) ;
2025-08-25 16:21:47 +02:00
it ( 'should prioritize mcp server flag if set' , async ( ) = > {
2025-07-15 20:45:24 +00:00
process . argv = [
'node' ,
'script.js' ,
'--allowed-mcp-server-names' ,
'server1' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-07-15 20:45:24 +00:00
. . . baseSettings ,
2025-08-27 18:39:45 -07:00
mcp : {
excluded : [ 'server1' ] ,
allowed : [ 'server2' ] ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'server1' ] ) ;
2025-07-15 20:45:24 +00:00
} ) ;
2025-08-27 18:39:45 -07:00
it ( 'should prioritize CLI flag over both allowed and excluded settings' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'--allowed-mcp-server-names' ,
'server2' ,
'--allowed-mcp-server-names' ,
'server3' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-08-27 18:39:45 -07:00
. . . baseSettings ,
mcp : {
allowed : [ 'server1' , 'server2' ] , // Should be ignored
excluded : [ 'server3' ] , // Should be ignored
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-04 07:51:18 -08:00
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'server2' , 'server3' ] ) ;
expect ( config . getBlockedMcpServers ( ) ) . toEqual ( [ ] ) ;
2025-08-27 18:39:45 -07:00
} ) ;
2025-07-07 09:45:58 -07:00
} ) ;
2025-07-08 12:57:34 -04:00
2025-08-04 16:41:58 -04:00
describe ( 'loadCliConfig model selection' , ( ) = > {
2025-10-28 09:04:30 -07:00
beforeEach ( ( ) = > {
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
} ) ;
afterEach ( ( ) = > {
vi . resetAllMocks ( ) ;
} ) ;
2025-08-04 16:41:58 -04:00
it ( 'selects a model from settings.json if provided' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-08-04 16:41:58 -04:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( {
2025-08-27 18:39:45 -07:00
model : {
2025-10-24 17:49:42 -04:00
name : 'gemini-2.5-pro' ,
2025-08-27 18:39:45 -07:00
} ,
2026-01-15 09:26:10 -08:00
} ) ,
2025-08-04 16:41:58 -04:00
'test-session' ,
argv ,
) ;
2025-10-24 17:49:42 -04:00
expect ( config . getModel ( ) ) . toBe ( 'gemini-2.5-pro' ) ;
2025-08-04 16:41:58 -04:00
} ) ;
it ( 'uses the default gemini model if nothing is set' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ; // No model set.
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-08-04 16:41:58 -04:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( {
2025-08-04 16:41:58 -04:00
// No model set.
2026-01-15 09:26:10 -08:00
} ) ,
2025-08-04 16:41:58 -04:00
'test-session' ,
argv ,
) ;
2025-12-17 09:43:21 -08:00
expect ( config . getModel ( ) ) . toBe ( 'auto-gemini-2.5' ) ;
2025-08-04 16:41:58 -04:00
} ) ;
2025-10-20 16:15:23 -07:00
it ( 'always prefers model from argv' , async ( ) = > {
2025-10-24 17:49:42 -04:00
process . argv = [ 'node' , 'script.js' , '--model' , 'gemini-2.5-flash-preview' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-08-04 16:41:58 -04:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( {
2025-08-27 18:39:45 -07:00
model : {
2025-10-24 17:49:42 -04:00
name : 'gemini-2.5-pro' ,
2025-08-27 18:39:45 -07:00
} ,
2026-01-15 09:26:10 -08:00
} ) ,
2025-08-04 16:41:58 -04:00
'test-session' ,
argv ,
) ;
2025-10-24 17:49:42 -04:00
expect ( config . getModel ( ) ) . toBe ( 'gemini-2.5-flash-preview' ) ;
2025-08-04 16:41:58 -04:00
} ) ;
2025-10-20 16:15:23 -07:00
it ( 'selects the model from argv if provided' , async ( ) = > {
2025-10-24 17:49:42 -04:00
process . argv = [ 'node' , 'script.js' , '--model' , 'gemini-2.5-flash-preview' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-08-04 16:41:58 -04:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( {
2025-08-04 16:41:58 -04:00
// No model provided via settings.
2026-01-15 09:26:10 -08:00
} ) ,
2025-08-04 16:41:58 -04:00
'test-session' ,
argv ,
) ;
2025-10-24 17:49:42 -04:00
expect ( config . getModel ( ) ) . toBe ( 'gemini-2.5-flash-preview' ) ;
2025-08-04 16:41:58 -04:00
} ) ;
2026-01-14 19:07:51 -05:00
it ( 'selects the default auto model if provided via auto alias' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--model' , 'auto' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2026-01-14 19:07:51 -05:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( {
2026-01-14 19:07:51 -05:00
// No model provided via settings.
2026-01-15 09:26:10 -08:00
} ) ,
2026-01-14 19:07:51 -05:00
'test-session' ,
argv ,
) ;
expect ( config . getModel ( ) ) . toBe ( 'auto-gemini-2.5' ) ;
} ) ;
2025-08-04 16:41:58 -04:00
} ) ;
2025-08-07 14:06:17 -07:00
describe ( 'loadCliConfig folderTrust' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-07 14:06:17 -07:00
} ) ;
afterEach ( ( ) = > {
2025-08-17 12:43:21 -04:00
vi . unstubAllEnvs ( ) ;
2025-08-07 14:06:17 -07:00
vi . restoreAllMocks ( ) ;
} ) ;
2025-09-02 12:01:22 -04:00
it ( 'should be false when folderTrust is false' , async ( ) = > {
2025-08-07 14:06:17 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
2025-08-27 18:39:45 -07:00
security : {
folderTrust : {
enabled : false ,
} ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-07 14:06:17 -07:00
expect ( config . getFolderTrust ( ) ) . toBe ( false ) ;
} ) ;
2025-09-02 12:01:22 -04:00
it ( 'should be true when folderTrust is true' , async ( ) = > {
2025-08-07 14:06:17 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-08-27 18:39:45 -07:00
security : {
folderTrust : {
enabled : true ,
} ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-02 12:01:22 -04:00
expect ( config . getFolderTrust ( ) ) . toBe ( true ) ;
2025-08-07 14:06:17 -07:00
} ) ;
2025-09-02 12:01:22 -04:00
it ( 'should be false by default' , async ( ) = > {
2025-08-07 14:06:17 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-02 12:01:22 -04:00
expect ( config . getFolderTrust ( ) ) . toBe ( false ) ;
2025-08-07 14:06:17 -07:00
} ) ;
} ) ;
2025-08-06 02:01:01 +09:00
describe ( 'loadCliConfig with includeDirectories' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
2025-11-06 20:01:30 -08:00
vi . mocked ( os . homedir ) . mockReturnValue (
path . resolve ( path . sep , 'mock' , 'home' , 'user' ) ,
) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-08-06 02:01:01 +09:00
vi . spyOn ( process , 'cwd' ) . mockReturnValue (
path . resolve ( path . sep , 'home' , 'user' , 'project' ) ,
) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-06 02:01:01 +09:00
} ) ;
afterEach ( ( ) = > {
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should combine and resolve paths from settings and CLI arguments' , async ( ) = > {
const mockCwd = path . resolve ( path . sep , 'home' , 'user' , 'project' ) ;
process . argv = [
'node' ,
'script.js' ,
'--include-directories' ,
` ${ path . resolve ( path . sep , 'cli' , 'path1' ) } , ${ path . join ( mockCwd , 'cli' , 'path2' ) } ` ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-08-27 18:39:45 -07:00
context : {
includeDirectories : [
path . resolve ( path . sep , 'settings' , 'path1' ) ,
path . join ( os . homedir ( ) , 'settings' , 'path2' ) ,
path . join ( mockCwd , 'settings' , 'path3' ) ,
] ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-08-06 02:01:01 +09:00
const expected = [
mockCwd ,
path . resolve ( path . sep , 'cli' , 'path1' ) ,
path . join ( mockCwd , 'cli' , 'path2' ) ,
path . resolve ( path . sep , 'settings' , 'path1' ) ,
path . join ( os . homedir ( ) , 'settings' , 'path2' ) ,
path . join ( mockCwd , 'settings' , 'path3' ) ,
] ;
2025-11-14 19:06:30 -08:00
const directories = config . getWorkspaceContext ( ) . getDirectories ( ) ;
expect ( directories ) . toEqual ( [ mockCwd ] ) ;
expect ( config . getPendingIncludeDirectories ( ) ) . toEqual (
expect . arrayContaining ( expected . filter ( ( dir ) = > dir !== mockCwd ) ) ,
2025-08-06 02:01:01 +09:00
) ;
2025-11-14 19:06:30 -08:00
expect ( config . getPendingIncludeDirectories ( ) ) . toHaveLength (
expected . length - 1 ,
2025-08-06 02:01:01 +09:00
) ;
} ) ;
} ) ;
2025-08-07 07:34:40 -07:00
2025-10-30 16:03:58 -07:00
describe ( 'loadCliConfig compressionThreshold' , ( ) = > {
2025-08-07 07:34:40 -07:00
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-07 07:34:40 -07:00
} ) ;
afterEach ( ( ) = > {
2025-08-17 12:43:21 -04:00
vi . unstubAllEnvs ( ) ;
2025-08-07 07:34:40 -07:00
vi . restoreAllMocks ( ) ;
} ) ;
2025-10-30 16:03:58 -07:00
it ( 'should pass settings to the core config' , async ( ) = > {
2025-08-07 07:34:40 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-08-27 18:39:45 -07:00
model : {
2025-10-30 16:03:58 -07:00
compressionThreshold : 0.5 ,
2025-08-07 07:34:40 -07:00
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-11-04 15:09:53 -08:00
expect ( await config . getCompressionThreshold ( ) ) . toBe ( 0.5 ) ;
2025-08-07 07:34:40 -07:00
} ) ;
2026-01-15 09:26:10 -08:00
it ( 'should have default compressionThreshold if not in settings' , async ( ) = > {
2025-08-07 07:34:40 -07:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2026-01-15 09:26:10 -08:00
expect ( await config . getCompressionThreshold ( ) ) . toBe ( 0.5 ) ;
2025-08-07 07:34:40 -07:00
} ) ;
} ) ;
2025-08-07 14:19:06 -07:00
2025-08-22 14:10:45 +08:00
describe ( 'loadCliConfig useRipgrep' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-22 14:10:45 +08:00
} ) ;
afterEach ( ( ) = > {
vi . unstubAllEnvs ( ) ;
vi . restoreAllMocks ( ) ;
} ) ;
2025-09-11 16:46:07 -07:00
it ( 'should be true by default when useRipgrep is not set in settings' , async ( ) = > {
2025-08-22 14:10:45 +08:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-11 16:46:07 -07:00
expect ( config . getUseRipgrep ( ) ) . toBe ( true ) ;
2025-08-22 14:10:45 +08:00
} ) ;
2025-09-11 16:46:07 -07:00
it ( 'should be false when useRipgrep is set to false in settings' , async ( ) = > {
2025-08-22 14:10:45 +08:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( { tools : { useRipgrep : false } } ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-11 16:46:07 -07:00
expect ( config . getUseRipgrep ( ) ) . toBe ( false ) ;
2025-08-22 14:10:45 +08:00
} ) ;
2025-09-11 16:46:07 -07:00
it ( 'should be true when useRipgrep is explicitly set to true in settings' , async ( ) = > {
2025-08-22 14:10:45 +08:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( { tools : { useRipgrep : true } } ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-11 16:46:07 -07:00
expect ( config . getUseRipgrep ( ) ) . toBe ( true ) ;
2025-08-22 14:10:45 +08:00
} ) ;
} ) ;
2025-09-19 18:28:11 -06:00
describe ( 'screenReader configuration' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-09-19 18:28:11 -06:00
} ) ;
afterEach ( ( ) = > {
vi . unstubAllEnvs ( ) ;
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should use screenReader value from settings if CLI flag is not present (settings true)' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-09-19 18:28:11 -06:00
ui : { accessibility : { screenReader : true } } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-19 18:28:11 -06:00
expect ( config . getScreenReader ( ) ) . toBe ( true ) ;
} ) ;
it ( 'should use screenReader value from settings if CLI flag is not present (settings false)' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-09-19 18:28:11 -06:00
ui : { accessibility : { screenReader : false } } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-19 18:28:11 -06:00
expect ( config . getScreenReader ( ) ) . toBe ( false ) ;
} ) ;
it ( 'should prioritize --screen-reader CLI flag (true) over settings (false)' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--screen-reader' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-09-19 18:28:11 -06:00
ui : { accessibility : { screenReader : false } } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-19 18:28:11 -06:00
expect ( config . getScreenReader ( ) ) . toBe ( true ) ;
} ) ;
it ( 'should be false by default when no flag or setting is present' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-19 18:28:11 -06:00
expect ( config . getScreenReader ( ) ) . toBe ( false ) ;
} ) ;
} ) ;
2025-08-07 14:19:06 -07:00
describe ( 'loadCliConfig tool exclusions' , ( ) = > {
const originalIsTTY = process . stdin . isTTY ;
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-08-07 14:19:06 -07:00
process . stdin . isTTY = true ;
2025-10-09 15:05:54 -07:00
vi . mocked ( isWorkspaceTrusted ) . mockReturnValue ( {
isTrusted : true ,
source : undefined ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-07 14:19:06 -07:00
} ) ;
afterEach ( ( ) = > {
process . stdin . isTTY = originalIsTTY ;
2025-08-17 12:43:21 -04:00
vi . unstubAllEnvs ( ) ;
2025-08-07 14:19:06 -07:00
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should not exclude interactive tools in interactive mode without YOLO' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'run_shell_command' ) ;
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'replace' ) ;
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'write_file' ) ;
} ) ;
it ( 'should not exclude interactive tools in interactive mode with YOLO' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'run_shell_command' ) ;
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'replace' ) ;
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'write_file' ) ;
} ) ;
it ( 'should exclude interactive tools in non-interactive mode without YOLO' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [ 'node' , 'script.js' , '-p' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . getExcludeTools ( ) ) . toContain ( 'run_shell_command' ) ;
expect ( config . getExcludeTools ( ) ) . toContain ( 'replace' ) ;
expect ( config . getExcludeTools ( ) ) . toContain ( 'write_file' ) ;
} ) ;
it ( 'should not exclude interactive tools in non-interactive mode with YOLO' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [ 'node' , 'script.js' , '-p' , 'test' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'run_shell_command' ) ;
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'replace' ) ;
expect ( config . getExcludeTools ( ) ) . not . toContain ( 'write_file' ) ;
} ) ;
2025-10-06 12:15:21 -07:00
it ( 'should not exclude shell tool in non-interactive mode when --allowed-tools="ShellTool" is set' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [
'node' ,
'script.js' ,
'-p' ,
'test' ,
'--allowed-tools' ,
'ShellTool' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-10-17 21:07:26 -04:00
expect ( config . getExcludeTools ( ) ) . not . toContain ( SHELL_TOOL_NAME ) ;
2025-10-06 12:15:21 -07:00
} ) ;
2025-12-01 22:43:14 +05:30
it ( 'should exclude web-fetch in non-interactive mode when not allowed' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [ 'node' , 'script.js' , '-p' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-12-01 22:43:14 +05:30
expect ( config . getExcludeTools ( ) ) . toContain ( WEB_FETCH_TOOL_NAME ) ;
} ) ;
it ( 'should not exclude web-fetch in non-interactive mode when allowed' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [
'node' ,
'script.js' ,
'-p' ,
'test' ,
'--allowed-tools' ,
WEB_FETCH_TOOL_NAME ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-12-01 22:43:14 +05:30
expect ( config . getExcludeTools ( ) ) . not . toContain ( WEB_FETCH_TOOL_NAME ) ;
} ) ;
2025-10-06 12:15:21 -07:00
it ( 'should not exclude shell tool in non-interactive mode when --allowed-tools="run_shell_command" is set' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [
'node' ,
'script.js' ,
'-p' ,
'test' ,
'--allowed-tools' ,
'run_shell_command' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-10-17 21:07:26 -04:00
expect ( config . getExcludeTools ( ) ) . not . toContain ( SHELL_TOOL_NAME ) ;
2025-10-06 12:15:21 -07:00
} ) ;
it ( 'should not exclude shell tool in non-interactive mode when --allowed-tools="ShellTool(wc)" is set' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [
'node' ,
'script.js' ,
'-p' ,
'test' ,
'--allowed-tools' ,
'ShellTool(wc)' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-10-17 21:07:26 -04:00
expect ( config . getExcludeTools ( ) ) . not . toContain ( SHELL_TOOL_NAME ) ;
2025-10-06 12:15:21 -07:00
} ) ;
2025-08-07 14:19:06 -07:00
} ) ;
describe ( 'loadCliConfig interactive' , ( ) = > {
const originalIsTTY = process . stdin . isTTY ;
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-08-07 14:19:06 -07:00
process . stdin . isTTY = true ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-07 14:19:06 -07:00
} ) ;
afterEach ( ( ) = > {
process . stdin . isTTY = originalIsTTY ;
2025-08-17 12:43:21 -04:00
vi . unstubAllEnvs ( ) ;
2025-08-07 14:19:06 -07:00
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should be interactive if isTTY and no prompt' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
} ) ;
it ( 'should be interactive if prompt-interactive is set' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [ 'node' , 'script.js' , '--prompt-interactive' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
} ) ;
it ( 'should not be interactive if not isTTY and no prompt' , async ( ) = > {
process . stdin . isTTY = false ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . isInteractive ( ) ) . toBe ( false ) ;
} ) ;
it ( 'should not be interactive if prompt is set' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' , '--prompt' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-07 14:19:06 -07:00
expect ( config . isInteractive ( ) ) . toBe ( false ) ;
} ) ;
2025-09-22 06:16:21 +09:00
2026-01-23 05:08:53 +05:30
it ( 'should be interactive if positional prompt words are provided with other flags' , async ( ) = > {
2025-09-22 06:16:21 +09:00
process . stdin . isTTY = true ;
2025-10-24 17:49:42 -04:00
process . argv = [ 'node' , 'script.js' , '--model' , 'gemini-2.5-pro' , 'Hello' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-23 05:08:53 +05:30
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
2025-09-22 06:16:21 +09:00
} ) ;
2026-01-23 05:08:53 +05:30
it ( 'should be interactive if positional prompt words are provided with multiple flags' , async ( ) = > {
2025-09-22 06:16:21 +09:00
process . stdin . isTTY = true ;
process . argv = [
'node' ,
'script.js' ,
'--model' ,
2025-10-24 17:49:42 -04:00
'gemini-2.5-pro' ,
2025-09-30 21:18:04 -04:00
'--yolo' ,
2025-09-22 06:16:21 +09:00
'Hello world' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-23 05:08:53 +05:30
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
2025-09-30 21:18:04 -04:00
// Verify the question is preserved for one-shot execution
2026-01-23 05:08:53 +05:30
expect ( argv . prompt ) . toBeUndefined ( ) ;
expect ( argv . promptInteractive ) . toBe ( 'Hello world' ) ;
2025-09-22 06:16:21 +09:00
} ) ;
2026-01-23 05:08:53 +05:30
it ( 'should be interactive if positional prompt words are provided with extensions flag' , async ( ) = > {
2025-10-09 05:32:05 +09:00
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' , '-e' , 'none' , 'hello' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-23 05:08:53 +05:30
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
2025-10-09 05:32:05 +09:00
expect ( argv . query ) . toBe ( 'hello' ) ;
2026-01-23 05:08:53 +05:30
expect ( argv . promptInteractive ) . toBe ( 'hello' ) ;
2025-10-09 05:32:05 +09:00
expect ( argv . extensions ) . toEqual ( [ 'none' ] ) ;
} ) ;
it ( 'should handle multiple positional words correctly' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' , 'hello world how are you' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-23 05:08:53 +05:30
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
2025-10-09 05:32:05 +09:00
expect ( argv . query ) . toBe ( 'hello world how are you' ) ;
2026-01-23 05:08:53 +05:30
expect ( argv . promptInteractive ) . toBe ( 'hello world how are you' ) ;
2025-10-09 05:32:05 +09:00
} ) ;
it ( 'should handle multiple positional words with flags' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [
'node' ,
'script.js' ,
'--model' ,
2025-10-24 17:49:42 -04:00
'gemini-2.5-pro' ,
2025-10-09 05:32:05 +09:00
'write' ,
'a' ,
'function' ,
'to' ,
'sort' ,
'array' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-23 05:08:53 +05:30
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
2025-10-09 05:32:05 +09:00
expect ( argv . query ) . toBe ( 'write a function to sort array' ) ;
2026-01-23 05:08:53 +05:30
expect ( argv . promptInteractive ) . toBe ( 'write a function to sort array' ) ;
2025-10-24 17:49:42 -04:00
expect ( argv . model ) . toBe ( 'gemini-2.5-pro' ) ;
2025-10-09 05:32:05 +09:00
} ) ;
it ( 'should handle empty positional arguments' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' , '' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-10-09 05:32:05 +09:00
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
expect ( argv . query ) . toBeUndefined ( ) ;
} ) ;
it ( 'should handle extensions flag with positional arguments correctly' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [
'node' ,
'script.js' ,
'-e' ,
'none' ,
'hello' ,
'world' ,
'how' ,
'are' ,
'you' ,
] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-23 05:08:53 +05:30
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
2025-10-09 05:32:05 +09:00
expect ( argv . query ) . toBe ( 'hello world how are you' ) ;
2026-01-23 05:08:53 +05:30
expect ( argv . promptInteractive ) . toBe ( 'hello world how are you' ) ;
2025-10-09 05:32:05 +09:00
expect ( argv . extensions ) . toEqual ( [ 'none' ] ) ;
} ) ;
2025-09-22 06:16:21 +09:00
it ( 'should be interactive if no positional prompt words are provided with flags' , async ( ) = > {
process . stdin . isTTY = true ;
2025-10-24 17:49:42 -04:00
process . argv = [ 'node' , 'script.js' , '--model' , 'gemini-2.5-pro' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-09-22 06:16:21 +09:00
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
} ) ;
2025-08-07 14:19:06 -07:00
} ) ;
2025-08-12 15:10:22 -07:00
describe ( 'loadCliConfig approval mode' , ( ) = > {
const originalArgv = process . argv ;
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
2025-08-17 12:43:21 -04:00
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
2025-08-13 11:06:31 -07:00
process . argv = [ 'node' , 'script.js' ] ; // Reset argv for each test
2025-10-09 15:05:54 -07:00
vi . mocked ( isWorkspaceTrusted ) . mockReturnValue ( {
isTrusted : true ,
source : undefined ,
} ) ;
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-08-12 15:10:22 -07:00
} ) ;
afterEach ( ( ) = > {
process . argv = originalArgv ;
2025-08-17 12:43:21 -04:00
vi . unstubAllEnvs ( ) ;
2025-08-12 15:10:22 -07:00
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should default to DEFAULT approval mode when no flags are set' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
it ( 'should set YOLO approval mode when --yolo flag is used' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . YOLO ) ;
} ) ;
it ( 'should set YOLO approval mode when -y flag is used' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '-y' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . YOLO ) ;
} ) ;
it ( 'should set DEFAULT approval mode when --approval-mode=default' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'default' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
it ( 'should set AUTO_EDIT approval mode when --approval-mode=auto_edit' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'auto_edit' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . AUTO_EDIT ) ;
} ) ;
it ( 'should set YOLO approval mode when --approval-mode=yolo' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . YOLO ) ;
} ) ;
it ( 'should prioritize --approval-mode over --yolo when both would be valid (but validation prevents this)' , async ( ) = > {
// Note: This test documents the intended behavior, but in practice the validation
// prevents both flags from being used together
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'default' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-08-12 15:10:22 -07:00
// Manually set yolo to true to simulate what would happen if validation didn't prevent it
argv . yolo = true ;
2026-01-15 09:26:10 -08:00
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
it ( 'should fall back to --yolo behavior when --approval-mode is not set' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-12 15:10:22 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . YOLO ) ;
} ) ;
2025-08-25 17:30:04 -07:00
2026-01-15 17:00:19 -05:00
it ( 'should set Plan approval mode when --approval-mode=plan is used and experimental.plan is enabled' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'plan' ] ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
experimental : {
plan : true ,
} ,
} ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . PLAN ) ;
} ) ;
2026-01-23 18:14:11 -05:00
it ( 'should ignore "yolo" in settings.tools.approvalMode and fall back to DEFAULT' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
const settings = createTestMergedSettings ( {
tools : {
// @ts-expect-error: testing invalid value
approvalMode : 'yolo' ,
} ,
} ) ;
const argv = await parseArguments ( settings ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
2026-01-15 17:00:19 -05:00
it ( 'should throw error when --approval-mode=plan is used but experimental.plan is disabled' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'plan' ] ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
experimental : {
plan : false ,
} ,
} ) ;
await expect ( loadCliConfig ( settings , 'test-session' , argv ) ) . rejects . toThrow (
'Approval mode "plan" is only available when experimental.plan is enabled.' ,
) ;
} ) ;
it ( 'should throw error when --approval-mode=plan is used but experimental.plan setting is missing' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'plan' ] ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( { } ) ;
await expect ( loadCliConfig ( settings , 'test-session' , argv ) ) . rejects . toThrow (
'Approval mode "plan" is only available when experimental.plan is enabled.' ,
) ;
} ) ;
2025-08-25 17:30:04 -07:00
// --- Untrusted Folder Scenarios ---
describe ( 'when folder is NOT trusted' , ( ) = > {
beforeEach ( ( ) = > {
2025-09-22 11:45:02 -07:00
vi . mocked ( isWorkspaceTrusted ) . mockReturnValue ( {
isTrusted : false ,
source : 'file' ,
} ) ;
2025-08-25 17:30:04 -07:00
} ) ;
it ( 'should override --approval-mode=yolo to DEFAULT' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-25 17:30:04 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
it ( 'should override --approval-mode=auto_edit to DEFAULT' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'auto_edit' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-25 17:30:04 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
it ( 'should override --yolo flag to DEFAULT' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-25 17:30:04 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
it ( 'should remain DEFAULT when --approval-mode=default' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'default' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-08-25 17:30:04 -07:00
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . DEFAULT ) ;
} ) ;
} ) ;
2026-01-23 18:14:11 -05:00
describe ( 'Persistent approvalMode setting' , ( ) = > {
it ( 'should use approvalMode from settings when no CLI flags are set' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
const settings = createTestMergedSettings ( {
tools : { approvalMode : 'auto_edit' } ,
} ) ;
const argv = await parseArguments ( settings ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getApprovalMode ( ) ) . toBe (
ServerConfig . ApprovalMode . AUTO_EDIT ,
) ;
} ) ;
it ( 'should prioritize --approval-mode flag over settings' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode' , 'auto_edit' ] ;
const settings = createTestMergedSettings ( {
tools : { approvalMode : 'default' } ,
} ) ;
const argv = await parseArguments ( settings ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getApprovalMode ( ) ) . toBe (
ServerConfig . ApprovalMode . AUTO_EDIT ,
) ;
} ) ;
it ( 'should prioritize --yolo flag over settings' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
const settings = createTestMergedSettings ( {
tools : { approvalMode : 'auto_edit' } ,
} ) ;
const argv = await parseArguments ( settings ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . YOLO ) ;
} ) ;
it ( 'should respect plan mode from settings when experimental.plan is enabled' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
const settings = createTestMergedSettings ( {
tools : { approvalMode : 'plan' } ,
experimental : { plan : true } ,
} ) ;
const argv = await parseArguments ( settings ) ;
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getApprovalMode ( ) ) . toBe ( ServerConfig . ApprovalMode . PLAN ) ;
} ) ;
it ( 'should throw error if plan mode is in settings but experimental.plan is disabled' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
const settings = createTestMergedSettings ( {
tools : { approvalMode : 'plan' } ,
experimental : { plan : false } ,
} ) ;
const argv = await parseArguments ( settings ) ;
await expect (
loadCliConfig ( settings , 'test-session' , argv ) ,
) . rejects . toThrow (
'Approval mode "plan" is only available when experimental.plan is enabled.' ,
) ;
} ) ;
} ) ;
2025-08-12 15:10:22 -07:00
} ) ;
2025-09-03 10:25:52 -07:00
describe ( 'loadCliConfig fileFiltering' , ( ) = > {
const originalArgv = process . argv ;
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
process . argv = [ 'node' , 'script.js' ] ; // Reset argv for each test
2025-10-28 09:04:30 -07:00
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
2025-09-03 10:25:52 -07:00
} ) ;
afterEach ( ( ) = > {
process . argv = originalArgv ;
vi . unstubAllEnvs ( ) ;
vi . restoreAllMocks ( ) ;
} ) ;
2025-10-17 22:39:53 -07:00
type FileFilteringSettings = NonNullable <
NonNullable < Settings [ 'context' ] > [ 'fileFiltering' ]
> ;
2025-09-03 10:25:52 -07:00
const testCases : Array < {
2025-10-17 22:39:53 -07:00
property : keyof FileFilteringSettings ;
2025-09-03 10:25:52 -07:00
getter : ( config : ServerConfig.Config ) = > boolean ;
value : boolean ;
} > = [
{
2026-01-16 23:33:49 +01:00
property : 'enableFuzzySearch' ,
getter : ( c ) = > c . getFileFilteringEnableFuzzySearch ( ) ,
2025-09-03 10:25:52 -07:00
value : true ,
} ,
{
2026-01-16 23:33:49 +01:00
property : 'enableFuzzySearch' ,
getter : ( c ) = > c . getFileFilteringEnableFuzzySearch ( ) ,
2025-09-03 10:25:52 -07:00
value : false ,
} ,
{
property : 'respectGitIgnore' ,
getter : ( c ) = > c . getFileFilteringRespectGitIgnore ( ) ,
value : true ,
} ,
{
property : 'respectGitIgnore' ,
getter : ( c ) = > c . getFileFilteringRespectGitIgnore ( ) ,
value : false ,
} ,
{
property : 'respectGeminiIgnore' ,
getter : ( c ) = > c . getFileFilteringRespectGeminiIgnore ( ) ,
value : true ,
} ,
{
property : 'respectGeminiIgnore' ,
getter : ( c ) = > c . getFileFilteringRespectGeminiIgnore ( ) ,
value : false ,
} ,
{
property : 'enableRecursiveFileSearch' ,
getter : ( c ) = > c . getEnableRecursiveFileSearch ( ) ,
value : true ,
} ,
{
property : 'enableRecursiveFileSearch' ,
getter : ( c ) = > c . getEnableRecursiveFileSearch ( ) ,
value : false ,
} ,
] ;
it . each ( testCases ) (
'should pass $property from settings to config when $value' ,
async ( { property , getter , value } ) = > {
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
2025-09-03 10:25:52 -07:00
context : {
fileFiltering : { [ property ] : value } ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-09-03 10:25:52 -07:00
const argv = await parseArguments ( settings ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-03 10:25:52 -07:00
expect ( getter ( config ) ) . toBe ( value ) ;
} ,
) ;
} ) ;
2025-09-03 15:48:54 -07:00
2025-09-18 08:41:12 -07:00
describe ( 'Output format' , ( ) = > {
2025-10-28 09:04:30 -07:00
beforeEach ( ( ) = > {
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
} ) ;
afterEach ( ( ) = > {
vi . resetAllMocks ( ) ;
} ) ;
2025-09-18 08:41:12 -07:00
it ( 'should default to TEXT' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-09-18 08:41:12 -07:00
expect ( config . getOutputFormat ( ) ) . toBe ( OutputFormat . TEXT ) ;
2025-09-11 05:19:47 +09:00
} ) ;
2025-09-18 08:41:12 -07:00
it ( 'should use the format from settings' , async ( ) = > {
2025-09-11 05:19:47 +09:00
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-11 05:19:47 +09:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( { output : { format : OutputFormat.JSON } } ) ,
2025-09-11 05:19:47 +09:00
'test-session' ,
argv ,
) ;
2025-09-18 08:41:12 -07:00
expect ( config . getOutputFormat ( ) ) . toBe ( OutputFormat . JSON ) ;
2025-09-11 05:19:47 +09:00
} ) ;
2025-09-18 08:41:12 -07:00
it ( 'should prioritize the format from argv' , async ( ) = > {
2025-09-11 05:19:47 +09:00
process . argv = [ 'node' , 'script.js' , '--output-format' , 'json' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-11 05:19:47 +09:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( { output : { format : OutputFormat.JSON } } ) ,
2025-09-11 05:19:47 +09:00
'test-session' ,
argv ,
) ;
2025-09-18 08:41:12 -07:00
expect ( config . getOutputFormat ( ) ) . toBe ( OutputFormat . JSON ) ;
2025-09-11 05:19:47 +09:00
} ) ;
2025-10-15 13:55:37 -07:00
it ( 'should accept stream-json as a valid output format' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--output-format' , 'stream-json' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-10-15 13:55:37 -07:00
expect ( config . getOutputFormat ( ) ) . toBe ( OutputFormat . STREAM_JSON ) ;
} ) ;
2025-09-18 08:41:12 -07:00
it ( 'should error on invalid --output-format argument' , async ( ) = > {
2025-11-20 10:44:02 -08:00
process . argv = [ 'node' , 'script.js' , '--output-format' , 'invalid' ] ;
2025-09-18 08:41:12 -07:00
const mockExit = vi . spyOn ( process , 'exit' ) . mockImplementation ( ( ) = > {
throw new Error ( 'process.exit called' ) ;
} ) ;
2025-11-20 10:44:02 -08:00
2025-09-18 08:41:12 -07:00
const mockConsoleError = vi
. spyOn ( console , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2025-11-20 10:44:02 -08:00
const debugErrorSpy = vi
. spyOn ( debugLogger , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2026-01-15 09:26:10 -08:00
await expect ( parseArguments ( createTestMergedSettings ( ) ) ) . rejects . toThrow (
2025-09-18 08:41:12 -07:00
'process.exit called' ,
) ;
2025-11-20 10:44:02 -08:00
expect ( debugErrorSpy ) . toHaveBeenCalledWith (
2025-09-18 08:41:12 -07:00
expect . stringContaining ( 'Invalid values:' ) ,
) ;
2025-11-20 10:44:02 -08:00
expect ( mockConsoleError ) . toHaveBeenCalled ( ) ;
2025-09-18 08:41:12 -07:00
mockExit . mockRestore ( ) ;
mockConsoleError . mockRestore ( ) ;
2025-11-20 10:44:02 -08:00
debugErrorSpy . mockRestore ( ) ;
2025-09-11 05:19:47 +09:00
} ) ;
} ) ;
2025-09-03 15:48:54 -07:00
describe ( 'parseArguments with positional prompt' , ( ) = > {
const originalArgv = process . argv ;
afterEach ( ( ) = > {
process . argv = originalArgv ;
} ) ;
it ( 'should throw an error when both a positional prompt and the --prompt flag are used' , async ( ) = > {
process . argv = [
'node' ,
'script.js' ,
'positional' ,
'prompt' ,
'--prompt' ,
'test prompt' ,
] ;
const mockExit = vi . spyOn ( process , 'exit' ) . mockImplementation ( ( ) = > {
throw new Error ( 'process.exit called' ) ;
} ) ;
const mockConsoleError = vi
. spyOn ( console , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2025-11-20 10:44:02 -08:00
const debugErrorSpy = vi
. spyOn ( debugLogger , 'error' )
. mockImplementation ( ( ) = > { } ) ;
2025-09-03 15:48:54 -07:00
2026-01-15 09:26:10 -08:00
await expect ( parseArguments ( createTestMergedSettings ( ) ) ) . rejects . toThrow (
2025-09-03 15:48:54 -07:00
'process.exit called' ,
) ;
2025-11-20 10:44:02 -08:00
expect ( debugErrorSpy ) . toHaveBeenCalledWith (
2025-09-03 15:48:54 -07:00
expect . stringContaining (
'Cannot use both a positional prompt and the --prompt (-p) flag together' ,
) ,
) ;
mockExit . mockRestore ( ) ;
mockConsoleError . mockRestore ( ) ;
2025-11-20 10:44:02 -08:00
debugErrorSpy . mockRestore ( ) ;
2025-09-03 15:48:54 -07:00
} ) ;
2025-09-30 21:18:04 -04:00
it ( 'should correctly parse a positional prompt to query field' , async ( ) = > {
2025-09-03 15:48:54 -07:00
process . argv = [ 'node' , 'script.js' , 'positional' , 'prompt' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-30 21:18:04 -04:00
expect ( argv . query ) . toBe ( 'positional prompt' ) ;
// Since no explicit prompt flags are set and query doesn't start with @, should map to prompt (one-shot)
expect ( argv . prompt ) . toBe ( 'positional prompt' ) ;
expect ( argv . promptInteractive ) . toBeUndefined ( ) ;
2025-09-03 15:48:54 -07:00
} ) ;
2025-10-09 05:32:05 +09:00
it ( 'should have correct positional argument description' , async ( ) = > {
// Test that the positional argument has the expected description
const yargsInstance = await import ( './config.js' ) ;
// This test verifies that the positional 'query' argument is properly configured
// with the description: "Positional prompt. Defaults to one-shot; use -i/--prompt-interactive for interactive."
process . argv = [ 'node' , 'script.js' , 'test' , 'query' ] ;
2026-01-15 09:26:10 -08:00
const argv = await yargsInstance . parseArguments ( createTestMergedSettings ( ) ) ;
2025-10-09 05:32:05 +09:00
expect ( argv . query ) . toBe ( 'test query' ) ;
} ) ;
2025-09-03 15:48:54 -07:00
it ( 'should correctly parse a prompt from the --prompt flag' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--prompt' , 'test prompt' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-03 15:48:54 -07:00
expect ( argv . prompt ) . toBe ( 'test prompt' ) ;
} ) ;
} ) ;
2025-09-23 03:23:06 +09:00
describe ( 'Telemetry configuration via environment variables' , ( ) = > {
2025-10-28 09:04:30 -07:00
beforeEach ( ( ) = > {
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
} ) ;
afterEach ( ( ) = > {
vi . resetAllMocks ( ) ;
} ) ;
2025-09-23 03:23:06 +09:00
it ( 'should prioritize GEMINI_TELEMETRY_ENABLED over settings' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_ENABLED' , 'true' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
telemetry : { enabled : false } ,
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryEnabled ( ) ) . toBe ( true ) ;
} ) ;
it ( 'should prioritize GEMINI_TELEMETRY_TARGET over settings' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_TARGET' , 'gcp' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-10-09 15:05:54 -07:00
telemetry : { target : ServerConfig.TelemetryTarget.LOCAL } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryTarget ( ) ) . toBe ( 'gcp' ) ;
} ) ;
it ( 'should throw when GEMINI_TELEMETRY_TARGET is invalid' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_TARGET' , 'bogus' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-10-09 15:05:54 -07:00
telemetry : { target : ServerConfig.TelemetryTarget.GCP } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
await expect ( loadCliConfig ( settings , 'test-session' , argv ) ) . rejects . toThrow (
2025-09-23 03:23:06 +09:00
/Invalid telemetry configuration: .*Invalid telemetry target/i ,
) ;
vi . unstubAllEnvs ( ) ;
} ) ;
it ( 'should prioritize GEMINI_TELEMETRY_OTLP_ENDPOINT over settings and default env var' , async ( ) = > {
vi . stubEnv ( 'OTEL_EXPORTER_OTLP_ENDPOINT' , 'http://default.env.com' ) ;
vi . stubEnv ( 'GEMINI_TELEMETRY_OTLP_ENDPOINT' , 'http://gemini.env.com' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-09-23 03:23:06 +09:00
telemetry : { otlpEndpoint : 'http://settings.com' } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryOtlpEndpoint ( ) ) . toBe ( 'http://gemini.env.com' ) ;
} ) ;
it ( 'should prioritize GEMINI_TELEMETRY_OTLP_PROTOCOL over settings' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_OTLP_PROTOCOL' , 'http' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
telemetry : { otlpProtocol : 'grpc' } ,
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryOtlpProtocol ( ) ) . toBe ( 'http' ) ;
} ) ;
it ( 'should prioritize GEMINI_TELEMETRY_LOG_PROMPTS over settings' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_LOG_PROMPTS' , 'false' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
telemetry : { logPrompts : true } ,
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryLogPromptsEnabled ( ) ) . toBe ( false ) ;
} ) ;
it ( 'should prioritize GEMINI_TELEMETRY_OUTFILE over settings' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_OUTFILE' , '/gemini/env/telemetry.log' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-09-23 03:23:06 +09:00
telemetry : { outfile : '/settings/telemetry.log' } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryOutfile ( ) ) . toBe ( '/gemini/env/telemetry.log' ) ;
} ) ;
it ( 'should prioritize GEMINI_TELEMETRY_USE_COLLECTOR over settings' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_USE_COLLECTOR' , 'true' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
telemetry : { useCollector : false } ,
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryUseCollector ( ) ) . toBe ( true ) ;
} ) ;
it ( 'should use settings value when GEMINI_TELEMETRY_ENABLED is not set' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_ENABLED' , undefined ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( { telemetry : { enabled : true } } ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryEnabled ( ) ) . toBe ( true ) ;
} ) ;
it ( 'should use settings value when GEMINI_TELEMETRY_TARGET is not set' , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_TARGET' , undefined ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2025-10-09 15:05:54 -07:00
telemetry : { target : ServerConfig.TelemetryTarget.LOCAL } ,
2026-01-15 09:26:10 -08:00
} ) ;
2025-10-28 09:04:30 -07:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryTarget ( ) ) . toBe ( 'local' ) ;
} ) ;
it ( "should treat GEMINI_TELEMETRY_ENABLED='1' as true" , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_ENABLED' , '1' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryEnabled ( ) ) . toBe ( true ) ;
} ) ;
it ( "should treat GEMINI_TELEMETRY_ENABLED='0' as false" , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_ENABLED' , '0' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-23 03:23:06 +09:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( { telemetry : { enabled : true } } ) ,
2025-09-23 03:23:06 +09:00
'test-session' ,
argv ,
) ;
expect ( config . getTelemetryEnabled ( ) ) . toBe ( false ) ;
} ) ;
it ( "should treat GEMINI_TELEMETRY_LOG_PROMPTS='1' as true" , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_LOG_PROMPTS' , '1' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2025-09-23 03:23:06 +09:00
expect ( config . getTelemetryLogPromptsEnabled ( ) ) . toBe ( true ) ;
} ) ;
it ( "should treat GEMINI_TELEMETRY_LOG_PROMPTS='false' as false" , async ( ) = > {
vi . stubEnv ( 'GEMINI_TELEMETRY_LOG_PROMPTS' , 'false' ) ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2025-09-23 03:23:06 +09:00
const config = await loadCliConfig (
2026-01-15 09:26:10 -08:00
createTestMergedSettings ( { telemetry : { logPrompts : true } } ) ,
2025-09-23 03:23:06 +09:00
'test-session' ,
argv ,
) ;
expect ( config . getTelemetryLogPromptsEnabled ( ) ) . toBe ( false ) ;
} ) ;
} ) ;
2026-01-04 00:19:00 -05:00
describe ( 'PolicyEngine nonInteractive wiring' , ( ) = > {
const originalIsTTY = process . stdin . isTTY ;
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
} ) ;
afterEach ( ( ) = > {
process . stdin . isTTY = originalIsTTY ;
vi . restoreAllMocks ( ) ;
} ) ;
2026-01-23 05:08:53 +05:30
it ( 'should set nonInteractive to true when -p flag is used' , async ( ) = > {
2026-01-04 00:19:00 -05:00
process . stdin . isTTY = true ;
2026-01-23 05:08:53 +05:30
process . argv = [ 'node' , 'script.js' , '-p' , 'echo hello' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-04 00:19:00 -05:00
expect ( config . isInteractive ( ) ) . toBe ( false ) ;
expect (
( config . getPolicyEngine ( ) as unknown as { nonInteractive : boolean } )
. nonInteractive ,
) . toBe ( true ) ;
} ) ;
it ( 'should set nonInteractive to false in interactive mode' , async ( ) = > {
process . stdin . isTTY = true ;
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const config = await loadCliConfig (
createTestMergedSettings ( ) ,
'test-session' ,
argv ,
) ;
2026-01-04 00:19:00 -05:00
expect ( config . isInteractive ( ) ) . toBe ( true ) ;
expect (
( config . getPolicyEngine ( ) as unknown as { nonInteractive : boolean } )
. nonInteractive ,
) . toBe ( false ) ;
} ) ;
} ) ;
describe ( 'Policy Engine Integration in loadCliConfig' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
} ) ;
afterEach ( ( ) = > {
vi . unstubAllEnvs ( ) ;
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should pass merged allowed tools from CLI and settings to createPolicyEngineConfig' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--allowed-tools' , 'cli-tool' ] ;
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
tools : { allowed : [ 'settings-tool' ] } ,
} ) ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2026-01-04 00:19:00 -05:00
await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( ServerConfig . createPolicyEngineConfig ) . toHaveBeenCalledWith (
expect . objectContaining ( {
tools : expect.objectContaining ( {
allowed : expect.arrayContaining ( [ 'cli-tool' ] ) ,
} ) ,
} ) ,
expect . anything ( ) ,
) ;
} ) ;
it ( 'should pass merged exclude tools from CLI logic and settings to createPolicyEngineConfig' , async ( ) = > {
process . stdin . isTTY = false ; // Non-interactive to trigger default excludes
process . argv = [ 'node' , 'script.js' , '-p' , 'test' ] ;
2026-01-15 09:26:10 -08:00
const settings = createTestMergedSettings ( {
tools : { exclude : [ 'settings-exclude' ] } ,
} ) ;
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
2026-01-04 00:19:00 -05:00
await loadCliConfig ( settings , 'test-session' , argv ) ;
// In non-interactive mode, ShellTool, etc. are excluded
expect ( ServerConfig . createPolicyEngineConfig ) . toHaveBeenCalledWith (
expect . objectContaining ( {
tools : expect.objectContaining ( {
exclude : expect.arrayContaining ( [ SHELL_TOOL_NAME ] ) ,
} ) ,
} ) ,
expect . anything ( ) ,
) ;
} ) ;
} ) ;
2026-01-06 16:38:07 -05:00
2026-01-08 11:48:03 -08:00
describe ( 'loadCliConfig disableYoloMode' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
vi . mocked ( isWorkspaceTrusted ) . mockReturnValue ( {
isTrusted : true ,
source : undefined ,
} ) ;
} ) ;
afterEach ( ( ) = > {
vi . unstubAllEnvs ( ) ;
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should allow auto_edit mode even if yolo mode is disabled' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode=auto_edit' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2026-01-08 11:48:03 -08:00
security : { disableYoloMode : true } ,
2026-01-15 09:26:10 -08:00
} ) ;
2026-01-08 11:48:03 -08:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getApprovalMode ( ) ) . toBe ( ApprovalMode . AUTO_EDIT ) ;
} ) ;
it ( 'should throw if YOLO mode is attempted when disableYoloMode is true' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2026-01-08 11:48:03 -08:00
security : { disableYoloMode : true } ,
2026-01-15 09:26:10 -08:00
} ) ;
2026-01-08 11:48:03 -08:00
await expect ( loadCliConfig ( settings , 'test-session' , argv ) ) . rejects . toThrow (
2026-01-30 13:05:22 -05:00
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli' ,
2026-01-08 11:48:03 -08:00
) ;
} ) ;
} ) ;
2026-01-06 16:38:07 -05:00
describe ( 'loadCliConfig secureModeEnabled' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
vi . mocked ( isWorkspaceTrusted ) . mockReturnValue ( {
isTrusted : true ,
source : undefined ,
} ) ;
} ) ;
afterEach ( ( ) = > {
vi . unstubAllEnvs ( ) ;
vi . restoreAllMocks ( ) ;
} ) ;
it ( 'should throw an error if YOLO mode is attempted when secureModeEnabled is true' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2026-01-06 16:38:07 -05:00
admin : {
secureModeEnabled : true ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2026-01-06 16:38:07 -05:00
await expect ( loadCliConfig ( settings , 'test-session' , argv ) ) . rejects . toThrow (
2026-01-30 13:05:22 -05:00
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli' ,
2026-01-06 16:38:07 -05:00
) ;
} ) ;
it ( 'should throw an error if approval-mode=yolo is attempted when secureModeEnabled is true' , async ( ) = > {
process . argv = [ 'node' , 'script.js' , '--approval-mode=yolo' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2026-01-06 16:38:07 -05:00
admin : {
secureModeEnabled : true ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2026-01-06 16:38:07 -05:00
await expect ( loadCliConfig ( settings , 'test-session' , argv ) ) . rejects . toThrow (
2026-01-30 13:05:22 -05:00
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli' ,
2026-01-06 16:38:07 -05:00
) ;
} ) ;
it ( 'should set disableYoloMode to true when secureModeEnabled is true' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2026-01-06 16:38:07 -05:00
admin : {
secureModeEnabled : true ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2026-01-06 16:38:07 -05:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . isYoloModeDisabled ( ) ) . toBe ( true ) ;
} ) ;
} ) ;
describe ( 'loadCliConfig mcpEnabled' , ( ) = > {
beforeEach ( ( ) = > {
vi . resetAllMocks ( ) ;
vi . mocked ( os . homedir ) . mockReturnValue ( '/mock/home/user' ) ;
vi . stubEnv ( 'GEMINI_API_KEY' , 'test-api-key' ) ;
vi . spyOn ( ExtensionManager . prototype , 'getExtensions' ) . mockReturnValue ( [ ] ) ;
} ) ;
afterEach ( ( ) = > {
vi . unstubAllEnvs ( ) ;
vi . restoreAllMocks ( ) ;
} ) ;
const mcpSettings = {
mcp : {
serverCommand : 'mcp-server' ,
allowed : [ 'serverA' ] ,
excluded : [ 'serverB' ] ,
} ,
mcpServers : { serverA : { url : 'http://a' } } ,
} ;
it ( 'should enable MCP by default' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( { . . . mcpSettings } ) ;
2026-01-06 16:38:07 -05:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getMcpEnabled ( ) ) . toBe ( true ) ;
expect ( config . getMcpServerCommand ( ) ) . toBe ( 'mcp-server' ) ;
expect ( config . getMcpServers ( ) ) . toEqual ( { serverA : { url : 'http://a' } } ) ;
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'serverA' ] ) ;
expect ( config . getBlockedMcpServers ( ) ) . toEqual ( [ 'serverB' ] ) ;
} ) ;
it ( 'should disable MCP when mcpEnabled is false' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2026-01-06 16:38:07 -05:00
. . . mcpSettings ,
admin : {
mcp : {
enabled : false ,
} ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2026-01-06 16:38:07 -05:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getMcpEnabled ( ) ) . toBe ( false ) ;
expect ( config . getMcpServerCommand ( ) ) . toBeUndefined ( ) ;
expect ( config . getMcpServers ( ) ) . toEqual ( { } ) ;
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ ] ) ;
expect ( config . getBlockedMcpServers ( ) ) . toEqual ( [ ] ) ;
} ) ;
it ( 'should enable MCP when mcpEnabled is true' , async ( ) = > {
process . argv = [ 'node' , 'script.js' ] ;
2026-01-15 09:26:10 -08:00
const argv = await parseArguments ( createTestMergedSettings ( ) ) ;
const settings = createTestMergedSettings ( {
2026-01-06 16:38:07 -05:00
. . . mcpSettings ,
admin : {
mcp : {
enabled : true ,
} ,
} ,
2026-01-15 09:26:10 -08:00
} ) ;
2026-01-06 16:38:07 -05:00
const config = await loadCliConfig ( settings , 'test-session' , argv ) ;
expect ( config . getMcpEnabled ( ) ) . toBe ( true ) ;
expect ( config . getMcpServerCommand ( ) ) . toBe ( 'mcp-server' ) ;
expect ( config . getMcpServers ( ) ) . toEqual ( { serverA : { url : 'http://a' } } ) ;
expect ( config . getAllowedMcpServers ( ) ) . toEqual ( [ 'serverA' ] ) ;
expect ( config . getBlockedMcpServers ( ) ) . toEqual ( [ 'serverB' ] ) ;
} ) ;
} ) ;