mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
237 lines
7.7 KiB
TypeScript
237 lines
7.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } 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';
|
|
import type { MockInstance } from 'vitest';
|
|
|
|
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); // Reset deferred command
|
|
});
|
|
|
|
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; // No admin settings
|
|
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);
|
|
|
|
// Execute the wrapper handler
|
|
const argv = { _: [], $0: 'gemini' } as ArgumentsCamelCase;
|
|
await deferredModule.handler(argv);
|
|
|
|
// Should check that it set the deferred command, but didn't run original handler yet
|
|
expect(originalHandler).not.toHaveBeenCalled();
|
|
|
|
// Now manually run it to verify it captured correctly
|
|
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);
|
|
|
|
// Verify it runs even if all known commands are disabled,
|
|
// confirming it didn't capture 'mcp', 'extensions', or 'skills'
|
|
// and defaulted to 'unknown' (or something else safe).
|
|
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);
|
|
});
|
|
});
|
|
});
|