mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-15 08:31:14 -07:00
Introduce GEMINI_CLI_HOME for strict test isolation (#15907)
This commit is contained in:
@@ -7,53 +7,79 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import type { Command } from './types.js';
|
||||
|
||||
describe('CommandRegistry', () => {
|
||||
const mockListExtensionsCommandInstance: Command = {
|
||||
const {
|
||||
mockExtensionsCommand,
|
||||
mockListExtensionsCommand,
|
||||
mockExtensionsCommandInstance,
|
||||
mockListExtensionsCommandInstance,
|
||||
} = vi.hoisted(() => {
|
||||
const listInstance: Command = {
|
||||
name: 'extensions list',
|
||||
description: 'Lists all installed extensions.',
|
||||
execute: vi.fn(),
|
||||
};
|
||||
const mockListExtensionsCommand = vi.fn(
|
||||
() => mockListExtensionsCommandInstance,
|
||||
);
|
||||
|
||||
const mockExtensionsCommandInstance: Command = {
|
||||
const extInstance: Command = {
|
||||
name: 'extensions',
|
||||
description: 'Manage extensions.',
|
||||
execute: vi.fn(),
|
||||
subCommands: [mockListExtensionsCommandInstance],
|
||||
subCommands: [listInstance],
|
||||
};
|
||||
const mockExtensionsCommand = vi.fn(() => mockExtensionsCommandInstance);
|
||||
|
||||
return {
|
||||
mockListExtensionsCommandInstance: listInstance,
|
||||
mockExtensionsCommandInstance: extInstance,
|
||||
mockExtensionsCommand: vi.fn(() => extInstance),
|
||||
mockListExtensionsCommand: vi.fn(() => listInstance),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('./extensions.js', () => ({
|
||||
ExtensionsCommand: mockExtensionsCommand,
|
||||
ListExtensionsCommand: mockListExtensionsCommand,
|
||||
}));
|
||||
|
||||
vi.mock('./init.js', () => ({
|
||||
InitCommand: vi.fn(() => ({
|
||||
name: 'init',
|
||||
description: 'Initializes the server.',
|
||||
execute: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('./restore.js', () => ({
|
||||
RestoreCommand: vi.fn(() => ({
|
||||
name: 'restore',
|
||||
description: 'Restores the server.',
|
||||
execute: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
import { commandRegistry } from './command-registry.js';
|
||||
|
||||
describe('CommandRegistry', () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock('./extensions.js', () => ({
|
||||
ExtensionsCommand: mockExtensionsCommand,
|
||||
ListExtensionsCommand: mockListExtensionsCommand,
|
||||
}));
|
||||
vi.clearAllMocks();
|
||||
commandRegistry.initialize();
|
||||
});
|
||||
|
||||
it('should register ExtensionsCommand on initialization', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
expect(mockExtensionsCommand).toHaveBeenCalled();
|
||||
const command = commandRegistry.get('extensions');
|
||||
expect(command).toBe(mockExtensionsCommandInstance);
|
||||
});
|
||||
|
||||
it('should register sub commands on initialization', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const command = commandRegistry.get('extensions list');
|
||||
expect(command).toBe(mockListExtensionsCommandInstance);
|
||||
});
|
||||
|
||||
it('get() should return undefined for a non-existent command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const command = commandRegistry.get('non-existent');
|
||||
expect(command).toBeUndefined();
|
||||
});
|
||||
|
||||
it('register() should register a new command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const mockCommand: Command = {
|
||||
name: 'test-command',
|
||||
description: '',
|
||||
@@ -65,7 +91,6 @@ describe('CommandRegistry', () => {
|
||||
});
|
||||
|
||||
it('register() should register a nested command', async () => {
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const mockSubSubCommand: Command = {
|
||||
name: 'test-command-sub-sub',
|
||||
description: '',
|
||||
@@ -95,8 +120,8 @@ describe('CommandRegistry', () => {
|
||||
});
|
||||
|
||||
it('register() should not enter an infinite loop with a cyclic command', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const { commandRegistry } = await import('./command-registry.js');
|
||||
const { debugLogger } = await import('@google/gemini-cli-core');
|
||||
const warnSpy = vi.spyOn(debugLogger, 'warn').mockImplementation(() => {});
|
||||
const mockCommand: Command = {
|
||||
name: 'cyclic-command',
|
||||
description: '',
|
||||
@@ -112,7 +137,6 @@ describe('CommandRegistry', () => {
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'Command cyclic-command already registered. Skipping.',
|
||||
);
|
||||
// If the test finishes, it means we didn't get into an infinite loop.
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,10 +10,15 @@ import { InitCommand } from './init.js';
|
||||
import { RestoreCommand } from './restore.js';
|
||||
import type { Command } from './types.js';
|
||||
|
||||
class CommandRegistry {
|
||||
export class CommandRegistry {
|
||||
private readonly commands = new Map<string, Command>();
|
||||
|
||||
constructor() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.commands.clear();
|
||||
this.register(new ExtensionsCommand());
|
||||
this.register(new RestoreCommand());
|
||||
this.register(new InitCommand());
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
import type { TelemetryTarget } from '@google/gemini-cli-core';
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
type ExtensionLoader,
|
||||
startupProfiler,
|
||||
PREVIEW_GEMINI_MODEL,
|
||||
homedir,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
type MCPServerConfig,
|
||||
type ExtensionInstallMetadata,
|
||||
type GeminiCLIExtension,
|
||||
homedir,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
export const EXTENSIONS_DIRECTORY_NAME = path.join(GEMINI_DIR, 'extensions');
|
||||
@@ -39,7 +39,7 @@ interface ExtensionConfig {
|
||||
export function loadExtensions(workspaceDir: string): GeminiCLIExtension[] {
|
||||
const allExtensions = [
|
||||
...loadExtensionsFromDir(workspaceDir),
|
||||
...loadExtensionsFromDir(os.homedir()),
|
||||
...loadExtensionsFromDir(homedir()),
|
||||
];
|
||||
|
||||
const uniqueExtensions: GeminiCLIExtension[] = [];
|
||||
|
||||
@@ -27,13 +27,21 @@ vi.mock('node:os', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@google/gemini-cli-core', () => ({
|
||||
GEMINI_DIR: '.gemini',
|
||||
debugLogger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
getErrorMessage: (error: unknown) => String(error),
|
||||
}));
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
const path = await import('node:path');
|
||||
const os = await import('node:os');
|
||||
return {
|
||||
...actual,
|
||||
GEMINI_DIR: '.gemini',
|
||||
debugLogger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
getErrorMessage: (error: unknown) => String(error),
|
||||
homedir: () => path.join(os.tmpdir(), `gemini-home-${mocks.suffix}`),
|
||||
};
|
||||
});
|
||||
|
||||
describe('loadSettings', () => {
|
||||
const mockHomeDir = path.join(os.tmpdir(), `gemini-home-${mocks.suffix}`);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
import type { MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
GEMINI_DIR,
|
||||
getErrorMessage,
|
||||
type TelemetrySettings,
|
||||
homedir,
|
||||
} from '@google/gemini-cli-core';
|
||||
import stripJsonComments from 'strip-json-comments';
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { gzipSync, gunzipSync } from 'node:zlib';
|
||||
import * as tar from 'tar';
|
||||
import * as fse from 'fs-extra';
|
||||
import { promises as fsPromises, createReadStream } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { tmpdir } from '@google/gemini-cli-core';
|
||||
import { join } from 'node:path';
|
||||
import type { Task as SDKTask } from '@a2a-js/sdk';
|
||||
import type { TaskStore } from '@a2a-js/sdk/server';
|
||||
|
||||
Reference in New Issue
Block a user