After the DC-005 module reorganization (41 files moved into src/ subdirs),
138 test suites failed because the refactor script's path-rewrite logic
missed three categories:
1. Files inside src/ doing 'require("./src/...")' — should be 'require("../...")'
2. Files in src/X/Y/ doing 'require("../../../src/...")' — should be 'require("../../...")'
3. Test files in __tests__/ with leftover 'require("../../../src/...")' paths
Root cause: the original refactor script ran before all files were moved,
so it computed relative paths against stale filesystem state.
Result:
- 30/30 test suites pass
- 879/879 tests pass (was: 18/30 suites, 614/687 tests)
Also fixed:
- routes/apps/restore.js: wrong responses import path
- routes/*/*.js: '../../src/utilities/X' → '../src/utilities/X' (depth 2 routes)
158 lines
4.9 KiB
JavaScript
158 lines
4.9 KiB
JavaScript
const {
|
|
AppError,
|
|
ValidationError,
|
|
AuthenticationError,
|
|
ForbiddenError,
|
|
NotFoundError,
|
|
ConflictError,
|
|
RateLimitError,
|
|
DockerError,
|
|
CaddyError,
|
|
DNSError,
|
|
ServiceUnavailableError
|
|
} = require('../src/utilities/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);
|
|
});
|
|
});
|
|
});
|