finished http-client tests
This commit is contained in:
parent
557c157228
commit
1899078523
4 changed files with 204 additions and 46 deletions
|
|
@ -1,7 +1,23 @@
|
|||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { HttpClient } from '../src/index.js';
|
||||
import type { RateLimitConfig } from '../src/types.js';
|
||||
import { MockServer } from './mock-server.js';
|
||||
|
||||
// Global mock server instance
|
||||
let mockServer: MockServer;
|
||||
let mockServerBaseUrl: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Start mock server
|
||||
mockServer = new MockServer();
|
||||
await mockServer.start();
|
||||
mockServerBaseUrl = mockServer.getBaseUrl();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Stop mock server
|
||||
await mockServer.stop();
|
||||
});
|
||||
|
||||
describe('HttpClient Error Handling Integration', () => {
|
||||
let client: HttpClient;
|
||||
|
|
@ -34,25 +50,15 @@ describe('HttpClient Error Handling Integration', () => {
|
|||
console.warn('URL validation test skipped');
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle server errors (5xx)', async () => {
|
||||
try {
|
||||
await expect(
|
||||
client.get('https://httpbin.org/status/500')
|
||||
).rejects.toThrow();
|
||||
} catch (e) {
|
||||
if ((e as Error).message.includes('ENOTFOUND') ||
|
||||
(e as Error).message.includes('Failed to fetch')) {
|
||||
console.warn('Network connectivity issue detected - skipping test');
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
test('should handle server errors (5xx)', async () => {
|
||||
await expect(
|
||||
client.get(`${mockServerBaseUrl}/status/500`)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should treat 404 as error by default', async () => {
|
||||
await expect(
|
||||
client.get('https://httpbin.org/status/404')
|
||||
client.get(`${mockServerBaseUrl}/status/404`)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
|
|
@ -62,36 +68,27 @@ describe('HttpClient Error Handling Integration', () => {
|
|||
});
|
||||
|
||||
// This should not throw because we're allowing 404
|
||||
const response = await customClient.get('https://httpbin.org/status/404');
|
||||
const response = await customClient.get(`${mockServerBaseUrl}/status/404`);
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HttpClient Authentication Integration', () => {
|
||||
test('should handle basic authentication', async () => {
|
||||
try {
|
||||
const client = new HttpClient({
|
||||
timeout: 10000 // Longer timeout for network stability
|
||||
});
|
||||
|
||||
const response = await client.get('https://httpbin.org/basic-auth/user/passwd', {
|
||||
auth: {
|
||||
username: 'user',
|
||||
password: 'passwd'
|
||||
}
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data).toHaveProperty('authenticated', true);
|
||||
expect(response.data).toHaveProperty('user', 'user');
|
||||
} catch (e) {
|
||||
if ((e as Error).message.includes('ENOTFOUND') ||
|
||||
(e as Error).message.includes('Failed to fetch')) {
|
||||
console.warn('Network connectivity issue detected - skipping test');
|
||||
} else {
|
||||
throw e;
|
||||
const client = new HttpClient({
|
||||
timeout: 10000 // Longer timeout for network stability
|
||||
});
|
||||
|
||||
const response = await client.get(`${mockServerBaseUrl}/basic-auth/user/passwd`, {
|
||||
auth: {
|
||||
username: 'user',
|
||||
password: 'passwd'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data).toHaveProperty('authenticated', true);
|
||||
expect(response.data).toHaveProperty('user', 'user');
|
||||
});
|
||||
|
||||
test('should handle bearer token authentication', async () => {
|
||||
|
|
@ -101,24 +98,23 @@ describe('HttpClient Authentication Integration', () => {
|
|||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
const response = await client.get('https://httpbin.org/headers');
|
||||
|
||||
const response = await client.get(`${mockServerBaseUrl}/headers`);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data.headers).toHaveProperty('Authorization', `Bearer ${token}`);
|
||||
expect(response.data.headers).toHaveProperty('authorization', `bearer ${token}`);
|
||||
});
|
||||
|
||||
test('should handle custom authentication headers', async () => {
|
||||
const apiKey = 'api-key-123456';
|
||||
const client = new HttpClient({
|
||||
defaultHeaders: {
|
||||
'X-API-Key': apiKey
|
||||
'x-api-key': apiKey
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const response = await client.get('https://httpbin.org/headers');
|
||||
|
||||
const response = await client.get(`${mockServerBaseUrl}/headers`);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data.headers).toHaveProperty('X-Api-Key', apiKey);
|
||||
expect(response.data.headers).toHaveProperty('x-api-key', apiKey);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
49
libs/http-client/test/mock-server.test.ts
Normal file
49
libs/http-client/test/mock-server.test.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { MockServer } from './mock-server.js';
|
||||
|
||||
describe('Mock Server Tests', () => {
|
||||
let mockServer: MockServer;
|
||||
let baseUrl: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
mockServer = new MockServer();
|
||||
await mockServer.start();
|
||||
baseUrl = mockServer.getBaseUrl();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mockServer.stop();
|
||||
});
|
||||
|
||||
test('should return status codes', async () => {
|
||||
const response = await fetch(`${baseUrl}/status/200`);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const response404 = await fetch(`${baseUrl}/status/404`);
|
||||
expect(response404.status).toBe(404);
|
||||
});
|
||||
|
||||
test('should handle headers endpoint', async () => {
|
||||
const response = await fetch(`${baseUrl}/headers`, {
|
||||
headers: {
|
||||
'X-Test-Header': 'test-value'
|
||||
}
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
const data = await response.json();
|
||||
expect(data.headers['x-test-header']).toBe('test-value');
|
||||
});
|
||||
|
||||
test('should handle basic auth', async () => {
|
||||
const credentials = btoa('user:pass');
|
||||
const response = await fetch(`${baseUrl}/basic-auth/user/pass`, {
|
||||
headers: {
|
||||
'Authorization': `Basic ${credentials}`
|
||||
}
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
const data = await response.json();
|
||||
expect(data.authenticated).toBe(true);
|
||||
expect(data.user).toBe('user');
|
||||
});
|
||||
});
|
||||
114
libs/http-client/test/mock-server.ts
Normal file
114
libs/http-client/test/mock-server.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* Mock HTTP server for testing the HTTP client
|
||||
* Replaces external dependency on httpbin.org with a local server
|
||||
*/
|
||||
export class MockServer {
|
||||
private server: ReturnType<typeof Bun.serve> | null = null;
|
||||
private port: number = 0;
|
||||
|
||||
/**
|
||||
* Start the mock server on a random port
|
||||
*/
|
||||
async start(): Promise<void> {
|
||||
this.server = Bun.serve({
|
||||
port: 1, // Use any available port
|
||||
fetch: this.handleRequest.bind(this),
|
||||
error: this.handleError.bind(this),
|
||||
});
|
||||
|
||||
this.port = this.server.port || 1;
|
||||
console.log(`Mock server started on port ${this.port}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the mock server
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
if (this.server) {
|
||||
this.server.stop(true);
|
||||
this.server = null;
|
||||
this.port = 0;
|
||||
console.log('Mock server stopped');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL of the mock server
|
||||
*/
|
||||
getBaseUrl(): string {
|
||||
if (!this.server) {
|
||||
throw new Error('Server not started');
|
||||
}
|
||||
return `http://localhost:${this.port}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming requests
|
||||
*/ private async handleRequest(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url);
|
||||
const path = url.pathname;
|
||||
|
||||
console.log(`Mock server handling request: ${req.method} ${path}`);
|
||||
|
||||
// Status endpoints
|
||||
if (path.startsWith('/status/')) {
|
||||
const status = parseInt(path.replace('/status/', ''), 10);
|
||||
console.log(`Returning status: ${status}`);
|
||||
return new Response(null, { status });
|
||||
} // Headers endpoint
|
||||
if (path === '/headers') {
|
||||
const headers = Object.fromEntries([...req.headers.entries()]);
|
||||
console.log('Headers endpoint called, received headers:', headers);
|
||||
return Response.json({ headers });
|
||||
} // Basic auth endpoint
|
||||
if (path.startsWith('/basic-auth/')) {
|
||||
const parts = path.split('/').filter(Boolean);
|
||||
const expectedUsername = parts[1];
|
||||
const expectedPassword = parts[2];
|
||||
console.log(`Basic auth endpoint called: expected user=${expectedUsername}, pass=${expectedPassword}`);
|
||||
|
||||
const authHeader = req.headers.get('authorization');
|
||||
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
||||
console.log('Missing or invalid Authorization header');
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
const base64Credentials = authHeader.split(' ')[1];
|
||||
const credentials = atob(base64Credentials);
|
||||
const [username, password] = credentials.split(':');
|
||||
|
||||
if (username === expectedUsername && password === expectedPassword) {
|
||||
return Response.json({
|
||||
authenticated: true,
|
||||
user: username
|
||||
});
|
||||
}
|
||||
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
// Echo request body
|
||||
if (path === '/post' && req.method === 'POST') {
|
||||
const data = await req.json();
|
||||
return Response.json({
|
||||
data,
|
||||
headers: Object.fromEntries([...req.headers.entries()]),
|
||||
method: req.method
|
||||
});
|
||||
}
|
||||
|
||||
// Default response
|
||||
return Response.json({
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
headers: Object.fromEntries([...req.headers.entries()])
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle errors
|
||||
*/
|
||||
private handleError(error: Error): Response {
|
||||
return new Response('Server error', { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.2",
|
||||
"jest": "^29.5.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue