272 lines
11 KiB
TypeScript
272 lines
11 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { ApiError, isApiError, isNetworkError, isAuthError, isRetryableError } from './ApiError';
|
|
import type { ApiErrorType, ApiErrorContext } from './ApiError';
|
|
|
|
describe('ApiError', () => {
|
|
describe('constructor', () => {
|
|
it('should create an ApiError with correct properties', () => {
|
|
const context: ApiErrorContext = {
|
|
endpoint: '/api/test',
|
|
method: 'GET',
|
|
timestamp: '2024-01-01T00:00:00Z',
|
|
statusCode: 500,
|
|
};
|
|
|
|
const error = new ApiError('Test error', 'SERVER_ERROR', context);
|
|
|
|
expect(error.message).toBe('Test error');
|
|
expect(error.type).toBe('SERVER_ERROR');
|
|
expect(error.context).toEqual(context);
|
|
expect(error.name).toBe('ApiError');
|
|
});
|
|
|
|
it('should accept an optional originalError', () => {
|
|
const originalError = new Error('Original');
|
|
const context: ApiErrorContext = { timestamp: '2024-01-01T00:00:00Z' };
|
|
|
|
const error = new ApiError('Wrapped', 'NETWORK_ERROR', context, originalError);
|
|
|
|
expect(error.originalError).toBe(originalError);
|
|
});
|
|
});
|
|
|
|
describe('getUserMessage', () => {
|
|
it('should return correct user message for NETWORK_ERROR', () => {
|
|
const error = new ApiError('Connection failed', 'NETWORK_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('Unable to connect to the server. Please check your internet connection.');
|
|
});
|
|
|
|
it('should return correct user message for AUTH_ERROR', () => {
|
|
const error = new ApiError('Unauthorized', 'AUTH_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('Authentication required. Please log in again.');
|
|
});
|
|
|
|
it('should return correct user message for VALIDATION_ERROR', () => {
|
|
const error = new ApiError('Invalid data', 'VALIDATION_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('The data you provided is invalid. Please check your input.');
|
|
});
|
|
|
|
it('should return correct user message for NOT_FOUND', () => {
|
|
const error = new ApiError('Not found', 'NOT_FOUND', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('The requested resource was not found.');
|
|
});
|
|
|
|
it('should return correct user message for SERVER_ERROR', () => {
|
|
const error = new ApiError('Server error', 'SERVER_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('Server is experiencing issues. Please try again later.');
|
|
});
|
|
|
|
it('should return correct user message for RATE_LIMIT_ERROR', () => {
|
|
const error = new ApiError('Rate limited', 'RATE_LIMIT_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('Too many requests. Please wait a moment and try again.');
|
|
});
|
|
|
|
it('should return correct user message for TIMEOUT_ERROR', () => {
|
|
const error = new ApiError('Timeout', 'TIMEOUT_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('Request timed out. Please try again.');
|
|
});
|
|
|
|
it('should return correct user message for CANCELED_ERROR', () => {
|
|
const error = new ApiError('Canceled', 'CANCELED_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('Request was canceled.');
|
|
});
|
|
|
|
it('should return correct user message for UNKNOWN_ERROR', () => {
|
|
const error = new ApiError('Unknown', 'UNKNOWN_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getUserMessage()).toBe('An unexpected error occurred. Please try again.');
|
|
});
|
|
});
|
|
|
|
describe('getDeveloperMessage', () => {
|
|
it('should return developer message with type and message', () => {
|
|
const error = new ApiError('Test error', 'NETWORK_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getDeveloperMessage()).toBe('[NETWORK_ERROR] Test error');
|
|
});
|
|
|
|
it('should include endpoint and method when available', () => {
|
|
const context: ApiErrorContext = {
|
|
endpoint: '/api/users',
|
|
method: 'POST',
|
|
timestamp: '2024-01-01T00:00:00Z',
|
|
};
|
|
const error = new ApiError('Test error', 'SERVER_ERROR', context);
|
|
expect(error.getDeveloperMessage()).toBe('[SERVER_ERROR] Test error POST /api/users');
|
|
});
|
|
|
|
it('should include status code when available', () => {
|
|
const context: ApiErrorContext = {
|
|
endpoint: '/api/users',
|
|
method: 'GET',
|
|
statusCode: 404,
|
|
timestamp: '2024-01-01T00:00:00Z',
|
|
};
|
|
const error = new ApiError('Not found', 'NOT_FOUND', context);
|
|
expect(error.getDeveloperMessage()).toBe('[NOT_FOUND] Not found GET /api/users status:404');
|
|
});
|
|
|
|
it('should include retry count when available', () => {
|
|
const context: ApiErrorContext = {
|
|
endpoint: '/api/users',
|
|
method: 'GET',
|
|
retryCount: 3,
|
|
timestamp: '2024-01-01T00:00:00Z',
|
|
};
|
|
const error = new ApiError('Failed', 'NETWORK_ERROR', context);
|
|
expect(error.getDeveloperMessage()).toBe('[NETWORK_ERROR] Failed GET /api/users retry:3');
|
|
});
|
|
|
|
it('should include all context fields when available', () => {
|
|
const context: ApiErrorContext = {
|
|
endpoint: '/api/users',
|
|
method: 'POST',
|
|
statusCode: 500,
|
|
retryCount: 2,
|
|
timestamp: '2024-01-01T00:00:00Z',
|
|
};
|
|
const error = new ApiError('Server error', 'SERVER_ERROR', context);
|
|
expect(error.getDeveloperMessage()).toBe('[SERVER_ERROR] Server error POST /api/users status:500 retry:2');
|
|
});
|
|
});
|
|
|
|
describe('isRetryable', () => {
|
|
it('should return true for retryable error types', () => {
|
|
const retryableTypes = ['NETWORK_ERROR', 'SERVER_ERROR', 'RATE_LIMIT_ERROR', 'TIMEOUT_ERROR'];
|
|
|
|
retryableTypes.forEach(type => {
|
|
const error = new ApiError('Test', type as ApiErrorType, { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.isRetryable()).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('should return false for non-retryable error types', () => {
|
|
const nonRetryableTypes = ['AUTH_ERROR', 'VALIDATION_ERROR', 'NOT_FOUND', 'CANCELED_ERROR', 'UNKNOWN_ERROR'];
|
|
|
|
nonRetryableTypes.forEach(type => {
|
|
const error = new ApiError('Test', type as ApiErrorType, { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.isRetryable()).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('isConnectivityIssue', () => {
|
|
it('should return true for NETWORK_ERROR', () => {
|
|
const error = new ApiError('Network', 'NETWORK_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.isConnectivityIssue()).toBe(true);
|
|
});
|
|
|
|
it('should return true for TIMEOUT_ERROR', () => {
|
|
const error = new ApiError('Timeout', 'TIMEOUT_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.isConnectivityIssue()).toBe(true);
|
|
});
|
|
|
|
it('should return false for other error types', () => {
|
|
const otherTypes = ['AUTH_ERROR', 'VALIDATION_ERROR', 'NOT_FOUND', 'SERVER_ERROR', 'RATE_LIMIT_ERROR', 'CANCELED_ERROR', 'UNKNOWN_ERROR'];
|
|
|
|
otherTypes.forEach(type => {
|
|
const error = new ApiError('Test', type as ApiErrorType, { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.isConnectivityIssue()).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getSeverity', () => {
|
|
it('should return "warn" for AUTH_ERROR', () => {
|
|
const error = new ApiError('Auth', 'AUTH_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getSeverity()).toBe('warn');
|
|
});
|
|
|
|
it('should return "warn" for VALIDATION_ERROR', () => {
|
|
const error = new ApiError('Validation', 'VALIDATION_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getSeverity()).toBe('warn');
|
|
});
|
|
|
|
it('should return "warn" for NOT_FOUND', () => {
|
|
const error = new ApiError('Not found', 'NOT_FOUND', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getSeverity()).toBe('warn');
|
|
});
|
|
|
|
it('should return "info" for RATE_LIMIT_ERROR', () => {
|
|
const error = new ApiError('Rate limited', 'RATE_LIMIT_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getSeverity()).toBe('info');
|
|
});
|
|
|
|
it('should return "info" for CANCELED_ERROR', () => {
|
|
const error = new ApiError('Canceled', 'CANCELED_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getSeverity()).toBe('info');
|
|
});
|
|
|
|
it('should return "error" for other error types', () => {
|
|
const errorTypes = ['NETWORK_ERROR', 'SERVER_ERROR', 'TIMEOUT_ERROR', 'UNKNOWN_ERROR'];
|
|
|
|
errorTypes.forEach(type => {
|
|
const error = new ApiError('Test', type as ApiErrorType, { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(error.getSeverity()).toBe('error');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Type guards', () => {
|
|
describe('isApiError', () => {
|
|
it('should return true for ApiError instances', () => {
|
|
const error = new ApiError('Test', 'NETWORK_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(isApiError(error)).toBe(true);
|
|
});
|
|
|
|
it('should return false for non-ApiError instances', () => {
|
|
expect(isApiError(new Error('Test'))).toBe(false);
|
|
expect(isApiError('string')).toBe(false);
|
|
expect(isApiError(null)).toBe(false);
|
|
expect(isApiError(undefined)).toBe(false);
|
|
expect(isApiError({})).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isNetworkError', () => {
|
|
it('should return true for NETWORK_ERROR ApiError', () => {
|
|
const error = new ApiError('Network', 'NETWORK_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(isNetworkError(error)).toBe(true);
|
|
});
|
|
|
|
it('should return false for other error types', () => {
|
|
const error = new ApiError('Auth', 'AUTH_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(isNetworkError(error)).toBe(false);
|
|
});
|
|
|
|
it('should return false for non-ApiError', () => {
|
|
expect(isNetworkError(new Error('Test'))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isAuthError', () => {
|
|
it('should return true for AUTH_ERROR ApiError', () => {
|
|
const error = new ApiError('Auth', 'AUTH_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(isAuthError(error)).toBe(true);
|
|
});
|
|
|
|
it('should return false for other error types', () => {
|
|
const error = new ApiError('Network', 'NETWORK_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(isAuthError(error)).toBe(false);
|
|
});
|
|
|
|
it('should return false for non-ApiError', () => {
|
|
expect(isAuthError(new Error('Test'))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isRetryableError', () => {
|
|
it('should return true for retryable ApiError', () => {
|
|
const error = new ApiError('Server', 'SERVER_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(isRetryableError(error)).toBe(true);
|
|
});
|
|
|
|
it('should return false for non-retryable ApiError', () => {
|
|
const error = new ApiError('Auth', 'AUTH_ERROR', { timestamp: '2024-01-01T00:00:00Z' });
|
|
expect(isRetryableError(error)).toBe(false);
|
|
});
|
|
|
|
it('should return false for non-ApiError', () => {
|
|
expect(isRetryableError(new Error('Test'))).toBe(false);
|
|
});
|
|
});
|
|
}); |