import { describe, test, expect, beforeEach } from 'bun:test'; import { HttpClient, RateLimiter, ProxyManager } from '../src/index.js'; import type { RateLimitConfig, ProxyConfig } from '../src/types.js'; import { RateLimitError } from '../src/types.js'; describe('HttpClient', () => { let client: HttpClient; beforeEach(() => { client = new HttpClient(); }); test('should create client with default config', () => { expect(client).toBeInstanceOf(HttpClient); }); test('should make GET request', async () => { // Using a mock endpoint that returns JSON const response = await client.get('https://jsonplaceholder.typicode.com/posts/1'); expect(response.status).toBe(200); expect(response.data).toHaveProperty('id'); expect(response.data).toHaveProperty('title'); }); test('should handle timeout', async () => { const clientWithTimeout = new HttpClient({ timeout: 1 }); // 1ms timeout await expect( clientWithTimeout.get('https://jsonplaceholder.typicode.com/posts/1') ).rejects.toThrow('timeout'); }); test('should handle 404 error', async () => { await expect( client.get('https://jsonplaceholder.typicode.com/posts/999999') ).rejects.toThrow(); }); test('should make POST request with body', async () => { const response = await client.post('https://jsonplaceholder.typicode.com/posts', { title: 'Test Post', body: 'Test body', userId: 1, }); expect(response.status).toBe(201); expect(response.data).toHaveProperty('id'); }); test('should use base URL', async () => { const clientWithBase = new HttpClient({ baseURL: 'https://jsonplaceholder.typicode.com' }); const response = await clientWithBase.get('/posts/1'); expect(response.status).toBe(200); }); test('should merge headers', async () => { const clientWithHeaders = new HttpClient({ defaultHeaders: { 'X-API-Key': 'test-key', } }); const response = await clientWithHeaders.get('https://jsonplaceholder.typicode.com/posts/1', { headers: { 'X-Custom': 'custom-value', } }); expect(response.status).toBe(200); }); }); describe('RateLimiter', () => { let rateLimiter: RateLimiter; const config: RateLimitConfig = { maxRequests: 2, windowMs: 1000, // 1 second }; beforeEach(() => { rateLimiter = new RateLimiter(config); }); test('should allow requests within limit', async () => { await expect(rateLimiter.checkRateLimit()).resolves.toBeUndefined(); rateLimiter.recordRequest(true); await expect(rateLimiter.checkRateLimit()).resolves.toBeUndefined(); rateLimiter.recordRequest(true); }); test('should reject requests exceeding limit', async () => { // Fill up the rate limit await rateLimiter.checkRateLimit(); rateLimiter.recordRequest(true); await rateLimiter.checkRateLimit(); rateLimiter.recordRequest(true); // This should throw await expect(rateLimiter.checkRateLimit()).rejects.toThrow(RateLimitError); }); test('should reset after window', async () => { const shortConfig: RateLimitConfig = { maxRequests: 1, windowMs: 100, // 100ms }; const shortRateLimiter = new RateLimiter(shortConfig); await shortRateLimiter.checkRateLimit(); shortRateLimiter.recordRequest(true); // Should be at limit await expect(shortRateLimiter.checkRateLimit()).rejects.toThrow(RateLimitError); // Wait for window to reset await new Promise(resolve => setTimeout(resolve, 150)); // Should be allowed again await expect(shortRateLimiter.checkRateLimit()).resolves.toBeUndefined(); }); test('should get current count', () => { expect(rateLimiter.getCurrentCount()).toBe(0); rateLimiter.recordRequest(true); expect(rateLimiter.getCurrentCount()).toBe(1); rateLimiter.recordRequest(false); expect(rateLimiter.getCurrentCount()).toBe(2); }); }); describe('ProxyManager', () => { test('should validate proxy config', () => { const validConfig: ProxyConfig = { type: 'http', host: 'proxy.example.com', port: 8080, }; expect(() => ProxyManager.validateConfig(validConfig)).not.toThrow(); }); test('should reject invalid proxy config', () => { const invalidConfig = { type: 'http', host: '', port: 8080, } as ProxyConfig; expect(() => ProxyManager.validateConfig(invalidConfig)).toThrow('Proxy host is required'); }); test('should reject invalid port', () => { const invalidConfig = { type: 'http', host: 'proxy.example.com', port: 70000, } as ProxyConfig; expect(() => ProxyManager.validateConfig(invalidConfig)).toThrow('Invalid proxy port'); }); test('should create HTTP proxy agent', () => { const config: ProxyConfig = { type: 'http', host: 'proxy.example.com', port: 8080, }; const agent = ProxyManager.createAgent(config); expect(agent).toBeDefined(); }); test('should create SOCKS proxy agent', () => { const config: ProxyConfig = { type: 'socks5', host: 'proxy.example.com', port: 1080, }; const agent = ProxyManager.createAgent(config); expect(agent).toBeDefined(); }); });