import axios from 'axios'; import { createSearchAPI } from './search'; jest.mock('axios'); const mockedAxios = axios as jest.Mocked; describe('createSearchAPI', () => { beforeEach(() => { jest.clearAllMocks(); }); const mockSerperResponse = { data: { organic: [ { position: 1, title: 'Test', link: 'https://example.com', snippet: 'test', }, ], topStories: [], images: [], videos: [], news: [], peopleAlsoAsk: [], relatedSearches: [], }, }; describe('domainBlocklist', () => { it('appends -site: operators to query for blocked domains', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', domainBlocklist: ['reddit.com', 'twitter.com'], }); await api.getSources({ query: 'test query' }); expect(mockedAxios.post).toHaveBeenCalledTimes(1); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query -site:reddit.com -site:twitter.com'); }); it('does not modify query when domainBlocklist is empty', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', domainBlocklist: [], }); await api.getSources({ query: 'test query' }); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query'); }); }); describe('countryBlocklist', () => { it('appends -site:.XX operators to query for blocked country TLDs', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', countryBlocklist: ['ru', 'ir'], }); await api.getSources({ query: 'test query' }); expect(mockedAxios.post).toHaveBeenCalledTimes(1); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query -site:.ru -site:.ir'); }); it('handles country codes with leading dots', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', countryBlocklist: ['.cn', '.ru'], }); await api.getSources({ query: 'test query' }); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query -site:.cn -site:.ru'); }); it('lowercases country codes', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', countryBlocklist: ['RU', 'IR'], }); await api.getSources({ query: 'test query' }); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query -site:.ru -site:.ir'); }); it('does not modify query when countryBlocklist is empty', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', countryBlocklist: [], }); await api.getSources({ query: 'test query' }); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query'); }); it('does not modify query when countryBlocklist is undefined', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', }); await api.getSources({ query: 'test query' }); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query'); }); }); describe('domainBlocklist + countryBlocklist combined', () => { it('appends both domain and country exclusions to query', async () => { mockedAxios.post.mockResolvedValueOnce(mockSerperResponse); const api = createSearchAPI({ searchProvider: 'serper', serperApiKey: 'test-key', domainBlocklist: ['reddit.com'], countryBlocklist: ['ru', 'ir'], }); await api.getSources({ query: 'test query' }); const payload = mockedAxios.post.mock.calls[0][1] as { q: string }; expect(payload.q).toBe('test query -site:reddit.com -site:.ru -site:.ir'); }); }); describe('searxng provider', () => { it('does not apply countryBlocklist to searxng (only serper)', async () => { mockedAxios.get.mockResolvedValueOnce({ data: { results: [], suggestions: [] }, }); const api = createSearchAPI({ searchProvider: 'searxng', searxngInstanceUrl: 'http://localhost:8080', countryBlocklist: ['ru', 'ir'], }); await api.getSources({ query: 'test query' }); expect(mockedAxios.get).toHaveBeenCalledTimes(1); const params = mockedAxios.get.mock.calls[0][1]?.params as { q: string }; expect(params.q).toBe('test query'); }); }); });