AtAuth
at main 121 lines 4.2 kB view raw
1import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; 2import { HttpError, httpError, sanitizeError, internalError } from './errors.js'; 3import type { ErrorResponse } from './errors.js'; 4 5describe('HttpError', () => { 6 it('should store statusCode, code, and message', () => { 7 const err = new HttpError(400, 'bad_request', 'Invalid input'); 8 expect(err.statusCode).toBe(400); 9 expect(err.code).toBe('bad_request'); 10 expect(err.message).toBe('Invalid input'); 11 expect(err.name).toBe('HttpError'); 12 }); 13 14 it('should be an instance of Error', () => { 15 const err = new HttpError(500, 'server_error', 'Boom'); 16 expect(err).toBeInstanceOf(Error); 17 expect(err).toBeInstanceOf(HttpError); 18 }); 19}); 20 21describe('httpError factories', () => { 22 it('badRequest should create 400', () => { 23 const err = httpError.badRequest('missing_field', 'Field X is required'); 24 expect(err.statusCode).toBe(400); 25 expect(err.code).toBe('missing_field'); 26 expect(err.message).toBe('Field X is required'); 27 }); 28 29 it('unauthorized should create 401', () => { 30 const err = httpError.unauthorized('invalid_token', 'Token expired'); 31 expect(err.statusCode).toBe(401); 32 expect(err.code).toBe('invalid_token'); 33 }); 34 35 it('forbidden should create 403', () => { 36 const err = httpError.forbidden('access_denied', 'Not allowed'); 37 expect(err.statusCode).toBe(403); 38 expect(err.code).toBe('access_denied'); 39 }); 40 41 it('notFound should create 404', () => { 42 const err = httpError.notFound('not_found', 'Resource missing'); 43 expect(err.statusCode).toBe(404); 44 expect(err.code).toBe('not_found'); 45 }); 46 47 it('conflict should create 409', () => { 48 const err = httpError.conflict('duplicate', 'Already exists'); 49 expect(err.statusCode).toBe(409); 50 expect(err.code).toBe('duplicate'); 51 }); 52 53 it('internalServerError should create 500', () => { 54 const err = httpError.internalServerError('server_error', 'Something broke'); 55 expect(err.statusCode).toBe(500); 56 expect(err.code).toBe('server_error'); 57 }); 58}); 59 60describe('sanitizeError', () => { 61 let consoleSpy: ReturnType<typeof vi.spyOn>; 62 63 beforeEach(() => { 64 consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); 65 }); 66 67 afterEach(() => { 68 consoleSpy.mockRestore(); 69 vi.unstubAllEnvs(); 70 }); 71 72 it('should log the error with context', () => { 73 const err = new Error('test error'); 74 sanitizeError(err, 'Token verify'); 75 expect(consoleSpy).toHaveBeenCalledWith('Token verify error:', err); 76 }); 77 78 it('should return generic message in production', () => { 79 vi.stubEnv('NODE_ENV', 'production'); 80 const result = sanitizeError(new Error('secret details'), 'ctx'); 81 expect(result).toBe('An internal error occurred. Please try again later.'); 82 }); 83 84 it('should return generic message in test mode', () => { 85 // NODE_ENV is 'test' by default in vitest 86 const result = sanitizeError(new Error('details'), 'ctx'); 87 expect(result).toBe('An internal error occurred. Please try again later.'); 88 }); 89 90 it('should strip file paths in development mode', () => { 91 vi.stubEnv('NODE_ENV', 'development'); 92 const result = sanitizeError(new Error('Failed at /home/user/app/src/index.ts:42'), 'ctx'); 93 expect(result).not.toContain('/home/user'); 94 expect(result).toContain('[path]'); 95 }); 96 97 it('should return generic message for non-Error objects', () => { 98 vi.stubEnv('NODE_ENV', 'development'); 99 const result = sanitizeError('string error', 'ctx'); 100 expect(result).toBe('An internal error occurred. Please try again later.'); 101 }); 102}); 103 104describe('internalError', () => { 105 let consoleSpy: ReturnType<typeof vi.spyOn>; 106 107 beforeEach(() => { 108 consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); 109 }); 110 111 afterEach(() => { 112 consoleSpy.mockRestore(); 113 }); 114 115 it('should return ErrorResponse with error code and sanitized message', () => { 116 const result: ErrorResponse = internalError('db_error', new Error('connection lost'), 'DB query'); 117 expect(result.error).toBe('db_error'); 118 expect(result.message).toBeTypeOf('string'); 119 expect(result.message).not.toContain('connection lost'); 120 }); 121});