feat: add sandboxed tool wrappers for shell.exec and process.start
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { createSandboxedShellTool, createSandboxedProcessStartTool } from './tools.js';
|
||||
import type { DockerSandbox } from './docker.js';
|
||||
|
||||
function mockSandbox(): DockerSandbox {
|
||||
return {
|
||||
exec: vi.fn().mockResolvedValue({ stdout: 'output', stderr: '' }),
|
||||
create: vi.fn(),
|
||||
destroy: vi.fn(),
|
||||
containerId: 'test-container',
|
||||
containerName: 'flynn-test',
|
||||
config: {},
|
||||
} as unknown as DockerSandbox;
|
||||
}
|
||||
|
||||
describe('createSandboxedShellTool', () => {
|
||||
let sandbox: DockerSandbox;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = mockSandbox();
|
||||
});
|
||||
|
||||
it('has the same name as shell.exec', () => {
|
||||
const tool = createSandboxedShellTool(sandbox);
|
||||
expect(tool.name).toBe('shell.exec');
|
||||
});
|
||||
|
||||
it('delegates to sandbox.exec', async () => {
|
||||
const tool = createSandboxedShellTool(sandbox);
|
||||
const result = await tool.execute({ command: 'echo hello' });
|
||||
|
||||
expect(sandbox.exec).toHaveBeenCalledWith('echo hello', { cwd: undefined, timeout: 30000 });
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.output).toBe('output');
|
||||
});
|
||||
|
||||
it('passes cwd to sandbox.exec', async () => {
|
||||
const tool = createSandboxedShellTool(sandbox);
|
||||
await tool.execute({ command: 'ls', cwd: '/workspace/project' });
|
||||
|
||||
expect(sandbox.exec).toHaveBeenCalledWith('ls', { cwd: '/workspace/project', timeout: 30000 });
|
||||
});
|
||||
|
||||
it('passes timeout to sandbox.exec', async () => {
|
||||
const tool = createSandboxedShellTool(sandbox);
|
||||
await tool.execute({ command: 'sleep 10', timeout: 5000 });
|
||||
|
||||
expect(sandbox.exec).toHaveBeenCalledWith('sleep 10', { cwd: undefined, timeout: 5000 });
|
||||
});
|
||||
|
||||
it('returns error on sandbox.exec failure', async () => {
|
||||
(sandbox.exec as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('container dead'));
|
||||
const tool = createSandboxedShellTool(sandbox);
|
||||
const result = await tool.execute({ command: 'fail' });
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('container dead');
|
||||
});
|
||||
|
||||
it('includes stderr in output', async () => {
|
||||
(sandbox.exec as ReturnType<typeof vi.fn>).mockResolvedValue({ stdout: 'out', stderr: 'warn' });
|
||||
const tool = createSandboxedShellTool(sandbox);
|
||||
const result = await tool.execute({ command: 'cmd' });
|
||||
|
||||
expect(result.output).toContain('out');
|
||||
expect(result.output).toContain('stderr: warn');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSandboxedProcessStartTool', () => {
|
||||
let sandbox: DockerSandbox;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = mockSandbox();
|
||||
});
|
||||
|
||||
it('has the same name as process.start', () => {
|
||||
const tool = createSandboxedProcessStartTool(sandbox);
|
||||
expect(tool.name).toBe('process.start');
|
||||
});
|
||||
|
||||
it('runs detached command via sandbox', async () => {
|
||||
const tool = createSandboxedProcessStartTool(sandbox);
|
||||
const result = await tool.execute({ command: 'npm run dev' });
|
||||
|
||||
expect(sandbox.exec).toHaveBeenCalledWith(
|
||||
expect.stringContaining('npm run dev'),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.output).toContain('Started sandboxed background process');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user