Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 59s
303 lines
11 KiB
TypeScript
303 lines
11 KiB
TypeScript
// src/services/db/errors.db.test.ts
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import type { Logger } from 'pino';
|
|
import {
|
|
RepositoryError,
|
|
UniqueConstraintError,
|
|
ForeignKeyConstraintError,
|
|
NotFoundError,
|
|
ForbiddenError,
|
|
ValidationError,
|
|
FileUploadError,
|
|
NotNullConstraintError,
|
|
CheckConstraintError,
|
|
InvalidTextRepresentationError,
|
|
NumericValueOutOfRangeError,
|
|
handleDbError,
|
|
} from './errors.db';
|
|
|
|
vi.mock('./logger.server');
|
|
|
|
describe('Custom Database and Application Errors', () => {
|
|
describe('RepositoryError', () => {
|
|
it('should create a generic database error with a message and status', () => {
|
|
const message = 'Generic DB Error';
|
|
const status = 500;
|
|
const error = new RepositoryError(message, status);
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error.message).toBe(message);
|
|
expect(error.status).toBe(status);
|
|
expect(error.name).toBe('RepositoryError');
|
|
});
|
|
});
|
|
|
|
describe('UniqueConstraintError', () => {
|
|
it('should create an error with a default message and status 409', () => {
|
|
const error = new UniqueConstraintError();
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error).toBeInstanceOf(UniqueConstraintError);
|
|
expect(error.message).toBe('The record already exists.');
|
|
expect(error.status).toBe(409);
|
|
expect(error.name).toBe('UniqueConstraintError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'This email is already taken.';
|
|
const error = new UniqueConstraintError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('ForeignKeyConstraintError', () => {
|
|
it('should create an error with a default message and status 400', () => {
|
|
const error = new ForeignKeyConstraintError();
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error).toBeInstanceOf(ForeignKeyConstraintError);
|
|
expect(error.message).toBe('The referenced record does not exist.');
|
|
expect(error.status).toBe(400);
|
|
expect(error.name).toBe('ForeignKeyConstraintError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'The specified user does not exist.';
|
|
const error = new ForeignKeyConstraintError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('NotFoundError', () => {
|
|
it('should create an error with a default message and status 404', () => {
|
|
const error = new NotFoundError();
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error).toBeInstanceOf(NotFoundError);
|
|
expect(error.message).toBe('The requested resource was not found.');
|
|
expect(error.status).toBe(404);
|
|
expect(error.name).toBe('NotFoundError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'Flyer with ID 999 not found.';
|
|
const error = new NotFoundError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('ForbiddenError', () => {
|
|
it('should create an error with a default message and status 403', () => {
|
|
const error = new ForbiddenError();
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error).toBeInstanceOf(ForbiddenError);
|
|
expect(error.message).toBe('Access denied.');
|
|
expect(error.status).toBe(403);
|
|
expect(error.name).toBe('ForbiddenError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'You shall not pass.';
|
|
const error = new ForbiddenError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('ValidationError', () => {
|
|
it('should create an error with a default message, status 400, and validation errors array', () => {
|
|
const validationIssues = [{ path: ['email'], message: 'Invalid email' }];
|
|
const error = new ValidationError(validationIssues);
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error).toBeInstanceOf(ValidationError);
|
|
expect(error.message).toBe('The request data is invalid.');
|
|
expect(error.status).toBe(400);
|
|
expect(error.name).toBe('ValidationError');
|
|
expect(error.validationErrors).toEqual(validationIssues);
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'Your input has some issues.';
|
|
const error = new ValidationError([], message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('FileUploadError', () => {
|
|
it('should create an error with the correct message, name, and status 400', () => {
|
|
const message = 'No file was uploaded.';
|
|
const error = new FileUploadError(message);
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(FileUploadError);
|
|
expect(error.message).toBe(message);
|
|
expect(error.status).toBe(400);
|
|
expect(error.name).toBe('FileUploadError');
|
|
});
|
|
});
|
|
|
|
describe('NotNullConstraintError', () => {
|
|
it('should create an error with a default message and status 400', () => {
|
|
const error = new NotNullConstraintError();
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error.message).toBe('A required field was left null.');
|
|
expect(error.status).toBe(400);
|
|
expect(error.name).toBe('NotNullConstraintError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'Email cannot be null.';
|
|
const error = new NotNullConstraintError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('CheckConstraintError', () => {
|
|
it('should create an error with a default message and status 400', () => {
|
|
const error = new CheckConstraintError();
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error.message).toBe('A check constraint was violated.');
|
|
expect(error.status).toBe(400);
|
|
expect(error.name).toBe('CheckConstraintError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'Price must be positive.';
|
|
const error = new CheckConstraintError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('InvalidTextRepresentationError', () => {
|
|
it('should create an error with a default message and status 400', () => {
|
|
const error = new InvalidTextRepresentationError();
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error.message).toBe('A value has an invalid format for its data type.');
|
|
expect(error.status).toBe(400);
|
|
expect(error.name).toBe('InvalidTextRepresentationError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'Invalid input syntax for type integer: "abc"';
|
|
const error = new InvalidTextRepresentationError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('NumericValueOutOfRangeError', () => {
|
|
it('should create an error with a default message and status 400', () => {
|
|
const error = new NumericValueOutOfRangeError();
|
|
expect(error).toBeInstanceOf(RepositoryError);
|
|
expect(error.message).toBe('A numeric value is out of the allowed range.');
|
|
expect(error.status).toBe(400);
|
|
expect(error.name).toBe('NumericValueOutOfRangeError');
|
|
});
|
|
|
|
it('should create an error with a custom message', () => {
|
|
const message = 'Value too large for type smallint.';
|
|
const error = new NumericValueOutOfRangeError(message);
|
|
expect(error.message).toBe(message);
|
|
});
|
|
});
|
|
|
|
describe('handleDbError', () => {
|
|
const mockLogger = {
|
|
error: vi.fn(),
|
|
} as unknown as Logger;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should re-throw existing RepositoryError instances without logging', () => {
|
|
const notFound = new NotFoundError('Test not found');
|
|
expect(() => handleDbError(notFound, mockLogger, 'msg', {})).toThrow(notFound);
|
|
expect(mockLogger.error).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw UniqueConstraintError for code 23505', () => {
|
|
const dbError = new Error('duplicate key');
|
|
(dbError as any).code = '23505';
|
|
expect(() =>
|
|
handleDbError(dbError, mockLogger, 'msg', {}, { uniqueMessage: 'custom unique' }),
|
|
).toThrow('custom unique');
|
|
});
|
|
|
|
it('should throw ForeignKeyConstraintError for code 23503', () => {
|
|
const dbError = new Error('fk violation');
|
|
(dbError as any).code = '23503';
|
|
expect(() =>
|
|
handleDbError(dbError, mockLogger, 'msg', {}, { fkMessage: 'custom fk' }),
|
|
).toThrow('custom fk');
|
|
});
|
|
|
|
it('should throw NotNullConstraintError for code 23502', () => {
|
|
const dbError = new Error('not null violation');
|
|
(dbError as any).code = '23502';
|
|
expect(() =>
|
|
handleDbError(dbError, mockLogger, 'msg', {}, { notNullMessage: 'custom not null' }),
|
|
).toThrow('custom not null');
|
|
});
|
|
|
|
it('should throw CheckConstraintError for code 23514', () => {
|
|
const dbError = new Error('check violation');
|
|
(dbError as any).code = '23514';
|
|
expect(() =>
|
|
handleDbError(dbError, mockLogger, 'msg', {}, { checkMessage: 'custom check' }),
|
|
).toThrow('custom check');
|
|
});
|
|
|
|
it('should throw InvalidTextRepresentationError for code 22P02', () => {
|
|
const dbError = new Error('invalid text');
|
|
(dbError as any).code = '22P02';
|
|
expect(() =>
|
|
handleDbError(dbError, mockLogger, 'msg', {}, { invalidTextMessage: 'custom invalid text' }),
|
|
).toThrow('custom invalid text');
|
|
});
|
|
|
|
it('should throw NumericValueOutOfRangeError for code 22003', () => {
|
|
const dbError = new Error('out of range');
|
|
(dbError as any).code = '22003';
|
|
expect(() =>
|
|
handleDbError(
|
|
dbError,
|
|
mockLogger,
|
|
'msg',
|
|
{},
|
|
{ numericOutOfRangeMessage: 'custom out of range' },
|
|
),
|
|
).toThrow('custom out of range');
|
|
});
|
|
|
|
it('should throw a generic Error with a default message', () => {
|
|
const genericError = new Error('Something else happened');
|
|
expect(() =>
|
|
handleDbError(genericError, mockLogger, 'msg', {}, { defaultMessage: 'Oops' }),
|
|
).toThrow('Oops');
|
|
expect(mockLogger.error).toHaveBeenCalledWith({ err: genericError }, 'msg');
|
|
});
|
|
|
|
it('should throw a generic Error with a constructed message using entityName', () => {
|
|
const genericError = new Error('Something else happened');
|
|
expect(() =>
|
|
handleDbError(genericError, mockLogger, 'msg', {}, { entityName: 'User' }),
|
|
).toThrow('Failed to perform operation on User.');
|
|
});
|
|
|
|
it('should throw a generic Error with a constructed message using "database" as a fallback', () => {
|
|
const genericError = new Error('Something else happened');
|
|
// No defaultMessage or entityName provided
|
|
expect(() => handleDbError(genericError, mockLogger, 'msg', {}, {})).toThrow(
|
|
'Failed to perform operation on database.',
|
|
);
|
|
});
|
|
});
|
|
});
|