| |
| |
| |
| |
| |
|
|
| import { |
| describe, |
| it, |
| expect, |
| vi, |
| beforeEach, |
| type MockInstance, |
| } from 'vitest'; |
| import { |
| runDeferredCommand, |
| defer, |
| setDeferredCommand, |
| type DeferredCommand, |
| } from './deferred.js'; |
| import { ExitCodes } from '@google/gemini-cli-core'; |
| import type { ArgumentsCamelCase, CommandModule } from 'yargs'; |
| import { createMockSettings } from './test-utils/settings.js'; |
|
|
| const { mockRunExitCleanup, mockCoreEvents } = vi.hoisted(() => ({ |
| mockRunExitCleanup: vi.fn(), |
| mockCoreEvents: { |
| emitFeedback: vi.fn(), |
| }, |
| })); |
|
|
| vi.mock('@google/gemini-cli-core', async () => { |
| const actual = await vi.importActual('@google/gemini-cli-core'); |
| return { |
| ...actual, |
| coreEvents: mockCoreEvents, |
| }; |
| }); |
|
|
| vi.mock('./utils/cleanup.js', () => ({ |
| runExitCleanup: mockRunExitCleanup, |
| })); |
|
|
| let mockExit: MockInstance; |
|
|
| describe('deferred', () => { |
| beforeEach(() => { |
| vi.clearAllMocks(); |
| mockExit = vi |
| .spyOn(process, 'exit') |
| .mockImplementation(() => undefined as never); |
| setDeferredCommand(undefined as unknown as DeferredCommand); |
| }); |
|
|
| describe('runDeferredCommand', () => { |
| it('should do nothing if no deferred command is set', async () => { |
| await runDeferredCommand(createMockSettings().merged); |
| expect(mockCoreEvents.emitFeedback).not.toHaveBeenCalled(); |
| expect(mockExit).not.toHaveBeenCalled(); |
| }); |
|
|
| it('should execute the deferred command if enabled', async () => { |
| const mockHandler = vi.fn(); |
| setDeferredCommand({ |
| handler: mockHandler, |
| argv: { _: [], $0: 'gemini' } as ArgumentsCamelCase, |
| commandName: 'mcp', |
| }); |
|
|
| const settings = createMockSettings({ |
| merged: { admin: { mcp: { enabled: true } } }, |
| }).merged; |
| await runDeferredCommand(settings); |
| expect(mockHandler).toHaveBeenCalled(); |
| expect(mockRunExitCleanup).toHaveBeenCalled(); |
| expect(mockExit).toHaveBeenCalledWith(ExitCodes.SUCCESS); |
| }); |
|
|
| it('should exit with FATAL_CONFIG_ERROR if MCP is disabled', async () => { |
| setDeferredCommand({ |
| handler: vi.fn(), |
| argv: {} as ArgumentsCamelCase, |
| commandName: 'mcp', |
| }); |
|
|
| const settings = createMockSettings({ |
| merged: { admin: { mcp: { enabled: false } } }, |
| }).merged; |
| await runDeferredCommand(settings); |
|
|
| expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith( |
| 'error', |
| 'MCP is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli', |
| ); |
| expect(mockRunExitCleanup).toHaveBeenCalled(); |
| expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR); |
| }); |
|
|
| it('should exit with FATAL_CONFIG_ERROR if extensions are disabled', async () => { |
| setDeferredCommand({ |
| handler: vi.fn(), |
| argv: {} as ArgumentsCamelCase, |
| commandName: 'extensions', |
| }); |
|
|
| const settings = createMockSettings({ |
| merged: { admin: { extensions: { enabled: false } } }, |
| }).merged; |
| await runDeferredCommand(settings); |
|
|
| expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith( |
| 'error', |
| 'Extensions is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli', |
| ); |
| expect(mockRunExitCleanup).toHaveBeenCalled(); |
| expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR); |
| }); |
|
|
| it('should exit with FATAL_CONFIG_ERROR if skills are disabled', async () => { |
| setDeferredCommand({ |
| handler: vi.fn(), |
| argv: {} as ArgumentsCamelCase, |
| commandName: 'skills', |
| }); |
|
|
| const settings = createMockSettings({ |
| merged: { admin: { skills: { enabled: false } } }, |
| }).merged; |
| await runDeferredCommand(settings); |
|
|
| expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith( |
| 'error', |
| 'Agent skills is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli', |
| ); |
| expect(mockRunExitCleanup).toHaveBeenCalled(); |
| expect(mockExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR); |
| }); |
|
|
| it('should execute if admin settings are undefined (default implicit enable)', async () => { |
| const mockHandler = vi.fn(); |
| setDeferredCommand({ |
| handler: mockHandler, |
| argv: {} as ArgumentsCamelCase, |
| commandName: 'mcp', |
| }); |
|
|
| const settings = createMockSettings({}).merged; |
| await runDeferredCommand(settings); |
|
|
| expect(mockHandler).toHaveBeenCalled(); |
| expect(mockExit).toHaveBeenCalledWith(ExitCodes.SUCCESS); |
| }); |
| }); |
|
|
| describe('defer', () => { |
| it('should wrap a command module and defer execution', async () => { |
| const originalHandler = vi.fn(); |
| const commandModule: CommandModule = { |
| command: 'test', |
| describe: 'test command', |
| handler: originalHandler, |
| }; |
|
|
| const deferredModule = defer(commandModule); |
| expect(deferredModule.command).toBe(commandModule.command); |
|
|
| |
| const argv = { _: [], $0: 'gemini' } as ArgumentsCamelCase; |
| await deferredModule.handler(argv); |
|
|
| |
| expect(originalHandler).not.toHaveBeenCalled(); |
|
|
| |
| await runDeferredCommand(createMockSettings().merged); |
| expect(originalHandler).toHaveBeenCalledWith( |
| expect.objectContaining({ |
| settings: expect.objectContaining({ |
| admin: expect.objectContaining({ |
| extensions: expect.objectContaining({ enabled: true }), |
| }), |
| }), |
| }), |
| ); |
| expect(mockExit).toHaveBeenCalledWith(ExitCodes.SUCCESS); |
| }); |
|
|
| it('should use parentCommandName if provided', async () => { |
| const commandModule: CommandModule = { |
| command: 'subcommand', |
| describe: 'sub command', |
| handler: vi.fn(), |
| }; |
|
|
| const deferredModule = defer(commandModule, 'parent'); |
| await deferredModule.handler({} as ArgumentsCamelCase); |
|
|
| const deferredMcp = defer(commandModule, 'mcp'); |
| await deferredMcp.handler({} as ArgumentsCamelCase); |
|
|
| const mcpSettings = createMockSettings({ |
| merged: { admin: { mcp: { enabled: false } } }, |
| }).merged; |
| await runDeferredCommand(mcpSettings); |
|
|
| expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith( |
| 'error', |
| 'MCP is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli', |
| ); |
| }); |
|
|
| it('should fallback to unknown if no parentCommandName is provided', async () => { |
| const mockHandler = vi.fn(); |
| const commandModule: CommandModule = { |
| command: ['foo', 'infoo'], |
| describe: 'foo command', |
| handler: mockHandler, |
| }; |
|
|
| const deferredModule = defer(commandModule); |
| await deferredModule.handler({} as ArgumentsCamelCase); |
|
|
| |
| |
| |
| const settings = createMockSettings({ |
| merged: { |
| admin: { |
| mcp: { enabled: false }, |
| extensions: { enabled: false }, |
| skills: { enabled: false }, |
| }, |
| }, |
| }).merged; |
|
|
| await runDeferredCommand(settings); |
|
|
| expect(mockHandler).toHaveBeenCalled(); |
| expect(mockExit).toHaveBeenCalledWith(ExitCodes.SUCCESS); |
| }); |
| }); |
| }); |
|
|