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