- 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
130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
import { HttpRequestTool } from '../../src/tools/builtin/HttpRequestTool';
|
|
import { ToolContext } from '../../src/tools/base/ToolHandler';
|
|
|
|
// Mock fetch
|
|
global.fetch = jest.fn();
|
|
|
|
describe('HttpRequestTool', () => {
|
|
let tool: HttpRequestTool;
|
|
|
|
beforeEach(() => {
|
|
tool = new HttpRequestTool();
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
const createContext = (permissions: string[] = ['network:http']): ToolContext => ({
|
|
requestId: 'test-request',
|
|
permissions,
|
|
});
|
|
|
|
describe('execute', () => {
|
|
it('should make GET request successfully', async () => {
|
|
const mockResponse = {
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers({ 'content-type': 'application/json' }),
|
|
json: async () => ({ data: 'test' }),
|
|
};
|
|
|
|
(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await tool.execute(
|
|
{ url: 'https://api.example.com/data' },
|
|
createContext(),
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
status: 200,
|
|
statusText: 'OK',
|
|
body: { data: 'test' },
|
|
});
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/data',
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should make POST request with JSON body', async () => {
|
|
const mockResponse = {
|
|
status: 201,
|
|
statusText: 'Created',
|
|
headers: new Headers({ 'content-type': 'application/json' }),
|
|
json: async () => ({ id: 123 }),
|
|
};
|
|
|
|
(global.fetch as jest.Mock).mockResolvedValue(mockResponse);
|
|
|
|
const result = await tool.execute(
|
|
{
|
|
url: 'https://api.example.com/users',
|
|
method: 'POST',
|
|
body: { name: 'John Doe' },
|
|
},
|
|
createContext(),
|
|
);
|
|
|
|
expect(result.status).toBe(201);
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/users',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify({ name: 'John Doe' }),
|
|
headers: expect.objectContaining({
|
|
'Content-Type': 'application/json',
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should throw error for internal URLs', async () => {
|
|
await expect(
|
|
tool.execute(
|
|
{ url: 'http://localhost:8080/internal' },
|
|
createContext(),
|
|
),
|
|
).rejects.toThrow('Requests to internal networks are not allowed');
|
|
|
|
await expect(
|
|
tool.execute(
|
|
{ url: 'http://192.168.1.1/admin' },
|
|
createContext(),
|
|
),
|
|
).rejects.toThrow('Requests to internal networks are not allowed');
|
|
});
|
|
|
|
it('should throw error without permission', async () => {
|
|
await expect(
|
|
tool.execute(
|
|
{ url: 'https://api.example.com' },
|
|
createContext([]),
|
|
),
|
|
).rejects.toThrow('Permission denied: network:http required');
|
|
});
|
|
|
|
it('should handle timeout', async () => {
|
|
(global.fetch as jest.Mock).mockImplementation(
|
|
(_url, options) => new Promise((_resolve, reject) => {
|
|
// Simulate AbortController behavior
|
|
if (options?.signal) {
|
|
options.signal.addEventListener('abort', () => {
|
|
const error = new Error('The operation was aborted');
|
|
error.name = 'AbortError';
|
|
reject(error);
|
|
});
|
|
}
|
|
// Never resolve to simulate timeout
|
|
}),
|
|
);
|
|
|
|
await expect(
|
|
tool.execute(
|
|
{ url: 'https://api.example.com', timeout: 100 },
|
|
createContext(),
|
|
),
|
|
).rejects.toThrow('Request timeout after 100ms');
|
|
});
|
|
});
|
|
}); |