/** * 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 '@/types/api'; 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); }); }); });