@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
text/typescript
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'
);
}
});
});