mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 22:14:52 -07:00
Protect stdout and stderr so JavaScript code can't accidentally write to stdout corrupting ink rendering (#13247)
Bypassing rules as link checker failure is spurious.
This commit is contained in:
@@ -13,13 +13,10 @@ import {
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
|
||||
import { format } from 'node:util';
|
||||
import { type CommandModule, type Argv } from 'yargs';
|
||||
|
||||
import { handleDisable, disableCommand } from './disable.js';
|
||||
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
|
||||
import {
|
||||
loadSettings,
|
||||
SettingScope,
|
||||
@@ -28,71 +25,53 @@ import {
|
||||
import { getErrorMessage } from '../../utils/errors.js';
|
||||
|
||||
// Mock dependencies
|
||||
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
|
||||
vi.mock('../../config/settings.js');
|
||||
|
||||
vi.mock('../../utils/errors.js');
|
||||
const emitConsoleLog = vi.hoisted(() => vi.fn());
|
||||
const debugLogger = vi.hoisted(() => ({
|
||||
log: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('log', format(message, ...args));
|
||||
}),
|
||||
error: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('error', format(message, ...args));
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
|
||||
return {
|
||||
...actual,
|
||||
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
|
||||
error: vi.fn(),
|
||||
coreEvents: {
|
||||
emitConsoleLog,
|
||||
},
|
||||
debugLogger,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
vi.mock('../../config/extensions/consent.js', () => ({
|
||||
requestConsentNonInteractive: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../config/extensions/extensionSettings.js', () => ({
|
||||
promptForSetting: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('extensions disable command', () => {
|
||||
const mockLoadSettings = vi.mocked(loadSettings);
|
||||
|
||||
const mockGetErrorMessage = vi.mocked(getErrorMessage);
|
||||
|
||||
const mockExtensionManager = vi.mocked(ExtensionManager);
|
||||
|
||||
interface MockDebugLogger {
|
||||
log: Mock;
|
||||
error: Mock;
|
||||
}
|
||||
let mockDebugLogger: MockDebugLogger;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// We need to re-import the mocked module to get the fresh mock
|
||||
|
||||
mockDebugLogger = (await import('@google/gemini-cli-core'))
|
||||
.debugLogger as unknown as MockDebugLogger;
|
||||
|
||||
mockLoadSettings.mockReturnValue({
|
||||
merged: {},
|
||||
} as unknown as LoadedSettings);
|
||||
|
||||
mockExtensionManager.prototype.loadExtensions = vi
|
||||
|
||||
.fn()
|
||||
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
mockExtensionManager.prototype.disableExtension = vi
|
||||
|
||||
.fn()
|
||||
|
||||
.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
@@ -127,54 +106,40 @@ describe('extensions disable command', () => {
|
||||
'should disable an extension in the $expectedScope scope when scope is $scope',
|
||||
async ({ name, scope, expectedScope, expectedLog }) => {
|
||||
const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
|
||||
|
||||
await handleDisable({ name, scope });
|
||||
|
||||
expect(mockExtensionManager).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: '/test/dir',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
mockExtensionManager.prototype.loadExtensions,
|
||||
).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
mockExtensionManager.prototype.disableExtension,
|
||||
).toHaveBeenCalledWith(name, expectedScope);
|
||||
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(expectedLog);
|
||||
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith('log', expectedLog);
|
||||
mockCwd.mockRestore();
|
||||
},
|
||||
);
|
||||
|
||||
it('should log an error message and exit with code 1 when extension disabling fails', async () => {
|
||||
const mockProcessExit = vi
|
||||
|
||||
.spyOn(process, 'exit')
|
||||
|
||||
.mockImplementation((() => {}) as (
|
||||
code?: string | number | null | undefined,
|
||||
) => never);
|
||||
|
||||
const error = new Error('Disable failed');
|
||||
|
||||
(
|
||||
mockExtensionManager.prototype.disableExtension as Mock
|
||||
).mockRejectedValue(error);
|
||||
|
||||
mockGetErrorMessage.mockReturnValue('Disable failed message');
|
||||
|
||||
await handleDisable({ name: 'my-extension' });
|
||||
|
||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'Disable failed message',
|
||||
);
|
||||
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
||||
|
||||
mockProcessExit.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -184,16 +149,13 @@ describe('extensions disable command', () => {
|
||||
|
||||
it('should have correct command and describe', () => {
|
||||
expect(command.command).toBe('disable [--scope] <name>');
|
||||
|
||||
expect(command.describe).toBe('Disables an extension.');
|
||||
});
|
||||
|
||||
describe('builder', () => {
|
||||
interface MockYargs {
|
||||
positional: Mock;
|
||||
|
||||
option: Mock;
|
||||
|
||||
check: Mock;
|
||||
}
|
||||
|
||||
@@ -202,9 +164,7 @@ describe('extensions disable command', () => {
|
||||
beforeEach(() => {
|
||||
yargsMock = {
|
||||
positional: vi.fn().mockReturnThis(),
|
||||
|
||||
option: vi.fn().mockReturnThis(),
|
||||
|
||||
check: vi.fn().mockReturnThis(),
|
||||
};
|
||||
});
|
||||
@@ -213,21 +173,15 @@ describe('extensions disable command', () => {
|
||||
(command.builder as (yargs: Argv) => Argv)(
|
||||
yargsMock as unknown as Argv,
|
||||
);
|
||||
|
||||
expect(yargsMock.positional).toHaveBeenCalledWith('name', {
|
||||
describe: 'The name of the extension to disable.',
|
||||
|
||||
type: 'string',
|
||||
});
|
||||
|
||||
expect(yargsMock.option).toHaveBeenCalledWith('scope', {
|
||||
describe: 'The scope to disable the extension in.',
|
||||
|
||||
type: 'string',
|
||||
|
||||
default: SettingScope.User,
|
||||
});
|
||||
|
||||
expect(yargsMock.check).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -235,17 +189,12 @@ describe('extensions disable command', () => {
|
||||
(command.builder as (yargs: Argv) => Argv)(
|
||||
yargsMock as unknown as Argv,
|
||||
);
|
||||
|
||||
const checkCallback = yargsMock.check.mock.calls[0][0];
|
||||
|
||||
const expectedError = `Invalid scope: invalid. Please use one of ${Object.values(
|
||||
SettingScope,
|
||||
)
|
||||
|
||||
.map((s) => s.toLowerCase())
|
||||
|
||||
.join(', ')}.`;
|
||||
|
||||
expect(() => checkCallback({ scope: 'invalid' })).toThrow(
|
||||
expectedError,
|
||||
);
|
||||
@@ -257,9 +206,7 @@ describe('extensions disable command', () => {
|
||||
(command.builder as (yargs: Argv) => Argv)(
|
||||
yargsMock as unknown as Argv,
|
||||
);
|
||||
|
||||
const checkCallback = yargsMock.check.mock.calls[0][0];
|
||||
|
||||
expect(checkCallback({ scope })).toBe(true);
|
||||
},
|
||||
);
|
||||
@@ -267,7 +214,6 @@ describe('extensions disable command', () => {
|
||||
|
||||
it('handler should trigger extension disabling', async () => {
|
||||
const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir');
|
||||
|
||||
interface TestArgv {
|
||||
name: string;
|
||||
scope: string;
|
||||
@@ -279,24 +225,20 @@ describe('extensions disable command', () => {
|
||||
_: [],
|
||||
$0: '',
|
||||
};
|
||||
|
||||
await (command.handler as unknown as (args: TestArgv) => void)(argv);
|
||||
expect(mockExtensionManager).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceDir: '/test/dir',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockExtensionManager.prototype.loadExtensions).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
mockExtensionManager.prototype.disableExtension,
|
||||
).toHaveBeenCalledWith('test-ext', SettingScope.Workspace);
|
||||
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "test-ext" successfully disabled for scope "workspace".',
|
||||
);
|
||||
|
||||
mockCwd.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { format } from 'node:util';
|
||||
import { type CommandModule, type Argv } from 'yargs';
|
||||
import { handleEnable, enableCommand } from './enable.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
@@ -24,17 +25,25 @@ import {
|
||||
import { FatalConfigError } from '@google/gemini-cli-core';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
const emitConsoleLog = vi.hoisted(() => vi.fn());
|
||||
const debugLogger = vi.hoisted(() => ({
|
||||
log: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('log', format(message, ...args));
|
||||
}),
|
||||
error: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('error', format(message, ...args));
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
coreEvents: {
|
||||
emitConsoleLog,
|
||||
},
|
||||
debugLogger,
|
||||
getErrorMessage: vi.fn((error: { message: string }) => error.message),
|
||||
FatalConfigError: class extends Error {
|
||||
constructor(message: string) {
|
||||
@@ -44,22 +53,18 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../config/extensions/consent.js');
|
||||
vi.mock('../../config/extensions/extensionSettings.js');
|
||||
|
||||
describe('extensions enable command', () => {
|
||||
const mockLoadSettings = vi.mocked(loadSettings);
|
||||
const mockExtensionManager = vi.mocked(ExtensionManager);
|
||||
interface MockDebugLogger {
|
||||
log: Mock;
|
||||
error: Mock;
|
||||
}
|
||||
let mockDebugLogger: MockDebugLogger;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
mockDebugLogger = (await import('@google/gemini-cli-core'))
|
||||
.debugLogger as unknown as MockDebugLogger;
|
||||
mockLoadSettings.mockReturnValue({
|
||||
merged: {},
|
||||
} as unknown as LoadedSettings);
|
||||
@@ -106,7 +111,7 @@ describe('extensions enable command', () => {
|
||||
expect(
|
||||
mockExtensionManager.prototype.enableExtension,
|
||||
).toHaveBeenCalledWith(name, expectedScope);
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(expectedLog);
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith('log', expectedLog);
|
||||
mockCwd.mockRestore();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { format } from 'node:util';
|
||||
import { type CommandModule, type Argv } from 'yargs';
|
||||
import { handleLink, linkCommand } from './link.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
@@ -20,20 +21,31 @@ import { loadSettings, type LoadedSettings } from '../../config/settings.js';
|
||||
import { getErrorMessage } from '../../utils/errors.js';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
const emitConsoleLog = vi.hoisted(() => vi.fn());
|
||||
const debugLogger = vi.hoisted(() => ({
|
||||
log: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('log', format(message, ...args));
|
||||
}),
|
||||
error: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('error', format(message, ...args));
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
coreEvents: {
|
||||
emitConsoleLog,
|
||||
},
|
||||
debugLogger,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
vi.mock('../../config/extensions/consent.js', () => ({
|
||||
requestConsentNonInteractive: vi.fn(),
|
||||
}));
|
||||
@@ -45,16 +57,9 @@ describe('extensions link command', () => {
|
||||
const mockLoadSettings = vi.mocked(loadSettings);
|
||||
const mockGetErrorMessage = vi.mocked(getErrorMessage);
|
||||
const mockExtensionManager = vi.mocked(ExtensionManager);
|
||||
interface MockDebugLogger {
|
||||
log: Mock;
|
||||
error: Mock;
|
||||
}
|
||||
let mockDebugLogger: MockDebugLogger;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
mockDebugLogger = (await import('@google/gemini-cli-core'))
|
||||
.debugLogger as unknown as MockDebugLogger;
|
||||
mockLoadSettings.mockReturnValue({
|
||||
merged: {},
|
||||
} as unknown as LoadedSettings);
|
||||
@@ -87,7 +92,8 @@ describe('extensions link command', () => {
|
||||
source: '/local/path/to/extension',
|
||||
type: 'link',
|
||||
});
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "my-linked-extension" linked successfully and enabled.',
|
||||
);
|
||||
mockCwd.mockRestore();
|
||||
@@ -107,7 +113,10 @@ describe('extensions link command', () => {
|
||||
|
||||
await handleLink({ path: '/local/path/to/extension' });
|
||||
|
||||
expect(mockDebugLogger.error).toHaveBeenCalledWith('Link failed message');
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'Link failed message',
|
||||
);
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
||||
mockProcessExit.mockRestore();
|
||||
});
|
||||
|
||||
@@ -4,15 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
vi,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { format } from 'node:util';
|
||||
import { type CommandModule } from 'yargs';
|
||||
import { handleList, listCommand } from './list.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
@@ -20,20 +13,31 @@ import { loadSettings, type LoadedSettings } from '../../config/settings.js';
|
||||
import { getErrorMessage } from '../../utils/errors.js';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
const emitConsoleLog = vi.hoisted(() => vi.fn());
|
||||
const debugLogger = vi.hoisted(() => ({
|
||||
log: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('log', format(message, ...args));
|
||||
}),
|
||||
error: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('error', format(message, ...args));
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
coreEvents: {
|
||||
emitConsoleLog,
|
||||
},
|
||||
debugLogger,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
vi.mock('../../config/extensions/consent.js', () => ({
|
||||
requestConsentNonInteractive: vi.fn(),
|
||||
}));
|
||||
@@ -45,16 +49,9 @@ describe('extensions list command', () => {
|
||||
const mockLoadSettings = vi.mocked(loadSettings);
|
||||
const mockGetErrorMessage = vi.mocked(getErrorMessage);
|
||||
const mockExtensionManager = vi.mocked(ExtensionManager);
|
||||
interface MockDebugLogger {
|
||||
log: Mock;
|
||||
error: Mock;
|
||||
}
|
||||
let mockDebugLogger: MockDebugLogger;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
mockDebugLogger = (await import('@google/gemini-cli-core'))
|
||||
.debugLogger as unknown as MockDebugLogger;
|
||||
mockLoadSettings.mockReturnValue({
|
||||
merged: {},
|
||||
} as unknown as LoadedSettings);
|
||||
@@ -72,7 +69,8 @@ describe('extensions list command', () => {
|
||||
.mockResolvedValue([]);
|
||||
await handleList();
|
||||
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'No extensions installed.',
|
||||
);
|
||||
mockCwd.mockRestore();
|
||||
@@ -92,7 +90,8 @@ describe('extensions list command', () => {
|
||||
);
|
||||
await handleList();
|
||||
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'ext1@1.0.0\n\next2@2.0.0',
|
||||
);
|
||||
mockCwd.mockRestore();
|
||||
@@ -112,7 +111,10 @@ describe('extensions list command', () => {
|
||||
|
||||
await handleList();
|
||||
|
||||
expect(mockDebugLogger.error).toHaveBeenCalledWith('List failed message');
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'List failed message',
|
||||
);
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
||||
mockProcessExit.mockRestore();
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { format } from 'node:util';
|
||||
import { type CommandModule, type Argv } from 'yargs';
|
||||
import { handleUninstall, uninstallCommand } from './uninstall.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
@@ -43,19 +44,31 @@ vi.mock('../../config/extension-manager.js', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
// Mock dependencies
|
||||
const emitConsoleLog = vi.hoisted(() => vi.fn());
|
||||
const debugLogger = vi.hoisted(() => ({
|
||||
log: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('log', format(message, ...args));
|
||||
}),
|
||||
error: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('error', format(message, ...args));
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
coreEvents: {
|
||||
emitConsoleLog,
|
||||
},
|
||||
debugLogger,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
vi.mock('../../config/extensions/consent.js', () => ({
|
||||
requestConsentNonInteractive: vi.fn(),
|
||||
}));
|
||||
@@ -67,15 +80,8 @@ describe('extensions uninstall command', () => {
|
||||
const mockLoadSettings = vi.mocked(loadSettings);
|
||||
const mockGetErrorMessage = vi.mocked(getErrorMessage);
|
||||
const mockExtensionManager = vi.mocked(ExtensionManager);
|
||||
interface MockDebugLogger {
|
||||
log: Mock;
|
||||
error: Mock;
|
||||
}
|
||||
let mockDebugLogger: MockDebugLogger;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockDebugLogger = (await import('@google/gemini-cli-core'))
|
||||
.debugLogger as unknown as MockDebugLogger;
|
||||
mockLoadSettings.mockReturnValue({
|
||||
merged: {},
|
||||
} as unknown as LoadedSettings);
|
||||
@@ -104,7 +110,8 @@ describe('extensions uninstall command', () => {
|
||||
'my-extension',
|
||||
false,
|
||||
);
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "my-extension" successfully uninstalled.',
|
||||
);
|
||||
mockCwd.mockRestore();
|
||||
@@ -120,13 +127,16 @@ describe('extensions uninstall command', () => {
|
||||
expect(mockUninstallExtension).toHaveBeenCalledWith('ext1', false);
|
||||
expect(mockUninstallExtension).toHaveBeenCalledWith('ext2', false);
|
||||
expect(mockUninstallExtension).toHaveBeenCalledWith('ext3', false);
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "ext1" successfully uninstalled.',
|
||||
);
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "ext2" successfully uninstalled.',
|
||||
);
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "ext3" successfully uninstalled.',
|
||||
);
|
||||
mockCwd.mockRestore();
|
||||
@@ -152,13 +162,16 @@ describe('extensions uninstall command', () => {
|
||||
await handleUninstall({ names: ['ext1', 'ext2', 'ext3'] });
|
||||
|
||||
expect(mockUninstallExtension).toHaveBeenCalledTimes(3);
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "ext1" successfully uninstalled.',
|
||||
);
|
||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'Failed to uninstall "ext2": Extension not found',
|
||||
);
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'Extension "ext3" successfully uninstalled.',
|
||||
);
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
||||
@@ -180,10 +193,12 @@ describe('extensions uninstall command', () => {
|
||||
|
||||
await handleUninstall({ names: ['ext1', 'ext2'] });
|
||||
|
||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'Failed to uninstall "ext1": Extension not found',
|
||||
);
|
||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'Failed to uninstall "ext2": Extension not found',
|
||||
);
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
||||
@@ -204,7 +219,8 @@ describe('extensions uninstall command', () => {
|
||||
|
||||
await handleUninstall({ names: ['my-extension'] });
|
||||
|
||||
expect(mockDebugLogger.error).toHaveBeenCalledWith(
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith(
|
||||
'error',
|
||||
'Initialization failed message',
|
||||
);
|
||||
expect(mockProcessExit).toHaveBeenCalledWith(1);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { format } from 'node:util';
|
||||
import { type CommandModule, type Argv } from 'yargs';
|
||||
import { handleUpdate, updateCommand } from './update.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
@@ -22,22 +23,33 @@ import * as github from '../../config/extensions/github.js';
|
||||
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
vi.mock('../../config/extensions/update.js');
|
||||
vi.mock('../../config/extensions/github.js');
|
||||
const emitConsoleLog = vi.hoisted(() => vi.fn());
|
||||
const debugLogger = vi.hoisted(() => ({
|
||||
log: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('log', format(message, ...args));
|
||||
}),
|
||||
error: vi.fn((message, ...args) => {
|
||||
emitConsoleLog('error', format(message, ...args));
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
debugLogger: {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
coreEvents: {
|
||||
emitConsoleLog,
|
||||
},
|
||||
debugLogger,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../config/extension-manager.js');
|
||||
vi.mock('../../config/settings.js');
|
||||
vi.mock('../../utils/errors.js');
|
||||
vi.mock('../../config/extensions/update.js');
|
||||
vi.mock('../../config/extensions/github.js');
|
||||
vi.mock('../../config/extensions/consent.js', () => ({
|
||||
requestConsentNonInteractive: vi.fn(),
|
||||
}));
|
||||
@@ -57,16 +69,8 @@ describe('extensions update command', () => {
|
||||
update.updateAllUpdatableExtensions,
|
||||
);
|
||||
|
||||
interface MockDebugLogger {
|
||||
log: Mock;
|
||||
error: Mock;
|
||||
}
|
||||
let mockDebugLogger: MockDebugLogger;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
mockDebugLogger = (await import('@google/gemini-cli-core'))
|
||||
.debugLogger as unknown as MockDebugLogger;
|
||||
mockLoadSettings.mockReturnValue({
|
||||
merged: { experimental: { extensionReloading: true } },
|
||||
} as unknown as LoadedSettings);
|
||||
@@ -106,7 +110,7 @@ describe('extensions update command', () => {
|
||||
|
||||
await handleUpdate({ name: 'my-extension' });
|
||||
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(expectedLog);
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith('log', expectedLog);
|
||||
if (shouldCallUpdateExtension) {
|
||||
expect(mockUpdateExtension).toHaveBeenCalled();
|
||||
} else {
|
||||
@@ -141,7 +145,7 @@ describe('extensions update command', () => {
|
||||
|
||||
await handleUpdate({ all: true });
|
||||
|
||||
expect(mockDebugLogger.log).toHaveBeenCalledWith(expectedLog);
|
||||
expect(emitConsoleLog).toHaveBeenCalledWith('log', expectedLog);
|
||||
mockCwd.mockRestore();
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user