serper-search-scrape-mcp-server
Version:
Serper MCP Server supporting search and webpage scraping
172 lines (171 loc) • 7.04 kB
JavaScript
import { SerperClient } from '../serper-client.js';
import fetch from 'node-fetch';
// Mock node-fetch
jest.mock('node-fetch');
const mockedFetch = fetch;
describe('SerperClient', () => {
const mockApiKey = 'test-api-key';
const mockBaseUrl = 'https://test.serper.dev';
let client;
beforeEach(() => {
client = new SerperClient(mockApiKey, mockBaseUrl);
jest.clearAllMocks();
});
describe('Unit Tests', () => {
describe('search', () => {
const mockSearchParams = {
q: 'test query',
gl: 'us',
hl: 'en'
};
const mockSearchResponse = {
searchParameters: {
q: 'test query',
gl: 'us',
hl: 'en'
},
organic: [
{
title: 'Test Result',
link: 'https://test.com',
snippet: 'Test snippet'
}
]
};
it('should make correct API call and return results', async () => {
mockedFetch.mockResolvedValueOnce({
ok: true,
json: async () => mockSearchResponse
});
const result = await client.search(mockSearchParams);
expect(mockedFetch).toHaveBeenCalledWith(`${mockBaseUrl}/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': mockApiKey
},
body: JSON.stringify(mockSearchParams)
});
expect(result).toEqual(mockSearchResponse);
});
it('should handle API errors correctly', async () => {
const errorMessage = 'API Error';
mockedFetch.mockResolvedValueOnce({
ok: false,
status: 400,
statusText: 'Bad Request',
text: async () => errorMessage
});
await expect(client.search(mockSearchParams))
.rejects
.toThrow(`Serper API error: 400 Bad Request - ${errorMessage}`);
});
it('should handle network errors', async () => {
const networkError = new Error('Network Error');
mockedFetch.mockRejectedValueOnce(networkError);
await expect(client.search(mockSearchParams))
.rejects
.toThrow(networkError);
});
});
describe('batchSearch', () => {
const mockBatchParams = [{
q: 'query1',
gl: 'us',
hl: 'en'
}, {
q: 'query2',
gl: 'uk',
hl: 'en'
}];
const mockBatchResponse = {
results: [
{
searchParameters: { q: 'query1', gl: 'us', hl: 'en' },
organic: [{ title: 'Result 1', link: 'https://test1.com' }]
},
{
searchParameters: { q: 'query2', gl: 'uk', hl: 'en' },
organic: [{ title: 'Result 2', link: 'https://test2.com' }]
}
]
};
it('should make correct batch API call and return results', async () => {
mockedFetch.mockResolvedValueOnce({
ok: true,
json: async () => mockBatchResponse
});
const result = await client.batchSearch(mockBatchParams);
expect(mockedFetch).toHaveBeenCalledWith(`${mockBaseUrl}/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': mockApiKey
},
body: JSON.stringify(mockBatchParams)
});
expect(result).toEqual(mockBatchResponse);
});
it('should handle batch API errors correctly', async () => {
const errorMessage = 'Batch API Error';
mockedFetch.mockResolvedValueOnce({
ok: false,
status: 400,
statusText: 'Bad Request',
text: async () => errorMessage
});
await expect(client.batchSearch(mockBatchParams))
.rejects
.toThrow(`Serper API error: 400 Bad Request - ${errorMessage}`);
});
});
});
describe('Integration Tests', () => {
// Note: These tests require a valid API key and will make real API calls
// They are disabled by default and should be run manually when needed
const realClient = new SerperClient(process.env.SERPER_API_KEY || '');
describe('search', () => {
it.skip('should perform a real search with all optional parameters', async () => {
const params = {
q: 'Latest AI developments',
gl: 'us',
hl: 'en',
num: 5,
tbs: 'qdr:d',
location: 'Silicon Valley',
autocorrect: true
};
const result = await realClient.search(params);
expect(result).toBeDefined();
expect(result.searchParameters).toBeDefined();
expect(result.searchParameters.q).toBe(params.q);
expect(Array.isArray(result.organic)).toBe(true);
expect(result.organic.length).toBeLessThanOrEqual(params.num || 10);
}, 10000);
it.skip('should perform a real batch search with varied parameters', async () => {
const batchParams = [
{
q: 'Web Development 2024',
gl: 'us',
hl: 'en',
num: 3,
tbs: 'qdr:m'
},
{
q: 'Cloud Computing Trends',
gl: 'uk',
hl: 'en',
num: 3,
location: 'London'
}
];
const result = await realClient.batchSearch(batchParams);
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(2);
expect(result[0].searchParameters.q).toBe(batchParams[0].q);
expect(result[1].searchParameters.q).toBe(batchParams[1].q);
}, 15000);
});
});
});