mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 01:51:20 -07:00
test(cli): add tests for builtin extension collisions and migration
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{ "name": "my-extension", "version": "1.0.0", "mcpServers": {} }
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
getRealPath,
|
||||
type CustomTheme,
|
||||
IntegrityDataStatus,
|
||||
coreEvents,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
||||
@@ -697,4 +698,97 @@ describe('ExtensionManager', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Builtin Extensions', () => {
|
||||
let builtinExtensionsDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
builtinExtensionsDir = fs.mkdtempSync(
|
||||
path.join(tempHomeDir, 'gemini-cli-test-builtin-'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn and prefer builtin when names collide', async () => {
|
||||
// 1. Create a manual extension named 'test-ext'
|
||||
createExtension({
|
||||
extensionsDir: userExtensionsDir,
|
||||
name: 'test-ext',
|
||||
version: '1.0.0-manual',
|
||||
});
|
||||
|
||||
// 2. Create a builtin extension named 'test-ext'
|
||||
createExtension({
|
||||
extensionsDir: builtinExtensionsDir,
|
||||
name: 'test-ext',
|
||||
version: '1.0.0-builtin',
|
||||
});
|
||||
|
||||
const emitSpy = vi.spyOn(coreEvents, 'emitFeedback');
|
||||
|
||||
// Create a FRESH manager to ensure loadExtensions actually runs
|
||||
const manager = new ExtensionManager({
|
||||
settings: createTestMergedSettings(),
|
||||
workspaceDir: tempWorkspaceDir,
|
||||
requestConsent: vi.fn().mockResolvedValue(true),
|
||||
requestSetting: null,
|
||||
});
|
||||
|
||||
const extensions = await manager.loadExtensions(builtinExtensionsDir);
|
||||
|
||||
// Verify builtin took precedence
|
||||
const testExt = extensions.find((e) => e.name === 'test-ext');
|
||||
expect(testExt).toBeDefined();
|
||||
expect(testExt?.version).toBe('1.0.0-builtin');
|
||||
|
||||
// Verify warning was shown
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
'warning',
|
||||
expect.stringContaining('Extension "test-ext" is now built-in'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when legacy conductor is found while loading sdd builtin', async () => {
|
||||
// 1. Create a manual extension named 'conductor'
|
||||
createExtension({
|
||||
extensionsDir: userExtensionsDir,
|
||||
name: 'conductor',
|
||||
version: '0.1.0',
|
||||
});
|
||||
|
||||
// 2. Create a builtin extension named 'sdd'
|
||||
createExtension({
|
||||
extensionsDir: builtinExtensionsDir,
|
||||
name: 'sdd',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
const emitSpy = vi.spyOn(coreEvents, 'emitFeedback');
|
||||
|
||||
// Create a FRESH manager
|
||||
const manager = new ExtensionManager({
|
||||
settings: createTestMergedSettings(),
|
||||
workspaceDir: tempWorkspaceDir,
|
||||
requestConsent: vi.fn().mockResolvedValue(true),
|
||||
requestSetting: null,
|
||||
});
|
||||
|
||||
const extensions = await manager.loadExtensions(builtinExtensionsDir);
|
||||
|
||||
// Verify both are loaded (logic currently loads both but warns)
|
||||
expect(extensions.find((e) => e.name === 'conductor')).toBeDefined();
|
||||
expect(extensions.find((e) => e.name === 'sdd')).toBeDefined();
|
||||
|
||||
// Verify warning was shown
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
'warning',
|
||||
expect.stringContaining(
|
||||
'The "conductor" extension has been renamed to "sdd"',
|
||||
),
|
||||
);
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
'warning',
|
||||
expect.stringContaining('.gemini/specs/'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -604,7 +604,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
/**
|
||||
* Loads all installed extensions, should only be called once.
|
||||
*/
|
||||
async loadExtensions(): Promise<GeminiCLIExtension[]> {
|
||||
async loadExtensions(builtinDir?: string): Promise<GeminiCLIExtension[]> {
|
||||
if (this.loadedExtensions) {
|
||||
throw new Error('Extensions already loaded, only load extensions once.');
|
||||
}
|
||||
@@ -637,26 +637,30 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
(ext): ext is GeminiCLIExtension => ext !== null,
|
||||
);
|
||||
|
||||
let builtinExtensionsDir = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'extensions',
|
||||
'builtin',
|
||||
);
|
||||
if (!fs.existsSync(builtinExtensionsDir)) {
|
||||
let builtinExtensionsDir = builtinDir;
|
||||
if (!builtinExtensionsDir) {
|
||||
builtinExtensionsDir = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'core',
|
||||
'src',
|
||||
'extensions',
|
||||
'builtin',
|
||||
);
|
||||
if (!fs.existsSync(builtinExtensionsDir)) {
|
||||
builtinExtensionsDir = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'core',
|
||||
'src',
|
||||
'extensions',
|
||||
'builtin',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!process.env['VITEST'] && fs.existsSync(builtinExtensionsDir)) {
|
||||
const loadBuiltins = builtinDir || !process.env['VITEST'];
|
||||
if (loadBuiltins && fs.existsSync(builtinExtensionsDir)) {
|
||||
const builtinSubdirs =
|
||||
await fs.promises.readdir(builtinExtensionsDir);
|
||||
const builtinPromises = builtinSubdirs.map((subdir) => {
|
||||
|
||||
Reference in New Issue
Block a user