const { AppError, ValidationError, AuthenticationError, ForbiddenError, NotFoundError, ConflictError, RateLimitError, DockerError, CaddyError, DNSError, ServiceUnavailableError } = require('../errors'); describe('Error Classes', () => { describe('AppError', () => { it('has default statusCode 500 and auto-generated code', () => { const err = new AppError('something broke'); expect(err.message).toBe('something broke'); expect(err.statusCode).toBe(500); expect(err.code).toBe('APP_ERROR'); expect(err.isOperational).toBe(true); expect(err).toBeInstanceOf(Error); }); it('accepts custom statusCode and code', () => { const err = new AppError('custom', 418, 'DC-TEAPOT'); expect(err.statusCode).toBe(418); expect(err.code).toBe('DC-TEAPOT'); }); }); describe('ValidationError', () => { it('has statusCode 400, code DC-400, and optional field', () => { const err = new ValidationError('bad input', 'email'); expect(err.statusCode).toBe(400); expect(err.code).toBe('DC-400'); expect(err.field).toBe('email'); expect(err).toBeInstanceOf(AppError); }); it('field defaults to null', () => { const err = new ValidationError('bad'); expect(err.field).toBeNull(); }); }); describe('AuthenticationError', () => { it('has statusCode 401 and requiresTotp flag', () => { const err = new AuthenticationError('need auth', true); expect(err.statusCode).toBe(401); expect(err.code).toBe('DC-401'); expect(err.requiresTotp).toBe(true); expect(err).toBeInstanceOf(AppError); }); it('has sensible defaults', () => { const err = new AuthenticationError(); expect(err.message).toBe('Authentication required'); expect(err.requiresTotp).toBe(false); }); }); describe('ForbiddenError', () => { it('has statusCode 403', () => { const err = new ForbiddenError(); expect(err.statusCode).toBe(403); expect(err.code).toBe('DC-403'); expect(err.message).toBe('Forbidden'); expect(err).toBeInstanceOf(AppError); }); }); describe('NotFoundError', () => { it('has statusCode 404 and resource in message', () => { const err = new NotFoundError('Service'); expect(err.statusCode).toBe(404); expect(err.code).toBe('DC-404'); expect(err.message).toBe('Service not found'); expect(err.resource).toBe('Service'); expect(err).toBeInstanceOf(AppError); }); it('defaults to "Resource"', () => { const err = new NotFoundError(); expect(err.message).toBe('Resource not found'); }); }); describe('ConflictError', () => { it('has statusCode 409 and optional conflictingResource', () => { const err = new ConflictError('already exists', 'service-x'); expect(err.statusCode).toBe(409); expect(err.code).toBe('DC-409'); expect(err.conflictingResource).toBe('service-x'); expect(err).toBeInstanceOf(AppError); }); }); describe('RateLimitError', () => { it('has statusCode 429 and retryAfter', () => { const err = new RateLimitError(30); expect(err.statusCode).toBe(429); expect(err.code).toBe('DC-429'); expect(err.retryAfter).toBe(30); expect(err.message).toBe('Rate limit exceeded'); expect(err).toBeInstanceOf(AppError); }); it('defaults retryAfter to 60', () => { const err = new RateLimitError(); expect(err.retryAfter).toBe(60); }); }); describe('DockerError', () => { it('has statusCode 500, operation, and details', () => { const err = new DockerError('container failed', 'create', { containerId: '123' }); expect(err.statusCode).toBe(500); expect(err.code).toBe('DC-500-DOCKER'); expect(err.operation).toBe('create'); expect(err.details).toEqual({ containerId: '123' }); expect(err).toBeInstanceOf(AppError); }); }); describe('CaddyError', () => { it('has statusCode 502', () => { const err = new CaddyError('reload failed', 'reload'); expect(err.statusCode).toBe(502); expect(err.code).toBe('DC-502-CADDY'); expect(err.operation).toBe('reload'); expect(err).toBeInstanceOf(AppError); }); }); describe('DNSError', () => { it('has statusCode 502', () => { const err = new DNSError('zone create failed', 'create-zone'); expect(err.statusCode).toBe(502); expect(err.code).toBe('DC-502-DNS'); expect(err).toBeInstanceOf(AppError); }); }); describe('ServiceUnavailableError', () => { it('has statusCode 503, service name, and optional retryAfter', () => { const err = new ServiceUnavailableError('plex', 120); expect(err.statusCode).toBe(503); expect(err.code).toBe('DC-503'); expect(err.message).toBe('Service unavailable: plex'); expect(err.service).toBe('plex'); expect(err.retryAfter).toBe(120); expect(err).toBeInstanceOf(AppError); }); }); });