mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(cli): mock fs.readdir in consent tests for Windows compatibility (#15904)
This commit is contained in:
@@ -27,12 +27,26 @@ const mockReadline = vi.hoisted(() => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockReaddir = vi.hoisted(() => vi.fn());
|
||||||
|
const originalReaddir = vi.hoisted(() => ({
|
||||||
|
current: null as typeof fs.readdir | null,
|
||||||
|
}));
|
||||||
|
|
||||||
// Mocking readline for non-interactive prompts
|
// Mocking readline for non-interactive prompts
|
||||||
vi.mock('node:readline', () => ({
|
vi.mock('node:readline', () => ({
|
||||||
default: mockReadline,
|
default: mockReadline,
|
||||||
createInterface: mockReadline.createInterface,
|
createInterface: mockReadline.createInterface,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('node:fs/promises', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('node:fs/promises')>();
|
||||||
|
originalReaddir.current = actual.readdir;
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
readdir: mockReaddir,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||||
const actual =
|
const actual =
|
||||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||||
@@ -49,6 +63,10 @@ describe('consent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
if (originalReaddir.current) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
mockReaddir.mockImplementation(originalReaddir.current as any);
|
||||||
|
}
|
||||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'consent-test-'));
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'consent-test-'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -328,7 +346,7 @@ describe('consent', () => {
|
|||||||
|
|
||||||
it('should show a warning if the skill directory cannot be read', async () => {
|
it('should show a warning if the skill directory cannot be read', async () => {
|
||||||
const lockedDir = path.join(tempDir, 'locked');
|
const lockedDir = path.join(tempDir, 'locked');
|
||||||
await fs.mkdir(lockedDir, { recursive: true, mode: 0o000 });
|
await fs.mkdir(lockedDir, { recursive: true });
|
||||||
|
|
||||||
const skill: SkillDefinition = {
|
const skill: SkillDefinition = {
|
||||||
name: 'locked-skill',
|
name: 'locked-skill',
|
||||||
@@ -337,8 +355,15 @@ describe('consent', () => {
|
|||||||
body: 'body',
|
body: 'body',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mock readdir to simulate a permission error.
|
||||||
|
// We do this instead of using fs.mkdir(..., { mode: 0o000 }) because
|
||||||
|
// directory permissions work differently on Windows and 0o000 doesn't
|
||||||
|
// effectively block access there, leading to test failures in Windows CI.
|
||||||
|
mockReaddir.mockRejectedValueOnce(
|
||||||
|
new Error('EACCES: permission denied, scandir'),
|
||||||
|
);
|
||||||
|
|
||||||
const requestConsent = vi.fn().mockResolvedValue(true);
|
const requestConsent = vi.fn().mockResolvedValue(true);
|
||||||
try {
|
|
||||||
await maybeRequestConsentOrFail(
|
await maybeRequestConsentOrFail(
|
||||||
baseConfig,
|
baseConfig,
|
||||||
requestConsent,
|
requestConsent,
|
||||||
@@ -353,10 +378,6 @@ describe('consent', () => {
|
|||||||
` (Location: ${skill.location}) ${chalk.red('⚠️ (Could not count items in directory)')}`,
|
` (Location: ${skill.location}) ${chalk.red('⚠️ (Could not count items in directory)')}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
// Restore permissions so cleanup works
|
|
||||||
await fs.chmod(lockedDir, 0o700);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ vi.mock('os', () => ({
|
|||||||
homedir: mockHomedir,
|
homedir: mockHomedir,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockSpawnSync = vi.hoisted(() => vi.fn());
|
||||||
|
vi.mock('node:child_process', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import('node:child_process')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
spawnSync: mockSpawnSync,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const mockQuote = vi.hoisted(() => vi.fn());
|
const mockQuote = vi.hoisted(() => vi.fn());
|
||||||
vi.mock('shell-quote', () => ({
|
vi.mock('shell-quote', () => ({
|
||||||
quote: mockQuote,
|
quote: mockQuote,
|
||||||
@@ -464,12 +473,34 @@ describeWindowsOnly('PowerShell integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should block commands when PowerShell parser reports errors', () => {
|
it('should block commands when PowerShell parser reports errors', () => {
|
||||||
|
// Mock spawnSync to avoid the overhead of spawning a real PowerShell process,
|
||||||
|
// which can lead to timeouts in CI environments even on Windows.
|
||||||
|
mockSpawnSync.mockReturnValue({
|
||||||
|
status: 0,
|
||||||
|
stdout: JSON.stringify({ success: false }),
|
||||||
|
});
|
||||||
|
|
||||||
const { allowed, reason } = isCommandAllowed('Get-ChildItem |', config);
|
const { allowed, reason } = isCommandAllowed('Get-ChildItem |', config);
|
||||||
expect(allowed).toBe(false);
|
expect(allowed).toBe(false);
|
||||||
expect(reason).toBe(
|
expect(reason).toBe(
|
||||||
'Command rejected because it could not be parsed safely',
|
'Command rejected because it could not be parsed safely',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow valid commands through PowerShell parser', () => {
|
||||||
|
// Mock spawnSync to avoid the overhead of spawning a real PowerShell process,
|
||||||
|
// which can lead to timeouts in CI environments even on Windows.
|
||||||
|
mockSpawnSync.mockReturnValue({
|
||||||
|
status: 0,
|
||||||
|
stdout: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
commands: [{ name: 'Get-ChildItem', text: 'Get-ChildItem' }],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { allowed } = isCommandAllowed('Get-ChildItem', config);
|
||||||
|
expect(allowed).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isShellInvocationAllowlisted', () => {
|
describe('isShellInvocationAllowlisted', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user