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.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-08 00:30:35 -05:00
parent 833c49adc9
commit a79488bd2d
1 changed files with 308 additions and 0 deletions

View File

@ -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<NotificationService>;
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();
});
});
});