mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-20 02:51:55 -07:00
118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { SandboxedFileSystemService } from './sandboxedFileSystemService.js';
|
|
import type {
|
|
SandboxManager,
|
|
SandboxRequest,
|
|
SandboxedCommand,
|
|
} from './sandboxManager.js';
|
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
import { EventEmitter } from 'node:events';
|
|
import type { Writable } from 'node:stream';
|
|
|
|
vi.mock('node:child_process', () => ({
|
|
spawn: vi.fn(),
|
|
}));
|
|
|
|
class MockSandboxManager implements SandboxManager {
|
|
async prepareCommand(req: SandboxRequest): Promise<SandboxedCommand> {
|
|
return {
|
|
program: 'sandbox.exe',
|
|
args: ['0', req.cwd, req.command, ...req.args],
|
|
env: req.env || {},
|
|
};
|
|
}
|
|
}
|
|
|
|
describe('SandboxedFileSystemService', () => {
|
|
let sandboxManager: MockSandboxManager;
|
|
let service: SandboxedFileSystemService;
|
|
const cwd = '/test/cwd';
|
|
|
|
beforeEach(() => {
|
|
sandboxManager = new MockSandboxManager();
|
|
service = new SandboxedFileSystemService(sandboxManager, cwd);
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should read a file through the sandbox', async () => {
|
|
const mockChild = new EventEmitter() as unknown as ChildProcess;
|
|
Object.assign(mockChild, {
|
|
stdout: new EventEmitter(),
|
|
stderr: new EventEmitter(),
|
|
});
|
|
|
|
vi.mocked(spawn).mockReturnValue(mockChild);
|
|
|
|
const readPromise = service.readTextFile('/test/file.txt');
|
|
|
|
// Use setImmediate to ensure events are emitted after the promise starts executing
|
|
setImmediate(() => {
|
|
mockChild.stdout!.emit('data', Buffer.from('file content'));
|
|
mockChild.emit('close', 0);
|
|
});
|
|
|
|
const content = await readPromise;
|
|
expect(content).toBe('file content');
|
|
expect(spawn).toHaveBeenCalledWith(
|
|
'sandbox.exe',
|
|
['0', cwd, '__read', '/test/file.txt'],
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('should write a file through the sandbox', async () => {
|
|
const mockChild = new EventEmitter() as unknown as ChildProcess;
|
|
Object.assign(mockChild, {
|
|
stdin: {
|
|
write: vi.fn(),
|
|
end: vi.fn(),
|
|
} as unknown as Writable,
|
|
stderr: new EventEmitter(),
|
|
});
|
|
|
|
vi.mocked(spawn).mockReturnValue(mockChild);
|
|
|
|
const writePromise = service.writeTextFile('/test/file.txt', 'new content');
|
|
|
|
setImmediate(() => {
|
|
mockChild.emit('close', 0);
|
|
});
|
|
|
|
await writePromise;
|
|
expect(mockChild.stdin!.write).toHaveBeenCalledWith('new content');
|
|
expect(mockChild.stdin!.end).toHaveBeenCalled();
|
|
expect(spawn).toHaveBeenCalledWith(
|
|
'sandbox.exe',
|
|
['0', cwd, '__write', '/test/file.txt'],
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('should reject if sandbox command fails', async () => {
|
|
const mockChild = new EventEmitter() as unknown as ChildProcess;
|
|
Object.assign(mockChild, {
|
|
stdout: new EventEmitter(),
|
|
stderr: new EventEmitter(),
|
|
});
|
|
|
|
vi.mocked(spawn).mockReturnValue(mockChild);
|
|
|
|
const readPromise = service.readTextFile('/test/file.txt');
|
|
|
|
setImmediate(() => {
|
|
mockChild.stderr!.emit('data', Buffer.from('access denied'));
|
|
mockChild.emit('close', 1);
|
|
});
|
|
|
|
await expect(readPromise).rejects.toThrow(
|
|
'Sandbox Error: Command failed with exit code 1. Details: access denied',
|
|
);
|
|
});
|
|
});
|