🐺 Initial commit - Lupul Augmentat MCP Server
- 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
This commit is contained in:
176
tests/tools/FileListTool.test.ts
Normal file
176
tests/tools/FileListTool.test.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { FileListTool } from '../../src/tools/builtin/FileListTool';
|
||||
import { ToolContext } from '../../src/tools/base/ToolHandler';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Mock fs module
|
||||
jest.mock('fs', () => ({
|
||||
promises: {
|
||||
readdir: jest.fn(),
|
||||
stat: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('FileListTool', () => {
|
||||
let tool: FileListTool;
|
||||
|
||||
beforeEach(() => {
|
||||
tool = new FileListTool();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const createContext = (permissions: string[] = ['file:read']): ToolContext => ({
|
||||
requestId: 'test-request',
|
||||
permissions,
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should list files in a directory', async () => {
|
||||
const testPath = './test/dir';
|
||||
|
||||
(fs.readdir as jest.Mock).mockResolvedValue(['file1.txt', 'file2.js', 'subdir']);
|
||||
(fs.stat as jest.Mock)
|
||||
.mockResolvedValueOnce({ isDirectory: () => true })
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 100,
|
||||
mtime: new Date('2025-01-01'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 200,
|
||||
mtime: new Date('2025-01-02'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => true,
|
||||
isFile: () => false,
|
||||
size: 0,
|
||||
mtime: new Date('2025-01-03'),
|
||||
});
|
||||
|
||||
const result = await tool.execute(
|
||||
{ path: testPath },
|
||||
createContext(),
|
||||
);
|
||||
|
||||
expect(result.path).toBe(path.resolve(testPath));
|
||||
const files = result.entries.filter(e => e.type === 'file');
|
||||
const dirs = result.entries.filter(e => e.type === 'directory');
|
||||
expect(files).toHaveLength(2);
|
||||
expect(dirs).toHaveLength(1);
|
||||
expect(files[0]).toMatchObject({
|
||||
name: 'file1.txt',
|
||||
size: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it('should list files recursively', async () => {
|
||||
const testPath = './test/dir';
|
||||
|
||||
// Mock for root directory
|
||||
(fs.readdir as jest.Mock)
|
||||
.mockResolvedValueOnce(['file.txt', 'subdir'])
|
||||
.mockResolvedValueOnce(['nested.txt']);
|
||||
|
||||
(fs.stat as jest.Mock)
|
||||
.mockResolvedValueOnce({ isDirectory: () => true }) // root dir
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 100,
|
||||
mtime: new Date(),
|
||||
}) // file.txt
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => true,
|
||||
isFile: () => false,
|
||||
size: 0,
|
||||
mtime: new Date(),
|
||||
}) // subdir
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 50,
|
||||
mtime: new Date(),
|
||||
}); // nested.txt
|
||||
|
||||
const result = await tool.execute(
|
||||
{ path: testPath, recursive: true },
|
||||
createContext(),
|
||||
);
|
||||
|
||||
const files = result.entries.filter(e => e.type === 'file');
|
||||
expect(files).toHaveLength(2);
|
||||
expect(files.map(f => f.path)).toContain(path.resolve(testPath, 'file.txt'));
|
||||
expect(files.map(f => f.path)).toContain(path.resolve(testPath, 'subdir', 'nested.txt'));
|
||||
});
|
||||
|
||||
it('should filter by pattern', async () => {
|
||||
const testPath = './test/dir';
|
||||
|
||||
(fs.readdir as jest.Mock).mockResolvedValue(['file1.txt', 'file2.js', 'test.txt']);
|
||||
(fs.stat as jest.Mock)
|
||||
.mockResolvedValueOnce({ isDirectory: () => true })
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 100,
|
||||
mtime: new Date(),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 200,
|
||||
mtime: new Date(),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 150,
|
||||
mtime: new Date(),
|
||||
});
|
||||
|
||||
const result = await tool.execute(
|
||||
{ path: testPath, pattern: '*.txt' },
|
||||
createContext(),
|
||||
);
|
||||
|
||||
const files = result.entries.filter(e => e.type === 'file');
|
||||
expect(files).toHaveLength(2);
|
||||
expect(files.every(f => f.name.endsWith('.txt'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should prevent directory traversal', async () => {
|
||||
await expect(
|
||||
tool.execute(
|
||||
{ path: '../../../etc' },
|
||||
createContext(),
|
||||
),
|
||||
).rejects.toThrow('Invalid path: directory traversal not allowed');
|
||||
});
|
||||
|
||||
it('should require file:read permission', async () => {
|
||||
await expect(
|
||||
tool.execute(
|
||||
{ path: './test' },
|
||||
createContext([]),
|
||||
),
|
||||
).rejects.toThrow('Permission denied: file:read required');
|
||||
});
|
||||
|
||||
it('should handle non-existent directory', async () => {
|
||||
(fs.stat as jest.Mock).mockResolvedValue({
|
||||
isDirectory: () => false,
|
||||
isFile: () => false
|
||||
});
|
||||
|
||||
await expect(
|
||||
tool.execute(
|
||||
{ path: './non/existent' },
|
||||
createContext(),
|
||||
),
|
||||
).rejects.toThrow('Path is not a directory');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user