UNPKG

@vepler/http-client

Version:

A flexible and extensible API service library for making HTTP requests with built-in authentication support for bearer tokens and API keys.

309 lines (283 loc) 10.1 kB
import interceptorResponseError from '../../src/interceptors/response-error'; import logger from '@vepler/logger'; import { AxiosError } from 'axios'; import { HttpError, ClientError, ServerError, NetworkError, AuthError, TimeoutError } from '../../src/errors/http-error'; import { AxiosHeaders } from 'axios'; jest.mock('@vepler/logger', () => ({ error: jest.fn(), warn: jest.fn(), info: jest.fn(), debug: jest.fn(), trace: jest.fn(), })); describe('interceptorResponseError', () => { beforeEach(() => { jest.clearAllMocks(); }); test('should handle response errors and return HttpError', async () => { const axiosError = new Error('Request failed with status code 404') as any; axiosError.name = 'AxiosError'; axiosError.isAxiosError = true; axiosError.toJSON = () => ({ error: 'json representation' }); axiosError.response = { status: 404, statusText: 'Not Found', data: { message: 'Resource not found' }, headers: new AxiosHeaders(), config: { url: '/api/users/999', method: 'get', headers: new AxiosHeaders({ 'Content-Type': 'application/json' }) } }; axiosError.config = { url: '/api/users/999', method: 'get', headers: new AxiosHeaders({ 'Content-Type': 'application/json' }) }; try { await interceptorResponseError(axiosError); fail('Promise should be rejected'); } catch (error) { expect(error).toBeInstanceOf(HttpError); expect(error).toBeInstanceOf(ClientError); const httpError = error as HttpError; expect(httpError.status).toBe(404); expect(httpError.message).toContain('404 Not Found'); expect(httpError.endpoint).toBe('/api/users/999'); expect(httpError.method).toBe('GET'); expect(httpError.data).toEqual({ message: 'Resource not found' }); expect(logger.error).toHaveBeenCalledWith( expect.any(Error), 'HTTP Response Error', expect.objectContaining({ status: 404, endpoint: '/api/users/999', errorDetails: expect.any(Object) }) ); } }); test('should handle timeout errors', async () => { const axiosError = new Error('timeout of 3000ms exceeded') as any; axiosError.name = 'AxiosError'; axiosError.code = 'ECONNABORTED'; axiosError.isAxiosError = true; axiosError.toJSON = () => ({ error: 'json representation' }); axiosError.config = { url: '/api/users', method: 'get', timeout: 3000, headers: new AxiosHeaders({ 'Content-Type': 'application/json' }) }; try { await interceptorResponseError(axiosError); fail('Promise should be rejected'); } catch (error) { expect(error).toBeInstanceOf(HttpError); expect(error).toBeInstanceOf(TimeoutError); // TypeScript cast since we know this is a HttpError const httpError = error as HttpError; expect(httpError.message).toContain('Request timeout after 3000ms'); expect(logger.error).toHaveBeenCalledWith( expect.any(Error), 'Request Timeout Error', expect.objectContaining({ timeout: 3000, url: '/api/users', method: 'get' }) ); } }); test('should handle network errors', async () => { const axiosError = new Error('Network Error') as any; axiosError.name = 'AxiosError'; axiosError.isAxiosError = true; axiosError.toJSON = () => ({ error: 'json representation' }); axiosError.request = {}; axiosError.config = { url: '/api/users', method: 'get', headers: new AxiosHeaders({ 'Content-Type': 'application/json' }) }; try { await interceptorResponseError(axiosError); fail('Promise should be rejected'); } catch (error) { expect(error).toBeInstanceOf(HttpError); expect(error).toBeInstanceOf(NetworkError); // TypeScript cast since we know this is a HttpError const httpError = error as HttpError; expect(httpError.message).toContain('Network error'); expect(httpError.endpoint).toBe('/api/users'); expect(httpError.method).toBe('GET'); expect(logger.error).toHaveBeenCalledWith( expect.any(Error), 'Network Error', expect.objectContaining({ url: '/api/users', method: 'get' }) ); } }); test('should handle auth errors', async () => { const axiosError = new Error('Request failed with status code 401') as any; axiosError.name = 'AxiosError'; axiosError.isAxiosError = true; axiosError.toJSON = () => ({ error: 'json representation' }); const headers = new AxiosHeaders({ 'Content-Type': 'application/json', 'Authorization': 'Bearer invalid-token', 'x-api-key': 'invalid-api-key' }); axiosError.response = { status: 401, statusText: 'Unauthorized', data: { message: 'Invalid credentials' }, headers: new AxiosHeaders(), config: { url: '/api/auth', method: 'post', headers } }; axiosError.config = { url: '/api/auth', method: 'post', headers }; try { await interceptorResponseError(axiosError); fail('Promise should be rejected'); } catch (error) { expect(error).toBeInstanceOf(HttpError); expect(error).toBeInstanceOf(AuthError); // TypeScript cast since we know this is an AuthError const httpError = error as AuthError; expect(httpError.status).toBe(401); expect(httpError.message).toContain('401 Unauthorized'); expect(httpError.endpoint).toBe('/api/auth'); expect(httpError.method).toBe('POST'); // Credentials should be defined expect(httpError.credentials).toBeDefined(); expect(httpError.credentials['Authorization']).toBeDefined(); expect(httpError.credentials['x-api-key']).toBeDefined(); expect(logger.error).toHaveBeenCalledWith( expect.any(Error), 'HTTP Response Error', expect.objectContaining({ status: 401, endpoint: '/api/auth', errorDetails: expect.any(Object) }) ); } }); test('should handle server errors', async () => { const axiosError = new Error('Request failed with status code 500') as any; axiosError.name = 'AxiosError'; axiosError.isAxiosError = true; axiosError.toJSON = () => ({ error: 'json representation' }); axiosError.response = { status: 500, statusText: 'Internal Server Error', data: { message: 'Server error occurred' }, headers: new AxiosHeaders(), config: { url: '/api/users', method: 'get', headers: new AxiosHeaders({ 'Content-Type': 'application/json' }) } }; axiosError.config = { url: '/api/users', method: 'get', headers: new AxiosHeaders({ 'Content-Type': 'application/json' }) }; try { await interceptorResponseError(axiosError); fail('Promise should be rejected'); } catch (error) { expect(error).toBeInstanceOf(HttpError); expect(error).toBeInstanceOf(ServerError); // TypeScript cast since we know this is a ServerError const httpError = error as ServerError; expect(httpError.status).toBe(500); expect(httpError.message).toContain('500 Internal Server Error'); expect(httpError.endpoint).toBe('/api/users'); expect(httpError.method).toBe('GET'); expect(logger.error).toHaveBeenCalledWith( expect.any(Error), 'HTTP Response Error', expect.objectContaining({ status: 500, endpoint: '/api/users', errorDetails: expect.any(Object) }) ); } }); test('should handle errors in error processing', async () => { const axiosError = new Error('Request failed with status code 400') as any; axiosError.name = 'AxiosError'; axiosError.isAxiosError = true; axiosError.toJSON = () => ({ error: 'json representation' }); axiosError.response = { status: 400, statusText: 'Bad Request', data: { message: 'Validation failed' }, headers: new AxiosHeaders(), config: { url: '/api/users', method: 'post', headers: new AxiosHeaders({ 'Content-Type': 'application/json' }) } }; // Force an error during error processing axiosError.response!.status = 400; const oldData = axiosError.response!.data; Object.defineProperty(axiosError.response!, 'data', { get: function() { throw new Error('Error parsing error'); }, configurable: true }); try { await interceptorResponseError(axiosError); fail('Promise should be rejected'); } catch (error) { expect(error).toBeInstanceOf(HttpError); // TypeScript cast since we know this is a HttpError const httpError = error as HttpError; expect(httpError.message).toBe(axiosError.message); expect(logger.error).toHaveBeenCalledWith( expect.any(Error), 'Error Processing', expect.objectContaining({ loggingError: expect.any(Object) }) ); } }); test('should handle request setup errors', async () => { const axiosError = new Error('Request setup error') as any; axiosError.name = 'AxiosError'; axiosError.isAxiosError = true; axiosError.code = 'ERR_BAD_REQUEST'; axiosError.toJSON = () => ({ error: 'json representation' }); try { await interceptorResponseError(axiosError); fail('Promise should be rejected'); } catch (error) { expect(error).toBeInstanceOf(HttpError); expect(error).toBeInstanceOf(ClientError); // TypeScript cast since we know this is a ClientError const httpError = error as ClientError; expect(httpError.message).toBe('Request setup error'); expect(logger.error).toHaveBeenCalledWith( expect.any(Error), 'Client Error' ); } }); });