Skip to content
Open
5 changes: 4 additions & 1 deletion packages/cli/src/commands/extensions/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ vi.mock('../utils.js', () => ({

describe('extensions install command', () => {
it('should fail if no source is provided', () => {
const validationParser = yargs([]).command(installCommand).fail(false);
const validationParser = yargs([])
.locale('en')
.command(installCommand)
.fail(false);
expect(() => validationParser.parse('install')).toThrow(
'Not enough non-option arguments: got 0, need at least 1',
);
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/commands/extensions/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ vi.mock('../utils.js', () => ({

describe('extensions validate command', () => {
it('should fail if no path is provided', () => {
const validationParser = yargs([]).command(validateCommand).fail(false);
const validationParser = yargs([])
.locale('en')
.command(validateCommand)
.fail(false);
expect(() => validationParser.parse('validate')).toThrow(
'Not enough non-option arguments: got 0, need at least 1',
);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('mcp command', () => {
});

it('should show help when no subcommand is provided', async () => {
const yargsInstance = yargs();
const yargsInstance = yargs().locale('en');
(mcpCommand.builder as (y: Argv) => Argv)(yargsInstance);

const parser = yargsInstance.command(mcpCommand).help();
Expand Down
76 changes: 76 additions & 0 deletions packages/cli/src/utils/sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,82 @@ describe('sandbox', () => {
await expect(start_sandbox(config)).rejects.toThrow(FatalSandboxError);
});

it('should fall back to embedded profile if the .sb file is missing on disk', async () => {
vi.mocked(os.platform).mockReturnValue('darwin');
vi.mocked(fs.existsSync).mockImplementation((p) =>
String(p).includes(
'gemini-sandbox-macos-permissive-open-a1b2c3d4e5f6.sb',
),
);

const config: SandboxConfig = createMockSandboxConfig({
command: 'sandbox-exec',
image: 'some-image',
});

const onSpy = vi.spyOn(process, 'on');
const offSpy = vi.spyOn(process, 'off');

interface MockProcess extends EventEmitter {
stdout: EventEmitter;
stderr: EventEmitter;
}
const mockSpawnProcess = new EventEmitter() as MockProcess;
mockSpawnProcess.stdout = new EventEmitter();
mockSpawnProcess.stderr = new EventEmitter();
vi.mocked(spawn).mockReturnValue(
mockSpawnProcess as unknown as ReturnType<typeof spawn>,
);

const promise = start_sandbox(config, [], undefined, ['arg1']);

setTimeout(() => {
mockSpawnProcess.emit('close', 0);
}, 10);

await expect(promise).resolves.toBe(0);

// Verify fs.writeFileSync was called with the temp profile file, content, and 0o600 permissions
expect(fs.writeFileSync).toHaveBeenCalledWith(
expect.stringContaining(
'gemini-sandbox-macos-permissive-open-a1b2c3d4e5f6.sb',
),
expect.stringContaining('deny default'),
expect.objectContaining({
encoding: 'utf8',
mode: 0o600,
}),
);

// Verify spawn was called with the temp profile file
expect(spawn).toHaveBeenCalledWith(
'sandbox-exec',
expect.arrayContaining([
'-f',
expect.stringContaining(
'gemini-sandbox-macos-permissive-open-a1b2c3d4e5f6.sb',
),
]),
expect.objectContaining({ stdio: 'inherit' }),
);

// Verify process on/off hooks were called for exit, SIGINT, and SIGTERM cleanups
expect(onSpy).toHaveBeenCalledWith('exit', expect.any(Function));
expect(onSpy).toHaveBeenCalledWith('SIGINT', expect.any(Function));
expect(onSpy).toHaveBeenCalledWith('SIGTERM', expect.any(Function));

expect(offSpy).toHaveBeenCalledWith('exit', expect.any(Function));
expect(offSpy).toHaveBeenCalledWith('SIGINT', expect.any(Function));
expect(offSpy).toHaveBeenCalledWith('SIGTERM', expect.any(Function));

// Verify fs.unlinkSync was called to clean up the temp file
expect(fs.unlinkSync).toHaveBeenCalledWith(
expect.stringContaining(
'gemini-sandbox-macos-permissive-open-a1b2c3d4e5f6.sb',
),
);
});

it('should handle Docker execution', async () => {
const config: SandboxConfig = createMockSandboxConfig({
command: 'docker',
Expand Down
Loading
Loading