- MCP server cu stdio transport pentru performanță maximă
- Tool-uri pentru file operations, HTTP requests, system commands
- Suport NATS pentru comunicare inter-module
- Configurare nginx cu API key auth și SSL
- Arhitectură modulară și extensibilă
🤖 Generated with Claude Code
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|
|
}); |