Files
gemini-cli/packages/core/src/ide/ide-installer.test.ts
2026-01-07 04:09:39 +00:00

262 lines
7.7 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
vi.mock('node:child_process', async (importOriginal) => {
const actual = await importOriginal();
return {
...(actual as object),
execSync: vi.fn(),
spawnSync: vi.fn(() => ({ status: 0 })),
};
});
vi.mock('node:fs');
vi.mock('node:os');
vi.mock('../utils/paths.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('../utils/paths.js')>();
return {
...actual,
homedir: vi.fn(),
};
});
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { getIdeInstaller } from './ide-installer.js';
import * as child_process from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { IDE_DEFINITIONS, type IdeInfo } from './detect-ide.js';
import { homedir as pathsHomedir } from '../utils/paths.js';
describe('ide-installer', () => {
const HOME_DIR = '/home/user';
beforeEach(() => {
vi.spyOn(os, 'homedir').mockReturnValue(HOME_DIR);
vi.mocked(pathsHomedir).mockReturnValue(HOME_DIR);
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('getIdeInstaller', () => {
it.each([
{ ide: IDE_DEFINITIONS.vscode },
{ ide: IDE_DEFINITIONS.firebasestudio },
])('returns a VsCodeInstaller for "$ide.name"', ({ ide }) => {
const installer = getIdeInstaller(ide);
expect(installer).not.toBeNull();
expect(installer?.install).toEqual(expect.any(Function));
});
it('returns an AntigravityInstaller for "antigravity"', () => {
const installer = getIdeInstaller(IDE_DEFINITIONS.antigravity);
expect(installer).not.toBeNull();
expect(installer?.install).toEqual(expect.any(Function));
});
});
describe('VsCodeInstaller', () => {
function setup({
ide = IDE_DEFINITIONS.vscode,
existsResult = false,
execSync = () => '',
platform = 'linux' as NodeJS.Platform,
}: {
ide?: IdeInfo;
existsResult?: boolean;
execSync?: () => string;
platform?: NodeJS.Platform;
} = {}) {
vi.spyOn(child_process, 'execSync').mockImplementation(execSync);
vi.spyOn(fs, 'existsSync').mockReturnValue(existsResult);
const installer = getIdeInstaller(ide, platform)!;
return { installer };
}
describe('install', () => {
it.each([
{
platform: 'win32' as NodeJS.Platform,
expectedLookupPaths: [
path.join('C:\\Program Files', 'Microsoft VS Code/bin/code.cmd'),
path.join(
HOME_DIR,
'/AppData/Local/Programs/Microsoft VS Code/bin/code.cmd',
),
],
},
{
platform: 'darwin' as NodeJS.Platform,
expectedLookupPaths: [
'/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code',
path.join(HOME_DIR, 'Library/Application Support/Code/bin/code'),
],
},
{
platform: 'linux' as NodeJS.Platform,
expectedLookupPaths: ['/usr/share/code/bin/code'],
},
])(
'identifies the path to code cli on platform: $platform',
async ({ platform, expectedLookupPaths }) => {
const { installer } = setup({
platform,
execSync: () => {
throw new Error('Command not found'); // `code` is not in PATH
},
});
await installer.install();
for (const [idx, path] of expectedLookupPaths.entries()) {
expect(fs.existsSync).toHaveBeenNthCalledWith(idx + 1, path);
}
},
);
it('installs the extension using code cli', async () => {
const { installer } = setup({
platform: 'linux',
});
await installer.install();
expect(child_process.spawnSync).toHaveBeenCalledWith(
'code',
[
'--install-extension',
'google.gemini-cli-vscode-ide-companion',
'--force',
],
{ stdio: 'pipe', shell: false },
);
});
it('installs the extension using code cli on windows', async () => {
const { installer } = setup({
platform: 'win32',
execSync: () => 'C:\\Program Files\\Microsoft VS Code\\bin\\code.cmd',
});
await installer.install();
expect(child_process.spawnSync).toHaveBeenCalledWith(
'C:\\Program Files\\Microsoft VS Code\\bin\\code.cmd',
[
'--install-extension',
'google.gemini-cli-vscode-ide-companion',
'--force',
],
{ stdio: 'pipe', shell: true },
);
});
it.each([
{
ide: IDE_DEFINITIONS.vscode,
expectedMessage:
'VS Code companion extension was installed successfully',
},
{
ide: IDE_DEFINITIONS.firebasestudio,
expectedMessage:
'Firebase Studio companion extension was installed successfully',
},
])(
'returns that the cli was installed successfully',
async ({ ide, expectedMessage }) => {
const { installer } = setup({ ide });
const result = await installer.install();
expect(result.success).toBe(true);
expect(result.message).toContain(expectedMessage);
},
);
it.each([
{
ide: IDE_DEFINITIONS.vscode,
expectedErr: 'VS Code CLI not found',
},
{
ide: IDE_DEFINITIONS.firebasestudio,
expectedErr: 'Firebase Studio CLI not found',
},
])(
'should return a failure message if $ide is not installed',
async ({ ide, expectedErr }) => {
const { installer } = setup({
ide,
execSync: () => {
throw new Error('Command not found');
},
existsResult: false,
});
const result = await installer.install();
expect(result.success).toBe(false);
expect(result.message).toContain(expectedErr);
},
);
});
});
});
describe('AntigravityInstaller', () => {
function setup({
execSync = () => '',
platform = 'linux' as NodeJS.Platform,
}: {
execSync?: () => string;
platform?: NodeJS.Platform;
} = {}) {
vi.spyOn(child_process, 'execSync').mockImplementation(execSync);
const installer = getIdeInstaller(IDE_DEFINITIONS.antigravity, platform)!;
return { installer };
}
it('installs the extension using the alias', async () => {
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', 'agy');
const { installer } = setup({});
const result = await installer.install();
expect(result.success).toBe(true);
expect(child_process.spawnSync).toHaveBeenCalledWith(
'agy',
[
'--install-extension',
'google.gemini-cli-vscode-ide-companion',
'--force',
],
{ stdio: 'pipe', shell: false },
);
});
it('returns a failure message if the alias is not set', async () => {
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', '');
const { installer } = setup({});
const result = await installer.install();
expect(result.success).toBe(false);
expect(result.message).toContain(
'ANTIGRAVITY_CLI_ALIAS environment variable not set',
);
});
it('returns a failure message if the command is not found', async () => {
vi.stubEnv('ANTIGRAVITY_CLI_ALIAS', 'not-a-command');
const { installer } = setup({
execSync: () => {
throw new Error('Command not found');
},
});
const result = await installer.install();
expect(result.success).toBe(false);
expect(result.message).toContain('not-a-command not found');
});
});