From dfb1341b2c375d9f7df16e662b7e64a49f4d284e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Eduardo=20Garc=C3=ADa=20M=C3=A1rquez?= Date: Thu, 8 Jan 2026 00:30:35 -0500 Subject: [PATCH] test(frontend): add comprehensive ErrorHandlerService tests - Test all GraphQL error codes (MAX_ENROLLMENTS, SAME_PROFESSOR, etc.) - Test network errors with different status codes - Test validation errors with field details - Test HTTP error handling (500, 0, 400) - Test unknown/null/undefined error handling - Test notification display with suggestions Increases code coverage for the error handling layer. --- .../services/error-handler.service.spec.ts | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 src/frontend/src/app/core/services/error-handler.service.spec.ts diff --git a/src/frontend/src/app/core/services/error-handler.service.spec.ts b/src/frontend/src/app/core/services/error-handler.service.spec.ts new file mode 100644 index 0000000..d39491a --- /dev/null +++ b/src/frontend/src/app/core/services/error-handler.service.spec.ts @@ -0,0 +1,308 @@ +import { TestBed } from '@angular/core/testing'; +import { ErrorHandlerService } from './error-handler.service'; +import { NotificationService } from './notification.service'; + +describe('ErrorHandlerService', () => { + let service: ErrorHandlerService; + let notificationSpy: jasmine.SpyObj; + + beforeEach(() => { + notificationSpy = jasmine.createSpyObj('NotificationService', ['error', 'success', 'info']); + + TestBed.configureTestingModule({ + providers: [ + ErrorHandlerService, + { provide: NotificationService, useValue: notificationSpy }, + ], + }); + + service = TestBed.inject(ErrorHandlerService); + }); + + describe('parseError', () => { + describe('Apollo GraphQL errors', () => { + it('should parse MAX_ENROLLMENTS error code', () => { + const error = { + graphQLErrors: [ + { + message: 'Maximum enrollments reached', + extensions: { code: 'MAX_ENROLLMENTS' }, + }, + ], + }; + + const result = service.parseError(error); + + expect(result.code).toBe('MAX_ENROLLMENTS'); + expect(result.userMessage).toBe('Has alcanzado el límite máximo de 3 materias inscritas'); + expect(result.suggestion).toBe('Debes cancelar una inscripción antes de agregar otra materia'); + }); + + it('should parse SAME_PROFESSOR error code', () => { + const error = { + graphQLErrors: [ + { + message: 'Already have subject with professor', + extensions: { code: 'SAME_PROFESSOR' }, + }, + ], + }; + + const result = service.parseError(error); + + expect(result.code).toBe('SAME_PROFESSOR'); + expect(result.userMessage).toBe('Ya tienes una materia con este profesor'); + }); + + it('should parse STUDENT_NOT_FOUND error code', () => { + const error = { + graphQLErrors: [ + { + message: 'Student not found', + extensions: { code: 'STUDENT_NOT_FOUND' }, + }, + ], + }; + + const result = service.parseError(error); + + expect(result.code).toBe('STUDENT_NOT_FOUND'); + expect(result.userMessage).toBe('El estudiante no existe en el sistema'); + }); + + it('should parse DUPLICATE_ENROLLMENT error code', () => { + const error = { + graphQLErrors: [ + { + message: 'Already enrolled', + extensions: { code: 'DUPLICATE_ENROLLMENT' }, + }, + ], + }; + + const result = service.parseError(error); + + expect(result.code).toBe('DUPLICATE_ENROLLMENT'); + expect(result.userMessage).toBe('Ya estás inscrito en esta materia'); + }); + + it('should parse VALIDATION_ERROR with field details', () => { + const error = { + graphQLErrors: [ + { + message: 'Validation failed', + extensions: { + code: 'VALIDATION_ERROR', + errors: [ + { PropertyName: 'Email', ErrorMessage: 'Invalid email format' }, + { PropertyName: 'Name', ErrorMessage: 'Name is required' }, + ], + }, + }, + ], + }; + + const result = service.parseError(error); + + expect(result.code).toBe('VALIDATION_ERROR'); + expect(result.userMessage).toBe('Los datos ingresados no son válidos'); + expect(result.devMessage).toContain('Email: Invalid email format'); + expect(result.devMessage).toContain('Name: Name is required'); + }); + + it('should handle unknown error code with UNKNOWN message', () => { + const error = { + graphQLErrors: [ + { + message: 'Some unknown error', + extensions: { code: 'WEIRD_ERROR' }, + }, + ], + }; + + const result = service.parseError(error); + + // Service preserves original code for logging but uses UNKNOWN's message + expect(result.userMessage).toBe('Ocurrió un error inesperado'); + }); + + it('should handle missing error code as UNKNOWN', () => { + const error = { + graphQLErrors: [ + { + message: 'Error without code', + extensions: {}, + }, + ], + }; + + const result = service.parseError(error); + + expect(result.code).toBe('UNKNOWN'); + }); + }); + + describe('Network errors', () => { + it('should parse network error with status 0', () => { + const error = { + networkError: { status: 0, message: 'Network failed' }, + message: 'Network error', + }; + + const result = service.parseError(error); + + expect(result.code).toBe('NETWORK_ERROR'); + expect(result.userMessage).toBe('No se pudo conectar con el servidor'); + expect(result.suggestion).toContain('Verifica tu conexión'); + }); + + it('should parse network error with HTTP status', () => { + const error = { + networkError: { status: 503, message: 'Service Unavailable' }, + message: 'Network error', + }; + + const result = service.parseError(error); + + expect(result.code).toBe('NETWORK_ERROR'); + expect(result.devMessage).toContain('503'); + }); + }); + + describe('HTTP errors', () => { + it('should parse HTTP 500 as SERVER_ERROR', () => { + const error = { status: 500, message: 'Internal Server Error' }; + + const result = service.parseError(error); + + expect(result.code).toBe('SERVER_ERROR'); + expect(result.userMessage).toBe('Error interno del servidor'); + }); + + it('should parse HTTP 502 as SERVER_ERROR', () => { + const error = { status: 502, message: 'Bad Gateway' }; + + const result = service.parseError(error); + + expect(result.code).toBe('SERVER_ERROR'); + }); + + it('should parse HTTP 0 as NETWORK_ERROR', () => { + const error = { status: 0, message: 'No response' }; + + const result = service.parseError(error); + + expect(result.code).toBe('NETWORK_ERROR'); + }); + + it('should parse HTTP 400 as UNKNOWN', () => { + const error = { status: 400, message: 'Bad Request' }; + + const result = service.parseError(error); + + expect(result.code).toBe('UNKNOWN'); + }); + }); + + describe('Unknown errors', () => { + it('should handle string error', () => { + const error = 'Something went wrong'; + + const result = service.parseError(error); + + expect(result.code).toBe('UNKNOWN'); + expect(result.devMessage).toBe('Something went wrong'); + }); + + it('should handle null error', () => { + const result = service.parseError(null); + + expect(result.code).toBe('UNKNOWN'); + }); + + it('should handle undefined error', () => { + const result = service.parseError(undefined); + + expect(result.code).toBe('UNKNOWN'); + }); + }); + }); + + describe('handle', () => { + it('should show notification with user message', () => { + const error = { + graphQLErrors: [ + { + message: 'Maximum reached', + extensions: { code: 'MAX_ENROLLMENTS' }, + }, + ], + }; + + service.handle(error); + + expect(notificationSpy.error).toHaveBeenCalledWith( + 'Has alcanzado el límite máximo de 3 materias inscritas. Debes cancelar una inscripción antes de agregar otra materia' + ); + }); + + it('should show notification without suggestion when not available', () => { + const error = { + graphQLErrors: [ + { + message: 'Duplicate', + extensions: { code: 'DUPLICATE_ENROLLMENT' }, + }, + ], + }; + + service.handle(error); + + expect(notificationSpy.error).toHaveBeenCalledWith('Ya estás inscrito en esta materia'); + }); + + it('should return ErrorInfo', () => { + const error = { + graphQLErrors: [ + { + message: 'Not found', + extensions: { code: 'STUDENT_NOT_FOUND' }, + }, + ], + }; + + const result = service.handle(error); + + expect(result.code).toBe('STUDENT_NOT_FOUND'); + expect(result.userMessage).toBeTruthy(); + }); + + it('should include context in log output', () => { + spyOn(console, 'group'); // Prevent console output during test + const error = { status: 500, message: 'Error' }; + + service.handle(error, 'StudentService.create'); + + // In dev mode, console.group is called + // This test verifies the method doesn't throw + expect(true).toBeTruthy(); + }); + }); + + describe('logError', () => { + it('should not show notification', () => { + const error = { + graphQLErrors: [ + { + message: 'Error', + extensions: { code: 'STUDENT_NOT_FOUND' }, + }, + ], + }; + + service.logError(error); + + expect(notificationSpy.error).not.toHaveBeenCalled(); + }); + }); +});