Skip to main content
Glama
brendon92

Specialized AI Search Tools

by brendon92
websearch.test.ts12 kB
import { describe, it, expect, jest, beforeEach } from '@jest/globals'; import { WebSearchTool } from '../../src/tools/websearch.js'; import axios from 'axios'; // Mock axios jest.mock('axios'); const mockedAxios = axios as jest.Mocked<typeof axios>; describe('WebSearchTool', () => { let tool: WebSearchTool; beforeEach(() => { tool = new WebSearchTool(); jest.clearAllMocks(); // Reset environment variables delete process.env.BRAVE_API_KEY; delete process.env.GOOGLE_API_KEY; delete process.env.GOOGLE_SEARCH_ENGINE_ID; delete process.env.BING_API_KEY; }); describe('Tool Definition', () => { it('should have correct name', () => { expect(tool.name).toBe('websearch'); }); it('should have description', () => { expect(tool.description).toBeTruthy(); expect(tool.description.length).toBeGreaterThan(0); }); it('should have schema', () => { expect(tool.schema).toBeDefined(); }); }); describe('DuckDuckGo Search', () => { const mockDDGHtml = ` <html> <body> <div class="result"> <h2 class="result__title"> <a href="https://example.com/page1">Test Result 1</a> </h2> <div class="result__snippet">This is a test snippet 1</div> </div> <div class="result"> <h2 class="result__title"> <a href="https://example.com/page2">Test Result 2</a> </h2> <div class="result__snippet">This is a test snippet 2</div> </div> </body> </html> `; beforeEach(() => { mockedAxios.create.mockReturnValue({ post: jest.fn<any>().mockResolvedValue({ data: mockDDGHtml }), } as any); }); it('should search with DuckDuckGo by default', async () => { const result = await tool.run({ query: 'test query', }); expect(result.isError).toBeFalsy(); const results = JSON.parse(result.content[0].text); expect(Array.isArray(results)).toBe(true); expect(results.length).toBeGreaterThan(0); expect(results[0].source).toBe('duckduckgo'); }); it('should extract titles and snippets', async () => { const result = await tool.run({ query: 'test query', engine: 'duckduckgo', }); const results = JSON.parse(result.content[0].text); expect(results[0].title).toBe('Test Result 1'); expect(results[0].snippet).toBe('This is a test snippet 1'); expect(results[0].url).toContain('example.com'); }); it('should respect result limit', async () => { const result = await tool.run({ query: 'test query', limit: 1, }); const results = JSON.parse(result.content[0].text); expect(results.length).toBeLessThanOrEqual(1); }); it('should apply file type filter', async () => { await tool.run({ query: 'test query', filters: { fileType: 'pdf' }, }); const axiosInstance: any = mockedAxios.create.mock.results[0].value; const postCall = axiosInstance.post.mock.calls[0]; const formData = postCall[1]; // Check that filetype: was added to query expect(formData.toString()).toContain('filetype'); }); it('should apply domain filter', async () => { await tool.run({ query: 'test query', filters: { domain: 'github.com' }, }); const axiosInstance: any = mockedAxios.create.mock.results[0].value; const postCall = axiosInstance.post.mock.calls[0]; const formData = postCall[1]; expect(formData.toString()).toContain('site'); }); it('should apply exact phrase filter', async () => { await tool.run({ query: 'test query', filters: { exactPhrase: true }, }); const axiosInstance: any = mockedAxios.create.mock.results[0].value; const postCall = axiosInstance.post.mock.calls[0]; const formData = postCall[1]; // Query should be wrapped in quotes expect(formData.toString()).toContain('"test query"'); }); }); describe('Brave Search', () => { const mockBraveResponse = { web: { results: [ { title: 'Brave Result 1', url: 'https://example.com/brave1', description: 'Brave snippet 1', }, { title: 'Brave Result 2', url: 'https://example.com/brave2', description: 'Brave snippet 2', }, ], }, }; beforeEach(() => { process.env.BRAVE_API_KEY = 'test-brave-key'; mockedAxios.create.mockReturnValue({ get: jest.fn<any>().mockResolvedValue({ data: mockBraveResponse }), } as any); }); it('should search with Brave when API key is provided', async () => { const result = await tool.run({ query: 'test query', engine: 'brave', }); expect(result.isError).toBeFalsy(); const results = JSON.parse(result.content[0].text); expect(results[0].source).toBe('brave'); expect(results[0].title).toBe('Brave Result 1'); }); it('should fail without API key', async () => { delete process.env.BRAVE_API_KEY; const result = await tool.run({ query: 'test query', engine: 'brave', }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('BRAVE_API_KEY'); }); it('should send API key in headers', async () => { await tool.run({ query: 'test query', engine: 'brave', }); const axiosInstance: any = mockedAxios.create.mock.results[0].value; const getCall = axiosInstance.get.mock.calls[0]; const config = getCall[1]; expect(config.headers['X-Subscription-Token']).toBe('test-brave-key'); }); }); describe('Google Search', () => { const mockGoogleResponse = { items: [ { title: 'Google Result 1', link: 'https://example.com/google1', snippet: 'Google snippet 1', }, ], }; beforeEach(() => { process.env.GOOGLE_API_KEY = 'test-google-key'; process.env.GOOGLE_SEARCH_ENGINE_ID = 'test-cx'; mockedAxios.create.mockReturnValue({ get: jest.fn<any>().mockResolvedValue({ data: mockGoogleResponse }), } as any); }); it('should search with Google when API keys are provided', async () => { const result = await tool.run({ query: 'test query', engine: 'google', }); expect(result.isError).toBeFalsy(); const results = JSON.parse(result.content[0].text); expect(results[0].source).toBe('google'); }); it('should fail without API keys', async () => { delete process.env.GOOGLE_API_KEY; const result = await tool.run({ query: 'test query', engine: 'google', }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('GOOGLE_API_KEY'); }); it('should apply date range filter', async () => { await tool.run({ query: 'test query', engine: 'google', filters: { dateRange: 'week' }, }); const axiosInstance: any = mockedAxios.create.mock.results[0].value; const getCall = axiosInstance.get.mock.calls[0]; const config = getCall[1]; expect(config.params.dateRestrict).toBe('w1'); }); }); describe('Bing Search', () => { const mockBingResponse = { webPages: { value: [ { name: 'Bing Result 1', url: 'https://example.com/bing1', snippet: 'Bing snippet 1', }, ], }, }; beforeEach(() => { process.env.BING_API_KEY = 'test-bing-key'; mockedAxios.create.mockReturnValue({ get: jest.fn<any>().mockResolvedValue({ data: mockBingResponse }), } as any); }); it('should search with Bing when API key is provided', async () => { const result = await tool.run({ query: 'test query', engine: 'bing', }); expect(result.isError).toBeFalsy(); const results = JSON.parse(result.content[0].text); expect(results[0].source).toBe('bing'); }); it('should fail without API key', async () => { delete process.env.BING_API_KEY; const result = await tool.run({ query: 'test query', engine: 'bing', }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('BING_API_KEY'); }); }); describe('Error Handling', () => { it('should handle network errors', async () => { mockedAxios.create.mockReturnValue({ post: jest.fn<any>().mockRejectedValue(new Error('Network error')), } as any); const result = await tool.run({ query: 'test query', }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Error'); }); it('should validate required query parameter', async () => { const result = await tool.run({ query: '', }); expect(result.isError).toBe(true); }); it('should handle unsupported engine', async () => { const result = await tool.run({ query: 'test', engine: 'invalid' as any, }); expect(result.isError).toBe(true); }); }); describe('Result Normalization', () => { it('should normalize results to common format', async () => { const mockHtml = ` <div class="result"> <h2 class="result__title"> <a href="https://example.com">Title</a> </h2> <div class="result__snippet">Snippet</div> </div> `; mockedAxios.create.mockReturnValue({ post: jest.fn<any>().mockResolvedValue({ data: mockHtml }), } as any); const result = await tool.run({ query: 'test', }); const results = JSON.parse(result.content[0].text); expect(results[0]).toHaveProperty('title'); expect(results[0]).toHaveProperty('url'); expect(results[0]).toHaveProperty('snippet'); expect(results[0]).toHaveProperty('source'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/brendon92/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server