tududi/backend/tests/unit/shared/errorHandler.test.js
Chris 3486541272
Add comprehensive LLM development documentation (#939)
* Increase coverage

* Add comprehensive LLM development documentation

- Add CLAUDE.md as main documentation index
- Create 8 detailed documentation files in docs/:
  - architecture.md: Tech stack, data models, auth system
  - directory-structure.md: Complete file tree with paths
  - backend-patterns.md: Module architecture and patterns
  - database.md: Models, migrations, and workflows
  - development-workflow.md: Setup and daily development
  - code-conventions.md: Style guide and best practices
  - testing.md: Test organization and patterns
  - common-tasks.md: How-to guides for frequent tasks
- Update .gitignore to allow project-level CLAUDE.md
- 4,285 lines of comprehensive documentation
- Organized for easy navigation with cross-links
- LLM-optimized with absolute paths and code examples

* fixup! Add comprehensive LLM development documentation
2026-03-14 02:54:59 +02:00

166 lines
5 KiB
JavaScript

const errorHandler = require('../../../shared/middleware/errorHandler');
const {
AppError,
NotFoundError,
ValidationError,
ConflictError,
UnauthorizedError,
ForbiddenError,
} = require('../../../shared/errors');
describe('errorHandler middleware', () => {
let req, res, next;
beforeEach(() => {
req = {};
res = { status: jest.fn().mockReturnThis(), json: jest.fn() };
next = jest.fn();
});
// --- AppError subclasses ---
it('should handle NotFoundError with 404', () => {
const err = new NotFoundError('Task not found');
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
error: 'Task not found',
code: 'NOT_FOUND',
});
});
it('should handle ValidationError with 400', () => {
const err = new ValidationError('Title is required');
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
error: 'Title is required',
code: 'VALIDATION_ERROR',
});
});
it('should handle ConflictError with 409', () => {
const err = new ConflictError();
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(409);
expect(res.json).toHaveBeenCalledWith({
error: 'Resource already exists',
code: 'CONFLICT',
});
});
it('should handle UnauthorizedError with 401', () => {
const err = new UnauthorizedError();
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({
error: 'Unauthorized',
code: 'UNAUTHORIZED',
});
});
it('should handle ForbiddenError with 403', () => {
const err = new ForbiddenError();
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalledWith({
error: 'Forbidden',
code: 'FORBIDDEN',
});
});
it('should handle generic AppError with custom status', () => {
const err = new AppError('Rate limited', 429, 'RATE_LIMITED');
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(429);
expect(res.json).toHaveBeenCalledWith({
error: 'Rate limited',
code: 'RATE_LIMITED',
});
});
// --- Sequelize errors ---
it('should handle SequelizeValidationError with 400', () => {
const err = {
name: 'SequelizeValidationError',
errors: [
{ message: 'email must be unique' },
{ message: 'name cannot be null' },
],
};
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
error: 'email must be unique, name cannot be null',
code: 'VALIDATION_ERROR',
});
});
it('should handle SequelizeUniqueConstraintError with 409', () => {
const err = { name: 'SequelizeUniqueConstraintError' };
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(409);
expect(res.json).toHaveBeenCalledWith({
error: 'A resource with this identifier already exists.',
code: 'CONFLICT',
});
});
// --- Unknown errors ---
it('should handle unknown errors with 500 in non-production', () => {
const originalEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'test';
const err = new Error('Something unexpected');
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
error: 'Something unexpected',
code: 'INTERNAL_ERROR',
});
process.env.NODE_ENV = originalEnv;
});
it('should hide error message in production', () => {
const originalEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
const err = new Error('Database credentials leaked');
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
error: 'Internal server error',
code: 'INTERNAL_ERROR',
});
process.env.NODE_ENV = originalEnv;
});
it('should use statusCode from unknown error if present', () => {
const err = new Error('Bad gateway');
err.statusCode = 502;
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(502);
});
it('should default to 500 when no statusCode on unknown error', () => {
const err = new Error('Oops');
errorHandler(err, req, res, next);
expect(res.status).toHaveBeenCalledWith(500);
});
});