import { FileWriteTool } from '../../src/tools/builtin/FileWriteTool'; import { ToolContext } from '../../src/tools/base/ToolHandler'; import { promises as fs } from 'fs'; import * as path from 'path'; import * as os from 'os'; // Mock fs module jest.mock('fs', () => ({ promises: { mkdir: jest.fn(), writeFile: jest.fn(), stat: jest.fn(), }, })); describe('FileWriteTool', () => { let tool: FileWriteTool; let tempDir: string; beforeEach(() => { tool = new FileWriteTool(); tempDir = path.join(os.tmpdir(), 'mcp-test'); jest.clearAllMocks(); }); const createContext = (permissions: string[] = ['file:write']): ToolContext => ({ requestId: 'test-request', permissions, }); describe('execute', () => { it('should write content to a file', async () => { const testPath = path.join(tempDir, 'test.txt'); const content = 'Hello, World!'; (fs.mkdir as jest.Mock).mockResolvedValue(undefined); (fs.writeFile as jest.Mock).mockResolvedValue(undefined); (fs.stat as jest.Mock).mockResolvedValue({ size: content.length }); const result = await tool.execute( { path: testPath, content }, createContext(), ); expect(result).toEqual({ path: testPath, size: content.length, mode: 'overwrite', }); expect(fs.mkdir).toHaveBeenCalledWith(tempDir, { recursive: true }); expect(fs.writeFile).toHaveBeenCalledWith(testPath, content, 'utf8'); }); it('should overwrite existing file', async () => { const testPath = path.join(tempDir, 'existing.txt'); const content = 'New content'; (fs.mkdir as jest.Mock).mockResolvedValue(undefined); (fs.writeFile as jest.Mock).mockResolvedValue(undefined); (fs.stat as jest.Mock).mockResolvedValue({ isFile: () => true, size: content.length }); const result = await tool.execute( { path: testPath, content }, createContext(), ); expect(result.mode).toBe('overwrite'); expect(fs.writeFile).toHaveBeenCalledWith(testPath, content, 'utf8'); }); it('should prevent directory traversal', async () => { await expect( tool.execute( { path: '../../../etc/passwd', content: 'malicious' }, createContext(), ), ).rejects.toThrow('Invalid path: directory traversal not allowed'); }); it('should require file:write permission', async () => { await expect( tool.execute( { path: 'test.txt', content: 'test' }, createContext([]), ), ).rejects.toThrow('Permission denied: file:write required'); }); it('should handle binary content', async () => { const testPath = path.join(tempDir, 'binary.bin'); const binaryContent = Buffer.from([0x89, 0x50, 0x4e, 0x47]).toString('base64'); (fs.mkdir as jest.Mock).mockResolvedValue(undefined); (fs.writeFile as jest.Mock).mockResolvedValue(undefined); (fs.stat as jest.Mock).mockResolvedValue({ size: 4 }); // binary data size await tool.execute( { path: testPath, content: binaryContent, encoding: 'base64' }, createContext(), ); expect(fs.writeFile).toHaveBeenCalledWith( testPath, expect.any(Buffer), undefined, ); }); }); });