search-exceptions.test.ts•3.62 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { DuckDuckGoSearcher } from '@/search.js';
import { RatelimitException, DuckDuckGoSearchException } from '@/types.js';
// Mock fetch globally
const mockFetch = vi.fn();
global.fetch = mockFetch;
describe('DuckDuckGoSearcher Exception Handling', () => {
let searcher: DuckDuckGoSearcher;
beforeEach(() => {
searcher = new DuckDuckGoSearcher();
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Rate limit exceptions', () => {
const rateLimitCodes = [202, 301, 403, 400, 429, 418];
rateLimitCodes.forEach(statusCode => {
it(`should throw RatelimitException for status code ${statusCode}`, async () => {
mockFetch.mockResolvedValueOnce({
status: statusCode,
statusText: `Status ${statusCode}`,
text: vi.fn().mockResolvedValue('<html></html>'),
ok: false,
});
await expect(searcher.search('test query')).rejects.toThrow(RatelimitException);
});
});
});
describe('DuckDuckGo search exceptions', () => {
const errorCodes = [404, 500, 502, 503, 504, 422, 401];
errorCodes.forEach(statusCode => {
it(`should retry and eventually throw for status code ${statusCode}`, async () => {
// Mock multiple failed responses (will be retried)
mockFetch.mockResolvedValue({
status: statusCode,
statusText: `Status ${statusCode}`,
text: vi.fn().mockResolvedValue('<html></html>'),
ok: false,
});
await expect(searcher.search('test query')).rejects.toThrow('Search failed after');
});
});
});
describe('Successful requests', () => {
it('should not throw exceptions for status code 200', async () => {
const mockHtml = `
<html>
<body>
<div id="links" class="results">
<div class="result">
<h2 class="result__title">
<a class="result__a" href="/l/?uddg=https%3A//example.com">Test Result</a>
</h2>
<div class="result__snippet">Test snippet</div>
</div>
</div>
</body>
</html>
`;
mockFetch.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
text: vi.fn().mockResolvedValue(mockHtml),
ok: true,
});
const result = await searcher.search('test query');
expect(result).toBeDefined();
expect(result.results).toBeDefined();
expect(Array.isArray(result.results)).toBe(true);
});
});
describe('Exception inheritance', () => {
it('RatelimitException should inherit from Error', () => {
const exception = new RatelimitException(429);
expect(exception).toBeInstanceOf(Error);
expect(exception.name).toBe('RatelimitException');
expect(exception.statusCode).toBe(429);
});
it('DuckDuckGoSearchException should inherit from Error', () => {
const exception = new DuckDuckGoSearchException(500);
expect(exception).toBeInstanceOf(Error);
expect(exception.name).toBe('DuckDuckGoSearchException');
expect(exception.statusCode).toBe(500);
});
it('should allow custom error messages', () => {
const rateLimit = new RatelimitException(429, 'Custom rate limit message');
expect(rateLimit.message).toBe('Custom rate limit message');
const searchError = new DuckDuckGoSearchException(500, 'Custom search error');
expect(searchError.message).toBe('Custom search error');
});
});
});