Download packages/a2a-server/src/config/config.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 20.1 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/a2a-server/src/config/config.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/a2a-server/src/config/config.test.ts
-
curl -L -o config.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/a2a-server/src/config/config.test.ts
20.1 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; | |
| import * as path from 'node:path'; | |
| import { loadConfig } from './config.js'; | |
| import type { Settings } from './settings.js'; | |
| import { | |
| type ExtensionLoader, | |
| getCodeAssistServer, | |
| Config, | |
| ExperimentFlags, | |
| fetchAdminControlsOnce, | |
| type FetchAdminControlsResponse, | |
| AuthType, | |
| isHeadlessMode, | |
| FatalAuthenticationError, | |
| PolicyDecision, | |
| ApprovalMode, | |
| PRIORITY_YOLO_ALLOW_ALL, | |
| createPolicyEngineConfig, | |
| } from '@google/gemini-cli-core'; | |
| import type { AgentSettings } from '../types.js'; | |
| // Mock dependencies | |
| vi.mock('@google/gemini-cli-core', async (importOriginal) => { | |
| const actual = | |
| await importOriginal<typeof import('@google/gemini-cli-core')>(); | |
| return { | |
| ...actual, | |
| PRIORITY_YOLO_ALLOW_ALL: 998, | |
| Config: vi.fn().mockImplementation((params) => { | |
| const mockConfig = { | |
| ...params, | |
| initialize: vi.fn(), | |
| waitForMcpInit: vi.fn(), | |
| refreshAuth: vi.fn(), | |
| getExperiments: vi.fn().mockReturnValue({ | |
| flags: { | |
| [actual.ExperimentFlags.ENABLE_ADMIN_CONTROLS]: { | |
| boolValue: false, | |
| }, | |
| }, | |
| }), | |
| getRemoteAdminSettings: vi.fn(), | |
| setRemoteAdminSettings: vi.fn(), | |
| }; | |
| return mockConfig; | |
| }), | |
| startupProfiler: { | |
| flush: vi.fn(), | |
| }, | |
| isHeadlessMode: vi.fn().mockReturnValue(false), | |
| getCodeAssistServer: vi.fn(), | |
| fetchAdminControlsOnce: vi.fn(), | |
| createPolicyEngineConfig: vi | |
| .fn() | |
| .mockImplementation( | |
| (_settings, mode, _defaultPoliciesDir, _interactive) => ({ | |
| rules: | |
| mode === actual.ApprovalMode.YOLO | |
| ? [ | |
| { | |
| toolName: '*', | |
| decision: actual.PolicyDecision.ALLOW, | |
| priority: actual.PRIORITY_YOLO_ALLOW_ALL, | |
| modes: [actual.ApprovalMode.YOLO], | |
| allowRedirection: true, | |
| }, | |
| ] | |
| : [ | |
| { | |
| toolName: 'read_file', | |
| decision: actual.PolicyDecision.ALLOW, | |
| priority: 1.05, | |
| source: 'Default: read-only.toml', | |
| }, | |
| ], | |
| checkers: [], | |
| }), | |
| ), | |
| coreEvents: { | |
| emitAdminSettingsChanged: vi.fn(), | |
| }, | |
| }; | |
| }); | |
| vi.mock('../utils/logger.js', () => ({ | |
| logger: { | |
| info: vi.fn(), | |
| warn: vi.fn(), | |
| error: vi.fn(), | |
| }, | |
| })); | |
| describe('loadConfig', () => { | |
| const mockSettings = {} as Settings; | |
| const mockExtensionLoader = {} as ExtensionLoader; | |
| const taskId = 'test-task-id'; | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| vi.stubEnv('GEMINI_API_KEY', 'test-key'); | |
| }); | |
| afterEach(() => { | |
| vi.unstubAllEnvs(); | |
| }); | |
| describe('admin settings overrides', () => { | |
| it('should not fetch admin controls if experiment is disabled', async () => { | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(fetchAdminControlsOnce).not.toHaveBeenCalled(); | |
| }); | |
| it('should pass clientName as a2a-server to Config', async () => { | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| clientName: 'a2a-server', | |
| }), | |
| ); | |
| }); | |
| describe('when admin controls experiment is enabled', () => { | |
| beforeEach(() => { | |
| // We need to cast to any here to modify the mock implementation | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| (Config as any).mockImplementation((params: unknown) => { | |
| const mockConfig = { | |
| ...(params as object), | |
| initialize: vi.fn(), | |
| waitForMcpInit: vi.fn(), | |
| refreshAuth: vi.fn(), | |
| getExperiments: vi.fn().mockReturnValue({ | |
| flags: { | |
| [ExperimentFlags.ENABLE_ADMIN_CONTROLS]: { | |
| boolValue: true, | |
| }, | |
| }, | |
| }), | |
| getRemoteAdminSettings: vi.fn().mockReturnValue({}), | |
| setRemoteAdminSettings: vi.fn(), | |
| }; | |
| return mockConfig; | |
| }); | |
| }); | |
| it('should fetch admin controls and apply them', async () => { | |
| const mockAdminSettings: FetchAdminControlsResponse = { | |
| mcpSetting: { | |
| mcpEnabled: false, | |
| }, | |
| cliFeatureSetting: { | |
| extensionsSetting: { | |
| extensionsEnabled: false, | |
| }, | |
| }, | |
| strictModeDisabled: false, | |
| }; | |
| vi.mocked(fetchAdminControlsOnce).mockResolvedValue(mockAdminSettings); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenLastCalledWith( | |
| expect.objectContaining({ | |
| disableYoloMode: !mockAdminSettings.strictModeDisabled, | |
| mcpEnabled: mockAdminSettings.mcpSetting?.mcpEnabled, | |
| extensionsEnabled: | |
| mockAdminSettings.cliFeatureSetting?.extensionsSetting | |
| ?.extensionsEnabled, | |
| }), | |
| ); | |
| }); | |
| it('should treat unset admin settings as false when admin settings are passed', async () => { | |
| const mockAdminSettings: FetchAdminControlsResponse = { | |
| mcpSetting: { | |
| mcpEnabled: true, | |
| }, | |
| }; | |
| vi.mocked(fetchAdminControlsOnce).mockResolvedValue(mockAdminSettings); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenLastCalledWith( | |
| expect.objectContaining({ | |
| disableYoloMode: !false, | |
| mcpEnabled: mockAdminSettings.mcpSetting?.mcpEnabled, | |
| extensionsEnabled: undefined, | |
| }), | |
| ); | |
| }); | |
| it('should not pass default unset admin settings when no admin settings are present', async () => { | |
| const mockAdminSettings: FetchAdminControlsResponse = {}; | |
| vi.mocked(fetchAdminControlsOnce).mockResolvedValue(mockAdminSettings); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenLastCalledWith(expect.objectContaining({})); | |
| }); | |
| it('should fetch admin controls using the code assist server when available', async () => { | |
| const mockAdminSettings: FetchAdminControlsResponse = { | |
| mcpSetting: { | |
| mcpEnabled: true, | |
| }, | |
| strictModeDisabled: true, | |
| }; | |
| const mockCodeAssistServer = { projectId: 'test-project' }; | |
| vi.mocked(getCodeAssistServer).mockReturnValue( | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| mockCodeAssistServer as any, | |
| ); | |
| vi.mocked(fetchAdminControlsOnce).mockResolvedValue(mockAdminSettings); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(fetchAdminControlsOnce).toHaveBeenCalledWith( | |
| mockCodeAssistServer, | |
| true, | |
| ); | |
| expect(Config).toHaveBeenLastCalledWith( | |
| expect.objectContaining({ | |
| disableYoloMode: !mockAdminSettings.strictModeDisabled, | |
| mcpEnabled: mockAdminSettings.mcpSetting?.mcpEnabled, | |
| extensionsEnabled: undefined, | |
| }), | |
| ); | |
| }); | |
| }); | |
| }); | |
| it('should set customIgnoreFilePaths when CUSTOM_IGNORE_FILE_PATHS env var is present', async () => { | |
| const testPath = '/tmp/ignore'; | |
| vi.stubEnv('CUSTOM_IGNORE_FILE_PATHS', testPath); | |
| const config = await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([ | |
| testPath, | |
| ]); | |
| }); | |
| it('should set customIgnoreFilePaths when settings.fileFiltering.customIgnoreFilePaths is present', async () => { | |
| const testPath = '/settings/ignore'; | |
| const settings: Settings = { | |
| fileFiltering: { | |
| customIgnoreFilePaths: [testPath], | |
| }, | |
| }; | |
| const config = await loadConfig(settings, mockExtensionLoader, taskId); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([ | |
| testPath, | |
| ]); | |
| }); | |
| it('should merge customIgnoreFilePaths from settings and env var', async () => { | |
| const envPath = '/env/ignore'; | |
| const settingsPath = '/settings/ignore'; | |
| vi.stubEnv('CUSTOM_IGNORE_FILE_PATHS', envPath); | |
| const settings: Settings = { | |
| fileFiltering: { | |
| customIgnoreFilePaths: [settingsPath], | |
| }, | |
| }; | |
| const config = await loadConfig(settings, mockExtensionLoader, taskId); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([ | |
| settingsPath, | |
| envPath, | |
| ]); | |
| }); | |
| it('should split CUSTOM_IGNORE_FILE_PATHS using system delimiter', async () => { | |
| const paths = ['/path/one', '/path/two']; | |
| vi.stubEnv('CUSTOM_IGNORE_FILE_PATHS', paths.join(path.delimiter)); | |
| const config = await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual(paths); | |
| }); | |
| it('should have empty customIgnoreFilePaths when both are missing', async () => { | |
| const config = await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| expect((config as any).fileFiltering.customIgnoreFilePaths).toEqual([]); | |
| }); | |
| describe('policy engine configuration', () => { | |
| it('should map tool settings into policySettings', async () => { | |
| const settings: Settings = { | |
| tools: { | |
| allowed: ['v2-allowed'], | |
| exclude: ['v2-exclude'], | |
| core: ['v2-core'], | |
| }, | |
| mcpServers: { | |
| test: { command: 'test', args: [] }, | |
| }, | |
| policyPaths: ['/path/to/policy'], | |
| adminPolicyPaths: ['/path/to/admin/policy'], | |
| }; | |
| await loadConfig(settings, mockExtensionLoader, taskId, true); | |
| expect(createPolicyEngineConfig).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| tools: { | |
| core: ['v2-core'], | |
| exclude: ['v2-exclude'], | |
| allowed: ['v2-allowed'], | |
| }, | |
| mcpServers: settings.mcpServers, | |
| policyPaths: settings.policyPaths, | |
| adminPolicyPaths: settings.adminPolicyPaths, | |
| }), | |
| ApprovalMode.DEFAULT, | |
| undefined, | |
| true, | |
| ); | |
| }); | |
| }); | |
| describe('tool configuration', () => { | |
| it('should pass V2 tools.allowed to Config properly', async () => { | |
| const settings: Settings = { | |
| tools: { | |
| allowed: ['shell', 'fetch'], | |
| }, | |
| }; | |
| await loadConfig(settings, mockExtensionLoader, taskId, true); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| allowedTools: ['shell', 'fetch'], | |
| }), | |
| ); | |
| }); | |
| it('should pass enableAgents to Config constructor', async () => { | |
| const settings: Settings = { | |
| experimental: { | |
| enableAgents: false, | |
| }, | |
| }; | |
| await loadConfig(settings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| enableAgents: false, | |
| }), | |
| ); | |
| }); | |
| it('should default enableAgents to true when not provided', async () => { | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| enableAgents: true, | |
| }), | |
| ); | |
| }); | |
| describe('interactivity', () => { | |
| it('should always set interactive true', async () => { | |
| vi.mocked(isHeadlessMode).mockReturnValue(true); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| interactive: true, | |
| }), | |
| ); | |
| vi.mocked(isHeadlessMode).mockReturnValue(false); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| interactive: true, | |
| }), | |
| ); | |
| }); | |
| it('should set enableInteractiveShell based on headless mode', async () => { | |
| vi.mocked(isHeadlessMode).mockReturnValue(false); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| enableInteractiveShell: true, | |
| }), | |
| ); | |
| vi.mocked(isHeadlessMode).mockReturnValue(true); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| enableInteractiveShell: false, | |
| }), | |
| ); | |
| }); | |
| }); | |
| describe('YOLO mode', () => { | |
| it('should enable YOLO mode and add policy rule when GEMINI_YOLO_MODE is true', async () => { | |
| vi.stubEnv('GEMINI_YOLO_MODE', 'true'); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| approvalMode: 'yolo', | |
| policyEngineConfig: expect.objectContaining({ | |
| rules: expect.arrayContaining([ | |
| expect.objectContaining({ | |
| decision: PolicyDecision.ALLOW, | |
| priority: PRIORITY_YOLO_ALLOW_ALL, | |
| modes: ['yolo'], | |
| allowRedirection: true, | |
| }), | |
| ]), | |
| }), | |
| }), | |
| ); | |
| }); | |
| it('should use default approval mode and load default rules when GEMINI_YOLO_MODE is not true', async () => { | |
| vi.stubEnv('GEMINI_YOLO_MODE', 'false'); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(Config).toHaveBeenCalledWith( | |
| expect.objectContaining({ | |
| approvalMode: 'default', | |
| policyEngineConfig: expect.objectContaining({ | |
| rules: expect.arrayContaining([ | |
| expect.objectContaining({ | |
| toolName: 'read_file', | |
| decision: PolicyDecision.ALLOW, | |
| }), | |
| ]), | |
| }), | |
| }), | |
| ); | |
| }); | |
| }); | |
| describe('authentication logic', () => { | |
| const setupConfigMock = (refreshAuthMock: ReturnType<typeof vi.fn>) => { | |
| vi.mocked(Config).mockImplementation( | |
| (params: unknown) => | |
| ({ | |
| ...(params as object), | |
| initialize: vi.fn(), | |
| waitForMcpInit: vi.fn(), | |
| refreshAuth: refreshAuthMock, | |
| getExperiments: vi.fn().mockReturnValue({ flags: {} }), | |
| getRemoteAdminSettings: vi.fn(), | |
| setRemoteAdminSettings: vi.fn(), | |
| }) as unknown as Config, | |
| ); | |
| }; | |
| beforeEach(() => { | |
| vi.stubEnv('USE_CCPA', 'true'); | |
| vi.stubEnv('GEMINI_API_KEY', ''); | |
| }); | |
| afterEach(() => { | |
| vi.unstubAllEnvs(); | |
| }); | |
| it('should attempt COMPUTE_ADC by default and bypass LOGIN_WITH_GOOGLE if successful', async () => { | |
| const refreshAuthMock = vi.fn().mockResolvedValue(undefined); | |
| setupConfigMock(refreshAuthMock); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.COMPUTE_ADC); | |
| expect(refreshAuthMock).not.toHaveBeenCalledWith( | |
| AuthType.LOGIN_WITH_GOOGLE, | |
| ); | |
| }); | |
| it('should fallback to LOGIN_WITH_GOOGLE if COMPUTE_ADC fails and interactive mode is available', async () => { | |
| vi.mocked(isHeadlessMode).mockReturnValue(false); | |
| const refreshAuthMock = vi.fn().mockImplementation((authType) => { | |
| if (authType === AuthType.COMPUTE_ADC) { | |
| return Promise.reject(new Error('ADC failed')); | |
| } | |
| return Promise.resolve(); | |
| }); | |
| setupConfigMock(refreshAuthMock); | |
| await loadConfig(mockSettings, mockExtensionLoader, taskId); | |
| expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.COMPUTE_ADC); | |
| expect(refreshAuthMock).toHaveBeenCalledWith( | |
| AuthType.LOGIN_WITH_GOOGLE, | |
| ); | |
| }); | |
| it('should throw FatalAuthenticationError in headless mode if COMPUTE_ADC fails', async () => { | |
| vi.mocked(isHeadlessMode).mockReturnValue(true); | |
| const refreshAuthMock = vi.fn().mockImplementation((authType) => { | |
| if (authType === AuthType.COMPUTE_ADC) { | |
| return Promise.reject(new Error('ADC not found')); | |
| } | |
| return Promise.resolve(); | |
| }); | |
| setupConfigMock(refreshAuthMock); | |
| await expect( | |
| loadConfig(mockSettings, mockExtensionLoader, taskId), | |
| ).rejects.toThrow( | |
| 'COMPUTE_ADC failed: ADC not found. (LOGIN_WITH_GOOGLE fallback skipped due to headless mode. Run in an interactive terminal to use OAuth.)', | |
| ); | |
| expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.COMPUTE_ADC); | |
| expect(refreshAuthMock).not.toHaveBeenCalledWith( | |
| AuthType.LOGIN_WITH_GOOGLE, | |
| ); | |
| }); | |
| it('should include both original and fallback error when LOGIN_WITH_GOOGLE fallback fails', async () => { | |
| vi.mocked(isHeadlessMode).mockReturnValue(false); | |
| const refreshAuthMock = vi.fn().mockImplementation((authType) => { | |
| if (authType === AuthType.COMPUTE_ADC) { | |
| throw new Error('ADC failed'); | |
| } | |
| if (authType === AuthType.LOGIN_WITH_GOOGLE) { | |
| throw new FatalAuthenticationError('OAuth failed'); | |
| } | |
| return Promise.resolve(); | |
| }); | |
| setupConfigMock(refreshAuthMock); | |
| await expect( | |
| loadConfig(mockSettings, mockExtensionLoader, taskId), | |
| ).rejects.toThrow( | |
| 'OAuth failed. The initial COMPUTE_ADC attempt also failed: ADC failed', | |
| ); | |
| }); | |
| }); | |
| }); | |
| }); | |
| describe('setIsTrusted', () => { | |
| beforeEach(() => { | |
| vi.resetModules(); | |
| // Ensure GEMINI_CLI_TRUST_WORKSPACE is not set by default in tests to prevent leakage | |
| vi.stubEnv('GEMINI_CLI_TRUST_WORKSPACE', ''); | |
| }); | |
| afterEach(() => { | |
| vi.unstubAllEnvs(); | |
| }); | |
| it('should return agentSettings.isTrusted if defined, ignoring env vars', async () => { | |
| vi.stubEnv('GEMINI_CLI_TRUST_WORKSPACE', 'false'); | |
| const { setIsTrusted } = await import('./config.js'); | |
| expect(setIsTrusted({ isTrusted: true } as AgentSettings)).toBe(true); | |
| vi.stubEnv('GEMINI_CLI_TRUST_WORKSPACE', 'true'); | |
| expect(setIsTrusted({ isTrusted: false } as AgentSettings)).toBe(false); | |
| }); | |
| it('should return true when GEMINI_CLI_TRUST_WORKSPACE env var is true and agentSettings.isTrusted is undefined', async () => { | |
| vi.stubEnv('GEMINI_CLI_TRUST_WORKSPACE', 'true'); | |
| const { setIsTrusted } = await import('./config.js'); | |
| expect(setIsTrusted(undefined)).toBe(true); | |
| expect(setIsTrusted({} as AgentSettings)).toBe(true); | |
| }); | |
| it('should return false when GEMINI_CLI_TRUST_WORKSPACE env var is false and agentSettings.isTrusted is undefined', async () => { | |
| vi.stubEnv('GEMINI_CLI_TRUST_WORKSPACE', 'false'); | |
| const { setIsTrusted } = await import('./config.js'); | |
| expect(setIsTrusted(undefined)).toBe(false); | |
| expect(setIsTrusted({} as AgentSettings)).toBe(false); | |
| }); | |
| it('should fallback to false if agentSettings.isTrusted and env var are undefined and no workspaceRoot is provided', async () => { | |
| const { setIsTrusted } = await import('./config.js'); | |
| expect(setIsTrusted(undefined)).toBe(false); | |
| expect(setIsTrusted({} as AgentSettings)).toBe(false); | |
| }); | |
| }); | |