veza/apps/web/src/utils/errorMessages.test.ts

208 lines
6.3 KiB
TypeScript
Raw Normal View History

/**
* Tests for Error Messages Utility
* FE-TEST-004: Test error message utility functions
*/
import { describe, it, expect } from 'vitest';
import {
ERROR_MESSAGES,
CONTEXT_ERROR_MESSAGES,
getErrorMessageByStatus,
getContextErrorMessage,
formatUserFriendlyError,
isRetryableError,
getRetryDelay,
} from './errorMessages';
import type { ApiError } from '@/schemas/apiSchemas';
describe('errorMessages utilities', () => {
describe('ERROR_MESSAGES', () => {
it('should have messages for common status codes', () => {
expect(ERROR_MESSAGES[400]).toBeTruthy();
expect(ERROR_MESSAGES[401]).toBeTruthy();
expect(ERROR_MESSAGES[404]).toBeTruthy();
expect(ERROR_MESSAGES[500]).toBeTruthy();
});
it('should have network error messages', () => {
expect(ERROR_MESSAGES.NETWORK).toBeTruthy();
expect(ERROR_MESSAGES.TIMEOUT).toBeTruthy();
expect(ERROR_MESSAGES.UNKNOWN).toBeTruthy();
});
});
describe('getErrorMessageByStatus', () => {
it('should return message for known status', () => {
expect(getErrorMessageByStatus(404)).toBe(ERROR_MESSAGES[404]);
expect(getErrorMessageByStatus(500)).toBe(ERROR_MESSAGES[500]);
});
it('should return default message for unknown status', () => {
expect(getErrorMessageByStatus(999)).toBe(ERROR_MESSAGES.UNKNOWN);
});
it('should use custom default message', () => {
const custom = 'Custom error';
expect(getErrorMessageByStatus(999, custom)).toBe(custom);
});
});
describe('getContextErrorMessage', () => {
it('should return context-specific message', () => {
expect(getContextErrorMessage('auth', 'login')).toBe(
CONTEXT_ERROR_MESSAGES.auth.login,
);
});
it('should return default for unknown context/action', () => {
expect(getContextErrorMessage('auth', 'unknown')).toBe(
ERROR_MESSAGES.UNKNOWN,
);
});
it('should use custom default message', () => {
const custom = 'Custom error';
expect(getContextErrorMessage('auth', 'unknown', custom)).toBe(custom);
});
});
describe('formatUserFriendlyError', () => {
it('should format ApiError', () => {
const error: ApiError = {
code: 404,
message: 'Not found',
timestamp: new Date().toISOString(),
};
const result = formatUserFriendlyError(error);
expect(result).toBe(ERROR_MESSAGES[404]);
});
it('should format Error instance', () => {
const error = new Error('Test error');
const result = formatUserFriendlyError(error);
expect(result).toBe('Test error');
});
it('should format with context', () => {
const error: ApiError = {
code: 404,
message: 'not found',
timestamp: new Date().toISOString(),
};
const result = formatUserFriendlyError(error, 'playlist');
expect(result).toBe(CONTEXT_ERROR_MESSAGES.playlist.notFound);
});
it('should include details when requested', () => {
const error: ApiError = {
code: 422,
message: 'Validation failed',
timestamp: new Date().toISOString(),
details: [
{ field: 'email', message: 'Invalid email' },
{ field: 'password', message: 'Too short' },
],
};
const result = formatUserFriendlyError(error, undefined, true);
// Details are included in parentheses
expect(result).toContain('Invalid email');
expect(result).toContain('Too short');
});
it('should handle network errors', () => {
const error = { code: 'ERR_NETWORK' };
const result = formatUserFriendlyError(error);
expect(result).toBe(ERROR_MESSAGES.NETWORK);
});
it('should handle timeout errors', () => {
const error = { code: 'ECONNABORTED' };
const result = formatUserFriendlyError(error);
expect(result).toBe(ERROR_MESSAGES.TIMEOUT);
});
it('should return unknown for unhandled errors', () => {
const result = formatUserFriendlyError(null);
expect(result).toBe(ERROR_MESSAGES.UNKNOWN);
});
});
describe('isRetryableError', () => {
it('should return true for retryable status codes', () => {
const error429: ApiError = {
code: 429,
message: 'Rate limit',
timestamp: new Date().toISOString(),
};
const error500: ApiError = {
code: 500,
message: 'Server error',
timestamp: new Date().toISOString(),
};
const error503: ApiError = {
code: 503,
message: 'Service unavailable',
timestamp: new Date().toISOString(),
};
expect(isRetryableError(error429)).toBe(true);
expect(isRetryableError(error500)).toBe(true);
expect(isRetryableError(error503)).toBe(true);
});
it('should return true for network errors', () => {
expect(isRetryableError({ code: 'ERR_NETWORK' })).toBe(true);
expect(isRetryableError({ code: 'ECONNABORTED' })).toBe(true);
});
it('should return false for non-retryable errors', () => {
const error404: ApiError = {
code: 404,
message: 'Not found',
timestamp: new Date().toISOString(),
};
const error401: ApiError = {
code: 401,
message: 'Unauthorized',
timestamp: new Date().toISOString(),
};
expect(isRetryableError(error404)).toBe(false);
expect(isRetryableError(error401)).toBe(false);
});
});
describe('getRetryDelay', () => {
it('should use retry_after from rate limit error', () => {
const error: ApiError = {
code: 429,
message: 'Rate limit',
timestamp: new Date().toISOString(),
retry_after: 5,
};
expect(getRetryDelay(error, 0)).toBe(5000);
});
it('should use exponential backoff', () => {
const error: ApiError = {
code: 500,
message: 'Server error',
timestamp: new Date().toISOString(),
};
expect(getRetryDelay(error, 0)).toBe(1000);
expect(getRetryDelay(error, 1)).toBe(2000);
expect(getRetryDelay(error, 2)).toBe(4000);
expect(getRetryDelay(error, 3)).toBe(8000);
});
it('should cap at 30 seconds', () => {
const error: ApiError = {
code: 500,
message: 'Server error',
timestamp: new Date().toISOString(),
};
expect(getRetryDelay(error, 10)).toBe(30000);
});
});
});