/** * Tests for Service Error Handler Utility * FE-TEST-004: Test service error handler utility functions */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { AxiosError } from 'axios'; import { handleServiceError, withErrorHandling, getServiceValidationErrors, isErrorStatus, isNetworkError, getUserFriendlyMessage, handleApiServiceError, } from './serviceErrorHandler'; import type { ApiError } from '@/schemas/apiSchemas'; // Mock dependencies vi.mock('./apiErrorHandler', () => ({ parseApiError: vi.fn((error: unknown): ApiError => { if (error && typeof error === 'object' && 'code' in error) { return error as ApiError; } return { code: 0, message: 'Unknown error', timestamp: new Date().toISOString(), }; }), formatErrorMessage: vi.fn((error: ApiError) => error.message), getValidationErrors: vi.fn((error: ApiError) => { if (error.details) { const errors: Record = {}; error.details.forEach((detail: any) => { if (detail.field && detail.message) { errors[detail.field] = detail.message; } }); return errors; } return {}; }), })); vi.mock('./errorMessages', () => ({ formatUserFriendlyError: vi.fn((error: ApiError) => error.message), isRetryableError: vi.fn(() => false), })); describe('serviceErrorHandler utilities', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('handleServiceError', () => { it('should throw error by default', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; expect(() => handleServiceError(error)).toThrow(); }); it('should return message when throwError is false', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; const result = handleServiceError(error, { throwError: false }); expect(result).toBe('Not found'); }); it('should use custom message override', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; expect(() => { handleServiceError(error, { customMessages: { 404: 'Custom not found' }, }); }).toThrow('Custom not found'); }); it('should include context in error', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; try { handleServiceError(error, { context: 'playlist' }); } catch (e: any) { expect(e.apiError).toBeDefined(); } }); }); describe('withErrorHandling', () => { it('should return result on success', async () => { const apiCall = vi.fn().mockResolvedValue('success'); const result = await withErrorHandling(apiCall); expect(result).toBe('success'); }); it('should handle errors', async () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; const apiCall = vi.fn().mockRejectedValue(error); await expect(withErrorHandling(apiCall)).rejects.toThrow(); }); }); describe('getServiceValidationErrors', () => { it('should extract validation errors', () => { 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 = getServiceValidationErrors(error); expect(result.email).toBe('Invalid email'); expect(result.password).toBe('Too short'); }); }); describe('isErrorStatus', () => { it('should return true for matching status', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; expect(isErrorStatus(error, 404)).toBe(true); }); it('should return false for non-matching status', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; expect(isErrorStatus(error, 500)).toBe(false); }); }); describe('isNetworkError', () => { it('should return true for AxiosError without response', () => { const error = { isAxiosError: true, request: {}, response: undefined, } as unknown as AxiosError; expect(isNetworkError(error)).toBe(true); }); it('should return false for AxiosError with response', () => { const error = { isAxiosError: true, request: {}, response: { status: 404 }, } as unknown as AxiosError; expect(isNetworkError(error)).toBe(false); }); }); describe('getUserFriendlyMessage', () => { it('should return user-friendly message', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; const result = getUserFriendlyMessage(error); expect(result).toBe('Not found'); }); it('should use context', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; const result = getUserFriendlyMessage(error, 'playlist'); expect(result).toBe('Not found'); }); }); describe('handleApiServiceError', () => { it('should always throw', () => { const error: ApiError = { code: 404, message: 'Not found', timestamp: new Date().toISOString(), }; expect(() => handleApiServiceError(error)).toThrow(); }); }); });