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:
parent
833c49adc9
commit
a79488bd2d
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue