mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 18:14:29 -07:00
13ccc16457
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
209 lines
6.8 KiB
TypeScript
209 lines
6.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import {
|
|
getProactiveToolSuggestions,
|
|
isNetworkReliantCommand,
|
|
} from './proactivePermissions.js';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import fs from 'node:fs';
|
|
|
|
vi.mock('node:os');
|
|
vi.mock('node:fs', () => ({
|
|
default: {
|
|
promises: {
|
|
access: vi.fn(),
|
|
},
|
|
constants: {
|
|
F_OK: 0,
|
|
},
|
|
},
|
|
promises: {
|
|
access: vi.fn(),
|
|
},
|
|
constants: {
|
|
F_OK: 0,
|
|
},
|
|
}));
|
|
|
|
describe('proactivePermissions', () => {
|
|
const homeDir = '/Users/testuser';
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(os.homedir).mockReturnValue(homeDir);
|
|
vi.mocked(os.platform).mockReturnValue('darwin');
|
|
});
|
|
|
|
describe('isNetworkReliantCommand', () => {
|
|
it('should return true for always-network tools', () => {
|
|
expect(isNetworkReliantCommand('ssh')).toBe(true);
|
|
expect(isNetworkReliantCommand('git')).toBe(true);
|
|
expect(isNetworkReliantCommand('curl')).toBe(true);
|
|
});
|
|
|
|
it('should return true for network-heavy node subcommands', () => {
|
|
expect(isNetworkReliantCommand('npm', 'install')).toBe(true);
|
|
expect(isNetworkReliantCommand('yarn', 'add')).toBe(true);
|
|
expect(isNetworkReliantCommand('bun', '')).toBe(true);
|
|
});
|
|
|
|
it('should return false for local node subcommands', () => {
|
|
expect(isNetworkReliantCommand('npm', 'test')).toBe(false);
|
|
expect(isNetworkReliantCommand('yarn', 'run')).toBe(false);
|
|
});
|
|
|
|
it('should return false for unknown tools', () => {
|
|
expect(isNetworkReliantCommand('ls')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getProactiveToolSuggestions', () => {
|
|
it('should return undefined for unknown tools', async () => {
|
|
expect(await getProactiveToolSuggestions('ls')).toBeUndefined();
|
|
expect(await getProactiveToolSuggestions('node')).toBeUndefined();
|
|
});
|
|
|
|
it('should return permissions for npm if paths exist', async () => {
|
|
vi.mocked(fs.promises.access).mockImplementation(
|
|
(p: fs.PathLike, _mode?: number) => {
|
|
const pathStr = p.toString();
|
|
if (
|
|
pathStr === path.join(homeDir, '.npm') ||
|
|
pathStr === path.join(homeDir, '.cache') ||
|
|
pathStr === path.join(homeDir, '.npmrc')
|
|
) {
|
|
return Promise.resolve();
|
|
}
|
|
return Promise.reject(new Error('ENOENT'));
|
|
},
|
|
);
|
|
|
|
const permissions = await getProactiveToolSuggestions('npm');
|
|
expect(permissions).toBeDefined();
|
|
expect(permissions?.network).toBe(true);
|
|
// .npmrc should be read-only
|
|
expect(permissions?.fileSystem?.read).toContain(
|
|
path.join(homeDir, '.npmrc'),
|
|
);
|
|
expect(permissions?.fileSystem?.write).not.toContain(
|
|
path.join(homeDir, '.npmrc'),
|
|
);
|
|
// .npm should be read-write
|
|
expect(permissions?.fileSystem?.read).toContain(
|
|
path.join(homeDir, '.npm'),
|
|
);
|
|
expect(permissions?.fileSystem?.write).toContain(
|
|
path.join(homeDir, '.npm'),
|
|
);
|
|
// .cache should be read-write
|
|
expect(permissions?.fileSystem?.write).toContain(
|
|
path.join(homeDir, '.cache'),
|
|
);
|
|
// should NOT contain .ssh or .gitconfig for npm
|
|
expect(permissions?.fileSystem?.read).not.toContain(
|
|
path.join(homeDir, '.ssh'),
|
|
);
|
|
});
|
|
|
|
it('should grant network access and suggest primary cache paths even if they do not exist', async () => {
|
|
vi.mocked(fs.promises.access).mockRejectedValue(new Error('ENOENT'));
|
|
const permissions = await getProactiveToolSuggestions('npm');
|
|
expect(permissions).toBeDefined();
|
|
expect(permissions?.network).toBe(true);
|
|
expect(permissions?.fileSystem?.write).toContain(
|
|
path.join(homeDir, '.npm'),
|
|
);
|
|
// .cache is optional and should NOT be included if it doesn't exist
|
|
expect(permissions?.fileSystem?.write).not.toContain(
|
|
path.join(homeDir, '.cache'),
|
|
);
|
|
});
|
|
|
|
it('should suggest .ssh and .gitconfig only for git', async () => {
|
|
vi.mocked(fs.promises.access).mockImplementation(
|
|
(p: fs.PathLike, _mode?: number) => {
|
|
const pathStr = p.toString();
|
|
if (
|
|
pathStr === path.join(homeDir, '.ssh') ||
|
|
pathStr === path.join(homeDir, '.gitconfig')
|
|
) {
|
|
return Promise.resolve();
|
|
}
|
|
return Promise.reject(new Error('ENOENT'));
|
|
},
|
|
);
|
|
|
|
const permissions = await getProactiveToolSuggestions('git');
|
|
expect(permissions?.network).toBe(true);
|
|
expect(permissions?.fileSystem?.read).toContain(
|
|
path.join(homeDir, '.ssh'),
|
|
);
|
|
expect(permissions?.fileSystem?.read).toContain(
|
|
path.join(homeDir, '.gitconfig'),
|
|
);
|
|
});
|
|
|
|
it('should suggest .ssh but NOT .gitconfig for ssh', async () => {
|
|
vi.mocked(fs.promises.access).mockImplementation(
|
|
(p: fs.PathLike, _mode?: number) => {
|
|
const pathStr = p.toString();
|
|
if (pathStr === path.join(homeDir, '.ssh')) {
|
|
return Promise.resolve();
|
|
}
|
|
return Promise.reject(new Error('ENOENT'));
|
|
},
|
|
);
|
|
|
|
const permissions = await getProactiveToolSuggestions('ssh');
|
|
expect(permissions?.network).toBe(true);
|
|
expect(permissions?.fileSystem?.read).toContain(
|
|
path.join(homeDir, '.ssh'),
|
|
);
|
|
expect(permissions?.fileSystem?.read).not.toContain(
|
|
path.join(homeDir, '.gitconfig'),
|
|
);
|
|
});
|
|
|
|
it('should handle Windows specific paths', async () => {
|
|
vi.mocked(os.platform).mockReturnValue('win32');
|
|
const appData = 'C:\\Users\\testuser\\AppData\\Roaming';
|
|
vi.stubEnv('AppData', appData);
|
|
|
|
vi.mocked(fs.promises.access).mockImplementation(
|
|
(p: fs.PathLike, _mode?: number) => {
|
|
const pathStr = p.toString();
|
|
if (pathStr === path.join(appData, 'npm')) {
|
|
return Promise.resolve();
|
|
}
|
|
return Promise.reject(new Error('ENOENT'));
|
|
},
|
|
);
|
|
|
|
const permissions = await getProactiveToolSuggestions('npm.exe');
|
|
expect(permissions).toBeDefined();
|
|
expect(permissions?.fileSystem?.read).toContain(
|
|
path.join(appData, 'npm'),
|
|
);
|
|
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
it('should include bun, pnpm, and yarn specific paths', async () => {
|
|
vi.mocked(fs.promises.access).mockResolvedValue(undefined);
|
|
|
|
const bun = await getProactiveToolSuggestions('bun');
|
|
expect(bun?.fileSystem?.read).toContain(path.join(homeDir, '.bun'));
|
|
expect(bun?.fileSystem?.read).not.toContain(path.join(homeDir, '.yarn'));
|
|
|
|
const yarn = await getProactiveToolSuggestions('yarn');
|
|
expect(yarn?.fileSystem?.read).toContain(path.join(homeDir, '.yarn'));
|
|
});
|
|
});
|
|
});
|