stock-bot/libs/http-client/test/http-client.test.ts

190 lines
5.3 KiB
TypeScript

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();
});
});