| import { logger } from '@librechat/data-schemas'; |
| import { ErrorController } from './error'; |
| import type { Request, Response } from 'express'; |
| import type { ValidationError, MongoServerError, CustomError } from '~/types'; |
|
|
| |
| jest.mock('@librechat/data-schemas', () => ({ |
| ...jest.requireActual('@librechat/data-schemas'), |
| logger: { |
| error: jest.fn(), |
| warn: jest.fn(), |
| }, |
| })); |
|
|
| describe('ErrorController', () => { |
| let mockReq: Request; |
| let mockRes: Response; |
| let mockNext: jest.Mock; |
|
|
| beforeEach(() => { |
| mockReq = { |
| originalUrl: '', |
| } as Request; |
| mockRes = { |
| status: jest.fn().mockReturnThis(), |
| send: jest.fn(), |
| } as unknown as Response; |
| (logger.error as jest.Mock).mockClear(); |
| mockNext = jest.fn(); |
| }); |
|
|
| describe('ValidationError handling', () => { |
| it('should handle ValidationError with single error', () => { |
| const validationError = { |
| name: 'ValidationError', |
| message: 'Validation error', |
| errors: { |
| email: { message: 'Email is required', path: 'email' }, |
| }, |
| } as ValidationError; |
|
|
| ErrorController(validationError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(400); |
| expect(mockRes.send).toHaveBeenCalledWith({ |
| messages: '["Email is required"]', |
| fields: '["email"]', |
| }); |
| expect(logger.error).toHaveBeenCalledWith('Validation error:', validationError.errors); |
| }); |
|
|
| it('should handle ValidationError with multiple errors', () => { |
| const validationError = { |
| name: 'ValidationError', |
| message: 'Validation error', |
| errors: { |
| email: { message: 'Email is required', path: 'email' }, |
| password: { message: 'Password is required', path: 'password' }, |
| }, |
| } as ValidationError; |
|
|
| ErrorController(validationError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(400); |
| expect(mockRes.send).toHaveBeenCalledWith({ |
| messages: '"Email is required Password is required"', |
| fields: '["email","password"]', |
| }); |
| expect(logger.error).toHaveBeenCalledWith('Validation error:', validationError.errors); |
| }); |
|
|
| it('should handle ValidationError with empty errors object', () => { |
| const validationError = { |
| name: 'ValidationError', |
| errors: {}, |
| } as ValidationError; |
|
|
| ErrorController(validationError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(400); |
| expect(mockRes.send).toHaveBeenCalledWith({ |
| messages: '[]', |
| fields: '[]', |
| }); |
| }); |
| }); |
|
|
| describe('Duplicate key error handling', () => { |
| it('should handle duplicate key error (code 11000)', () => { |
| const duplicateKeyError = { |
| name: 'MongoServerError', |
| message: 'Duplicate key error', |
| code: 11000, |
| keyValue: { email: 'test@example.com' }, |
| errmsg: |
| 'E11000 duplicate key error collection: test.users index: email_1 dup key: { email: "test@example.com" }', |
| } as MongoServerError; |
|
|
| ErrorController(duplicateKeyError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(409); |
| expect(mockRes.send).toHaveBeenCalledWith({ |
| messages: 'An document with that ["email"] already exists.', |
| fields: '["email"]', |
| }); |
| expect(logger.warn).toHaveBeenCalledWith( |
| 'Duplicate key error: E11000 duplicate key error collection: test.users index: email_1 dup key: { email: "test@example.com" }', |
| ); |
| }); |
|
|
| it('should handle duplicate key error with multiple fields', () => { |
| const duplicateKeyError = { |
| name: 'MongoServerError', |
| message: 'Duplicate key error', |
| code: 11000, |
| keyValue: { email: 'test@example.com', username: 'testuser' }, |
| errmsg: |
| 'E11000 duplicate key error collection: test.users index: email_1 dup key: { email: "test@example.com" }', |
| } as MongoServerError; |
|
|
| ErrorController(duplicateKeyError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(409); |
| expect(mockRes.send).toHaveBeenCalledWith({ |
| messages: 'An document with that ["email","username"] already exists.', |
| fields: '["email","username"]', |
| }); |
| expect(logger.warn).toHaveBeenCalledWith( |
| 'Duplicate key error: E11000 duplicate key error collection: test.users index: email_1 dup key: { email: "test@example.com" }', |
| ); |
| }); |
|
|
| it('should handle error with code 11000 as string', () => { |
| const duplicateKeyError = { |
| name: 'MongoServerError', |
| message: 'Duplicate key error', |
| code: 11000, |
| keyValue: { email: 'test@example.com' }, |
| errmsg: |
| 'E11000 duplicate key error collection: test.users index: email_1 dup key: { email: "test@example.com" }', |
| } as MongoServerError; |
|
|
| ErrorController(duplicateKeyError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(409); |
| expect(mockRes.send).toHaveBeenCalledWith({ |
| messages: 'An document with that ["email"] already exists.', |
| fields: '["email"]', |
| }); |
| }); |
| }); |
|
|
| describe('SyntaxError handling', () => { |
| it('should handle errors with statusCode and body', () => { |
| const syntaxError = { |
| statusCode: 400, |
| body: 'Invalid JSON syntax', |
| } as CustomError; |
|
|
| ErrorController(syntaxError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(400); |
| expect(mockRes.send).toHaveBeenCalledWith('Invalid JSON syntax'); |
| }); |
|
|
| it('should handle errors with different statusCode and body', () => { |
| const customError = { |
| statusCode: 422, |
| body: { error: 'Unprocessable entity' }, |
| } as CustomError; |
|
|
| ErrorController(customError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(422); |
| expect(mockRes.send).toHaveBeenCalledWith({ error: 'Unprocessable entity' }); |
| }); |
|
|
| it('should handle error with statusCode but no body', () => { |
| const partialError = { |
| statusCode: 400, |
| } as CustomError; |
|
|
| ErrorController(partialError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(500); |
| expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.'); |
| }); |
|
|
| it('should handle error with body but no statusCode', () => { |
| const partialError = { |
| body: 'Some error message', |
| } as CustomError; |
|
|
| ErrorController(partialError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(500); |
| expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.'); |
| }); |
| }); |
|
|
| describe('Unknown error handling', () => { |
| it('should handle unknown errors', () => { |
| const unknownError = new Error('Some unknown error'); |
|
|
| ErrorController(unknownError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(500); |
| expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.'); |
| expect(logger.error).toHaveBeenCalledWith('ErrorController => error', unknownError); |
| }); |
|
|
| it('should handle errors with code other than 11000', () => { |
| const mongoError = { |
| code: 11100, |
| message: 'Some MongoDB error', |
| } as MongoServerError; |
|
|
| ErrorController(mongoError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(500); |
| expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.'); |
| expect(logger.error).toHaveBeenCalledWith('ErrorController => error', mongoError); |
| }); |
|
|
| it('should handle generic errors', () => { |
| const genericError = new Error('Test error'); |
|
|
| ErrorController(genericError, mockReq, mockRes, mockNext); |
|
|
| expect(mockRes.status).toHaveBeenCalledWith(500); |
| expect(mockRes.send).toHaveBeenCalledWith('An unknown error occurred.'); |
| expect(logger.error).toHaveBeenCalledWith('ErrorController => error', genericError); |
| }); |
| }); |
|
|
| describe('Catch block handling', () => { |
| beforeEach(() => { |
| |
| (logger.error as jest.Mock).mockRestore(); |
| (logger.error as jest.Mock) = jest.fn(); |
| }); |
|
|
| it('should handle errors when logger.error throws', () => { |
| |
| const freshMockRes = { |
| status: jest.fn().mockReturnThis(), |
| send: jest.fn(), |
| } as unknown as Response; |
|
|
| |
| (logger.error as jest.Mock) |
| .mockImplementationOnce(() => { |
| throw new Error('Logger error'); |
| }) |
| .mockImplementation(() => {}); |
|
|
| const testError = new Error('Test error'); |
|
|
| ErrorController(testError, mockReq, freshMockRes, mockNext); |
|
|
| expect(freshMockRes.status).toHaveBeenCalledWith(500); |
| expect(freshMockRes.send).toHaveBeenCalledWith('Processing error in ErrorController.'); |
| expect(logger.error).toHaveBeenCalledTimes(2); |
| }); |
| }); |
| }); |
|
|