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).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).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'); }); });