@geekmidas/errors 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,591 +1,591 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import {
3
- BadGatewayError,
4
- BadRequestError,
5
- ConflictError,
6
- ForbiddenError,
7
- GatewayTimeoutError,
8
- HttpError,
9
- HttpStatusCode,
10
- InternalServerError,
11
- MethodNotAllowedError,
12
- NotFoundError,
13
- NotImplementedError,
14
- ServiceUnavailableError,
15
- TooManyRequestsError,
16
- UnauthorizedError,
17
- UnprocessableEntityError,
18
- createError,
19
- createHttpError,
20
- isClientError,
21
- isHttpError,
22
- isServerError,
23
- wrapError,
3
+ BadGatewayError,
4
+ BadRequestError,
5
+ ConflictError,
6
+ createError,
7
+ createHttpError,
8
+ ForbiddenError,
9
+ GatewayTimeoutError,
10
+ HttpError,
11
+ HttpStatusCode,
12
+ InternalServerError,
13
+ isClientError,
14
+ isHttpError,
15
+ isServerError,
16
+ MethodNotAllowedError,
17
+ NotFoundError,
18
+ NotImplementedError,
19
+ ServiceUnavailableError,
20
+ TooManyRequestsError,
21
+ UnauthorizedError,
22
+ UnprocessableEntityError,
23
+ wrapError,
24
24
  } from '../index';
25
25
 
26
26
  describe('HttpError', () => {
27
- it('should create basic HTTP error', () => {
28
- const error = new HttpError(400, 'Bad request');
29
-
30
- expect(error).toBeInstanceOf(Error);
31
- expect(error).toBeInstanceOf(HttpError);
32
- expect(error.statusCode).toBe(400);
33
- expect(error.statusMessage).toBe('Bad Request');
34
- expect(error.message).toBe('Bad request');
35
- expect(error.name).toBe('HttpError');
36
- expect(error.isHttpError).toBe(true);
37
- });
38
-
39
- it('should use default status message when not provided', () => {
40
- const error = new HttpError(404);
41
-
42
- expect(error.statusMessage).toBe('Not Found');
43
- expect(error.message).toBe('HTTP Error'); // Default from constructor
44
- });
45
-
46
- it('should include error details', () => {
47
- const error = new HttpError(400, 'Invalid input', {
48
- details: { field: 'email', value: 'invalid' },
49
- });
50
-
51
- expect(error.details).toEqual({ field: 'email', value: 'invalid' });
52
- });
53
-
54
- it('should include error code', () => {
55
- const error = new HttpError(400, 'Invalid input', {
56
- code: 'VALIDATION_ERROR',
57
- });
58
-
59
- expect(error.code).toBe('VALIDATION_ERROR');
60
- });
61
-
62
- it('should support error cause chaining', () => {
63
- const originalError = new Error('Database connection failed');
64
- const error = new HttpError(500, 'Internal error', {
65
- cause: originalError,
66
- });
67
-
68
- expect(error.cause).toBe(originalError);
69
- });
70
-
71
- it('should serialize to JSON', () => {
72
- const error = new HttpError(404, 'Not found', {
73
- details: { id: '123' },
74
- code: 'NOT_FOUND',
75
- });
76
-
77
- const json = error.toJSON();
78
-
79
- expect(json).toMatchObject({
80
- name: 'HttpError',
81
- message: 'Not found',
82
- statusCode: 404,
83
- statusMessage: 'Not Found',
84
- code: 'NOT_FOUND',
85
- details: { id: '123' },
86
- });
87
- expect(json.stack).toBeDefined();
88
- });
89
-
90
- it('should generate error body', () => {
91
- const error = new HttpError(400, 'Bad request', {
92
- details: { field: 'email' },
93
- code: 'INVALID_EMAIL',
94
- });
95
-
96
- const body = JSON.parse(error.body);
97
-
98
- expect(body).toEqual({
99
- message: 'Bad request',
100
- code: 'INVALID_EMAIL',
101
- error: { field: 'email' },
102
- });
103
- });
104
-
105
- it('should handle unknown status codes', () => {
106
- const error = new HttpError(418);
107
-
108
- expect(error.statusCode).toBe(418);
109
- expect(error.statusMessage).toBe('Unknown Error');
110
- });
27
+ it('should create basic HTTP error', () => {
28
+ const error = new HttpError(400, 'Bad request');
29
+
30
+ expect(error).toBeInstanceOf(Error);
31
+ expect(error).toBeInstanceOf(HttpError);
32
+ expect(error.statusCode).toBe(400);
33
+ expect(error.statusMessage).toBe('Bad Request');
34
+ expect(error.message).toBe('Bad request');
35
+ expect(error.name).toBe('HttpError');
36
+ expect(error.isHttpError).toBe(true);
37
+ });
38
+
39
+ it('should use default status message when not provided', () => {
40
+ const error = new HttpError(404);
41
+
42
+ expect(error.statusMessage).toBe('Not Found');
43
+ expect(error.message).toBe('HTTP Error'); // Default from constructor
44
+ });
45
+
46
+ it('should include error details', () => {
47
+ const error = new HttpError(400, 'Invalid input', {
48
+ details: { field: 'email', value: 'invalid' },
49
+ });
50
+
51
+ expect(error.details).toEqual({ field: 'email', value: 'invalid' });
52
+ });
53
+
54
+ it('should include error code', () => {
55
+ const error = new HttpError(400, 'Invalid input', {
56
+ code: 'VALIDATION_ERROR',
57
+ });
58
+
59
+ expect(error.code).toBe('VALIDATION_ERROR');
60
+ });
61
+
62
+ it('should support error cause chaining', () => {
63
+ const originalError = new Error('Database connection failed');
64
+ const error = new HttpError(500, 'Internal error', {
65
+ cause: originalError,
66
+ });
67
+
68
+ expect(error.cause).toBe(originalError);
69
+ });
70
+
71
+ it('should serialize to JSON', () => {
72
+ const error = new HttpError(404, 'Not found', {
73
+ details: { id: '123' },
74
+ code: 'NOT_FOUND',
75
+ });
76
+
77
+ const json = error.toJSON();
78
+
79
+ expect(json).toMatchObject({
80
+ name: 'HttpError',
81
+ message: 'Not found',
82
+ statusCode: 404,
83
+ statusMessage: 'Not Found',
84
+ code: 'NOT_FOUND',
85
+ details: { id: '123' },
86
+ });
87
+ expect(json.stack).toBeDefined();
88
+ });
89
+
90
+ it('should generate error body', () => {
91
+ const error = new HttpError(400, 'Bad request', {
92
+ details: { field: 'email' },
93
+ code: 'INVALID_EMAIL',
94
+ });
95
+
96
+ const body = JSON.parse(error.body);
97
+
98
+ expect(body).toEqual({
99
+ message: 'Bad request',
100
+ code: 'INVALID_EMAIL',
101
+ error: { field: 'email' },
102
+ });
103
+ });
104
+
105
+ it('should handle unknown status codes', () => {
106
+ const error = new HttpError(418);
107
+
108
+ expect(error.statusCode).toBe(418);
109
+ expect(error.statusMessage).toBe('Unknown Error');
110
+ });
111
111
  });
112
112
 
113
113
  describe('Client Error Classes (4xx)', () => {
114
- describe('BadRequestError', () => {
115
- it('should create 400 error', () => {
116
- const error = new BadRequestError('Invalid input');
117
-
118
- expect(error.statusCode).toBe(400);
119
- expect(error.statusMessage).toBe('Bad Request');
120
- expect(error.message).toBe('Invalid input');
121
- });
122
-
123
- it('should include details', () => {
124
- const error = new BadRequestError('Invalid input', { field: 'email' });
125
-
126
- expect(error.details).toEqual({ field: 'email' });
127
- });
128
- });
129
-
130
- describe('UnauthorizedError', () => {
131
- it('should create 401 error', () => {
132
- const error = new UnauthorizedError('Invalid token');
133
-
134
- expect(error.statusCode).toBe(401);
135
- expect(error.statusMessage).toBe('Unauthorized');
136
- expect(error.message).toBe('Invalid token');
137
- });
138
- });
139
-
140
- describe('ForbiddenError', () => {
141
- it('should create 403 error', () => {
142
- const error = new ForbiddenError('Access denied');
143
-
144
- expect(error.statusCode).toBe(403);
145
- expect(error.statusMessage).toBe('Forbidden');
146
- expect(error.message).toBe('Access denied');
147
- });
148
- });
149
-
150
- describe('NotFoundError', () => {
151
- it('should create 404 error', () => {
152
- const error = new NotFoundError('User not found');
153
-
154
- expect(error.statusCode).toBe(404);
155
- expect(error.statusMessage).toBe('Not Found');
156
- expect(error.message).toBe('User not found');
157
- });
158
- });
159
-
160
- describe('MethodNotAllowedError', () => {
161
- it('should create 405 error', () => {
162
- const error = new MethodNotAllowedError('DELETE not allowed');
163
-
164
- expect(error.statusCode).toBe(405);
165
- expect(error.statusMessage).toBe('Method Not Allowed');
166
- expect(error.message).toBe('DELETE not allowed');
167
- });
168
-
169
- it('should include allowed methods', () => {
170
- const error = new MethodNotAllowedError('DELETE not allowed', [
171
- 'GET',
172
- 'POST',
173
- 'PUT',
174
- ]);
175
-
176
- expect(error.details).toEqual({
177
- allowedMethods: ['GET', 'POST', 'PUT'],
178
- });
179
- });
180
- });
181
-
182
- describe('ConflictError', () => {
183
- it('should create 409 error', () => {
184
- const error = new ConflictError('Email already exists');
185
-
186
- expect(error.statusCode).toBe(409);
187
- expect(error.statusMessage).toBe('Conflict');
188
- expect(error.message).toBe('Email already exists');
189
- });
190
- });
191
-
192
- describe('UnprocessableEntityError', () => {
193
- it('should create 422 error', () => {
194
- const error = new UnprocessableEntityError('Validation failed');
195
-
196
- expect(error.statusCode).toBe(422);
197
- expect(error.statusMessage).toBe('Unprocessable Entity');
198
- expect(error.message).toBe('Validation failed');
199
- });
200
-
201
- it('should include validation errors', () => {
202
- const error = new UnprocessableEntityError('Validation failed', {
203
- email: 'Invalid format',
204
- age: 'Must be 18+',
205
- });
206
-
207
- expect(error.details).toEqual({
208
- validationErrors: {
209
- email: 'Invalid format',
210
- age: 'Must be 18+',
211
- },
212
- });
213
- });
214
- });
215
-
216
- describe('TooManyRequestsError', () => {
217
- it('should create 429 error', () => {
218
- const error = new TooManyRequestsError('Rate limit exceeded');
219
-
220
- expect(error.statusCode).toBe(429);
221
- expect(error.statusMessage).toBe('Too Many Requests');
222
- expect(error.message).toBe('Rate limit exceeded');
223
- });
224
-
225
- it('should include retry after', () => {
226
- const error = new TooManyRequestsError('Rate limit exceeded', 60);
227
-
228
- expect(error.details).toEqual({ retryAfter: 60 });
229
- });
230
- });
114
+ describe('BadRequestError', () => {
115
+ it('should create 400 error', () => {
116
+ const error = new BadRequestError('Invalid input');
117
+
118
+ expect(error.statusCode).toBe(400);
119
+ expect(error.statusMessage).toBe('Bad Request');
120
+ expect(error.message).toBe('Invalid input');
121
+ });
122
+
123
+ it('should include details', () => {
124
+ const error = new BadRequestError('Invalid input', { field: 'email' });
125
+
126
+ expect(error.details).toEqual({ field: 'email' });
127
+ });
128
+ });
129
+
130
+ describe('UnauthorizedError', () => {
131
+ it('should create 401 error', () => {
132
+ const error = new UnauthorizedError('Invalid token');
133
+
134
+ expect(error.statusCode).toBe(401);
135
+ expect(error.statusMessage).toBe('Unauthorized');
136
+ expect(error.message).toBe('Invalid token');
137
+ });
138
+ });
139
+
140
+ describe('ForbiddenError', () => {
141
+ it('should create 403 error', () => {
142
+ const error = new ForbiddenError('Access denied');
143
+
144
+ expect(error.statusCode).toBe(403);
145
+ expect(error.statusMessage).toBe('Forbidden');
146
+ expect(error.message).toBe('Access denied');
147
+ });
148
+ });
149
+
150
+ describe('NotFoundError', () => {
151
+ it('should create 404 error', () => {
152
+ const error = new NotFoundError('User not found');
153
+
154
+ expect(error.statusCode).toBe(404);
155
+ expect(error.statusMessage).toBe('Not Found');
156
+ expect(error.message).toBe('User not found');
157
+ });
158
+ });
159
+
160
+ describe('MethodNotAllowedError', () => {
161
+ it('should create 405 error', () => {
162
+ const error = new MethodNotAllowedError('DELETE not allowed');
163
+
164
+ expect(error.statusCode).toBe(405);
165
+ expect(error.statusMessage).toBe('Method Not Allowed');
166
+ expect(error.message).toBe('DELETE not allowed');
167
+ });
168
+
169
+ it('should include allowed methods', () => {
170
+ const error = new MethodNotAllowedError('DELETE not allowed', [
171
+ 'GET',
172
+ 'POST',
173
+ 'PUT',
174
+ ]);
175
+
176
+ expect(error.details).toEqual({
177
+ allowedMethods: ['GET', 'POST', 'PUT'],
178
+ });
179
+ });
180
+ });
181
+
182
+ describe('ConflictError', () => {
183
+ it('should create 409 error', () => {
184
+ const error = new ConflictError('Email already exists');
185
+
186
+ expect(error.statusCode).toBe(409);
187
+ expect(error.statusMessage).toBe('Conflict');
188
+ expect(error.message).toBe('Email already exists');
189
+ });
190
+ });
191
+
192
+ describe('UnprocessableEntityError', () => {
193
+ it('should create 422 error', () => {
194
+ const error = new UnprocessableEntityError('Validation failed');
195
+
196
+ expect(error.statusCode).toBe(422);
197
+ expect(error.statusMessage).toBe('Unprocessable Entity');
198
+ expect(error.message).toBe('Validation failed');
199
+ });
200
+
201
+ it('should include validation errors', () => {
202
+ const error = new UnprocessableEntityError('Validation failed', {
203
+ email: 'Invalid format',
204
+ age: 'Must be 18+',
205
+ });
206
+
207
+ expect(error.details).toEqual({
208
+ validationErrors: {
209
+ email: 'Invalid format',
210
+ age: 'Must be 18+',
211
+ },
212
+ });
213
+ });
214
+ });
215
+
216
+ describe('TooManyRequestsError', () => {
217
+ it('should create 429 error', () => {
218
+ const error = new TooManyRequestsError('Rate limit exceeded');
219
+
220
+ expect(error.statusCode).toBe(429);
221
+ expect(error.statusMessage).toBe('Too Many Requests');
222
+ expect(error.message).toBe('Rate limit exceeded');
223
+ });
224
+
225
+ it('should include retry after', () => {
226
+ const error = new TooManyRequestsError('Rate limit exceeded', 60);
227
+
228
+ expect(error.details).toEqual({ retryAfter: 60 });
229
+ });
230
+ });
231
231
  });
232
232
 
233
233
  describe('Server Error Classes (5xx)', () => {
234
- describe('InternalServerError', () => {
235
- it('should create 500 error', () => {
236
- const error = new InternalServerError('Database error');
237
-
238
- expect(error.statusCode).toBe(500);
239
- expect(error.statusMessage).toBe('Internal Server Error');
240
- expect(error.message).toBe('Database error');
241
- });
242
- });
243
-
244
- describe('NotImplementedError', () => {
245
- it('should create 501 error', () => {
246
- const error = new NotImplementedError('Feature not implemented');
247
-
248
- expect(error.statusCode).toBe(501);
249
- expect(error.statusMessage).toBe('Not Implemented');
250
- expect(error.message).toBe('Feature not implemented');
251
- });
252
- });
253
-
254
- describe('BadGatewayError', () => {
255
- it('should create 502 error', () => {
256
- const error = new BadGatewayError('Upstream error');
257
-
258
- expect(error.statusCode).toBe(502);
259
- expect(error.statusMessage).toBe('Bad Gateway');
260
- expect(error.message).toBe('Upstream error');
261
- });
262
- });
263
-
264
- describe('ServiceUnavailableError', () => {
265
- it('should create 503 error', () => {
266
- const error = new ServiceUnavailableError('Service down');
267
-
268
- expect(error.statusCode).toBe(503);
269
- expect(error.statusMessage).toBe('Service Unavailable');
270
- expect(error.message).toBe('Service down');
271
- });
272
-
273
- it('should include retry after', () => {
274
- const error = new ServiceUnavailableError('Service down', 300);
275
-
276
- expect(error.details).toEqual({ retryAfter: 300 });
277
- });
278
- });
279
-
280
- describe('GatewayTimeoutError', () => {
281
- it('should create 504 error', () => {
282
- const error = new GatewayTimeoutError('Upstream timeout');
283
-
284
- expect(error.statusCode).toBe(504);
285
- expect(error.statusMessage).toBe('Gateway Timeout');
286
- expect(error.message).toBe('Upstream timeout');
287
- });
288
- });
234
+ describe('InternalServerError', () => {
235
+ it('should create 500 error', () => {
236
+ const error = new InternalServerError('Database error');
237
+
238
+ expect(error.statusCode).toBe(500);
239
+ expect(error.statusMessage).toBe('Internal Server Error');
240
+ expect(error.message).toBe('Database error');
241
+ });
242
+ });
243
+
244
+ describe('NotImplementedError', () => {
245
+ it('should create 501 error', () => {
246
+ const error = new NotImplementedError('Feature not implemented');
247
+
248
+ expect(error.statusCode).toBe(501);
249
+ expect(error.statusMessage).toBe('Not Implemented');
250
+ expect(error.message).toBe('Feature not implemented');
251
+ });
252
+ });
253
+
254
+ describe('BadGatewayError', () => {
255
+ it('should create 502 error', () => {
256
+ const error = new BadGatewayError('Upstream error');
257
+
258
+ expect(error.statusCode).toBe(502);
259
+ expect(error.statusMessage).toBe('Bad Gateway');
260
+ expect(error.message).toBe('Upstream error');
261
+ });
262
+ });
263
+
264
+ describe('ServiceUnavailableError', () => {
265
+ it('should create 503 error', () => {
266
+ const error = new ServiceUnavailableError('Service down');
267
+
268
+ expect(error.statusCode).toBe(503);
269
+ expect(error.statusMessage).toBe('Service Unavailable');
270
+ expect(error.message).toBe('Service down');
271
+ });
272
+
273
+ it('should include retry after', () => {
274
+ const error = new ServiceUnavailableError('Service down', 300);
275
+
276
+ expect(error.details).toEqual({ retryAfter: 300 });
277
+ });
278
+ });
279
+
280
+ describe('GatewayTimeoutError', () => {
281
+ it('should create 504 error', () => {
282
+ const error = new GatewayTimeoutError('Upstream timeout');
283
+
284
+ expect(error.statusCode).toBe(504);
285
+ expect(error.statusMessage).toBe('Gateway Timeout');
286
+ expect(error.message).toBe('Upstream timeout');
287
+ });
288
+ });
289
289
  });
290
290
 
291
291
  describe('createError factory', () => {
292
- it('should create badRequest error', () => {
293
- const error = createError.badRequest('Invalid input');
292
+ it('should create badRequest error', () => {
293
+ const error = createError.badRequest('Invalid input');
294
294
 
295
- expect(error).toBeInstanceOf(BadRequestError);
296
- expect(error.statusCode).toBe(400);
297
- });
295
+ expect(error).toBeInstanceOf(BadRequestError);
296
+ expect(error.statusCode).toBe(400);
297
+ });
298
298
 
299
- it('should create unauthorized error', () => {
300
- const error = createError.unauthorized('Invalid token');
299
+ it('should create unauthorized error', () => {
300
+ const error = createError.unauthorized('Invalid token');
301
301
 
302
- expect(error).toBeInstanceOf(UnauthorizedError);
303
- expect(error.statusCode).toBe(401);
304
- });
302
+ expect(error).toBeInstanceOf(UnauthorizedError);
303
+ expect(error.statusCode).toBe(401);
304
+ });
305
305
 
306
- it('should create forbidden error', () => {
307
- const error = createError.forbidden('Access denied');
306
+ it('should create forbidden error', () => {
307
+ const error = createError.forbidden('Access denied');
308
308
 
309
- expect(error).toBeInstanceOf(ForbiddenError);
310
- expect(error.statusCode).toBe(403);
311
- });
309
+ expect(error).toBeInstanceOf(ForbiddenError);
310
+ expect(error.statusCode).toBe(403);
311
+ });
312
312
 
313
- it('should create notFound error', () => {
314
- const error = createError.notFound('Resource not found');
313
+ it('should create notFound error', () => {
314
+ const error = createError.notFound('Resource not found');
315
315
 
316
- expect(error).toBeInstanceOf(NotFoundError);
317
- expect(error.statusCode).toBe(404);
318
- });
316
+ expect(error).toBeInstanceOf(NotFoundError);
317
+ expect(error.statusCode).toBe(404);
318
+ });
319
319
 
320
- it('should create methodNotAllowed error', () => {
321
- const error = createError.methodNotAllowed('Method not allowed', [
322
- 'GET',
323
- 'POST',
324
- ]);
320
+ it('should create methodNotAllowed error', () => {
321
+ const error = createError.methodNotAllowed('Method not allowed', [
322
+ 'GET',
323
+ 'POST',
324
+ ]);
325
325
 
326
- expect(error).toBeInstanceOf(MethodNotAllowedError);
327
- expect(error.statusCode).toBe(405);
328
- });
326
+ expect(error).toBeInstanceOf(MethodNotAllowedError);
327
+ expect(error.statusCode).toBe(405);
328
+ });
329
329
 
330
- it('should create conflict error', () => {
331
- const error = createError.conflict('Resource exists');
330
+ it('should create conflict error', () => {
331
+ const error = createError.conflict('Resource exists');
332
332
 
333
- expect(error).toBeInstanceOf(ConflictError);
334
- expect(error.statusCode).toBe(409);
335
- });
333
+ expect(error).toBeInstanceOf(ConflictError);
334
+ expect(error.statusCode).toBe(409);
335
+ });
336
336
 
337
- it('should create unprocessableEntity error', () => {
338
- const error = createError.unprocessableEntity('Validation failed');
337
+ it('should create unprocessableEntity error', () => {
338
+ const error = createError.unprocessableEntity('Validation failed');
339
339
 
340
- expect(error).toBeInstanceOf(UnprocessableEntityError);
341
- expect(error.statusCode).toBe(422);
342
- });
340
+ expect(error).toBeInstanceOf(UnprocessableEntityError);
341
+ expect(error.statusCode).toBe(422);
342
+ });
343
343
 
344
- it('should create tooManyRequests error', () => {
345
- const error = createError.tooManyRequests('Rate limited', 60);
344
+ it('should create tooManyRequests error', () => {
345
+ const error = createError.tooManyRequests('Rate limited', 60);
346
346
 
347
- expect(error).toBeInstanceOf(TooManyRequestsError);
348
- expect(error.statusCode).toBe(429);
349
- });
347
+ expect(error).toBeInstanceOf(TooManyRequestsError);
348
+ expect(error.statusCode).toBe(429);
349
+ });
350
350
 
351
- it('should create internalServerError error', () => {
352
- const error = createError.internalServerError('Server error');
351
+ it('should create internalServerError error', () => {
352
+ const error = createError.internalServerError('Server error');
353
353
 
354
- expect(error).toBeInstanceOf(InternalServerError);
355
- expect(error.statusCode).toBe(500);
356
- });
354
+ expect(error).toBeInstanceOf(InternalServerError);
355
+ expect(error.statusCode).toBe(500);
356
+ });
357
357
 
358
- it('should create notImplemented error', () => {
359
- const error = createError.notImplemented('Not implemented');
358
+ it('should create notImplemented error', () => {
359
+ const error = createError.notImplemented('Not implemented');
360
360
 
361
- expect(error).toBeInstanceOf(NotImplementedError);
362
- expect(error.statusCode).toBe(501);
363
- });
361
+ expect(error).toBeInstanceOf(NotImplementedError);
362
+ expect(error.statusCode).toBe(501);
363
+ });
364
364
 
365
- it('should create badGateway error', () => {
366
- const error = createError.badGateway('Gateway error');
365
+ it('should create badGateway error', () => {
366
+ const error = createError.badGateway('Gateway error');
367
367
 
368
- expect(error).toBeInstanceOf(BadGatewayError);
369
- expect(error.statusCode).toBe(502);
370
- });
368
+ expect(error).toBeInstanceOf(BadGatewayError);
369
+ expect(error.statusCode).toBe(502);
370
+ });
371
371
 
372
- it('should create serviceUnavailable error', () => {
373
- const error = createError.serviceUnavailable('Service down', 300);
372
+ it('should create serviceUnavailable error', () => {
373
+ const error = createError.serviceUnavailable('Service down', 300);
374
374
 
375
- expect(error).toBeInstanceOf(ServiceUnavailableError);
376
- expect(error.statusCode).toBe(503);
377
- });
375
+ expect(error).toBeInstanceOf(ServiceUnavailableError);
376
+ expect(error.statusCode).toBe(503);
377
+ });
378
378
 
379
- it('should create gatewayTimeout error', () => {
380
- const error = createError.gatewayTimeout('Timeout');
379
+ it('should create gatewayTimeout error', () => {
380
+ const error = createError.gatewayTimeout('Timeout');
381
381
 
382
- expect(error).toBeInstanceOf(GatewayTimeoutError);
383
- expect(error.statusCode).toBe(504);
384
- });
382
+ expect(error).toBeInstanceOf(GatewayTimeoutError);
383
+ expect(error.statusCode).toBe(504);
384
+ });
385
385
  });
386
386
 
387
387
  describe('createHttpError function', () => {
388
- it('should create error with valid status code', () => {
389
- const error = createHttpError(404, 'Not found');
390
-
391
- expect(error).toBeInstanceOf(NotFoundError);
392
- expect(error.statusCode).toBe(404);
393
- expect(error.message).toBe('Not found');
394
- });
395
-
396
- it('should create error with type-safe options', () => {
397
- const error = createHttpError(429, 'Rate limited', { retryAfter: 60 });
398
-
399
- expect(error).toBeInstanceOf(TooManyRequestsError);
400
- expect(error.details).toEqual({ retryAfter: 60 });
401
- });
402
-
403
- it('should create error with validation errors', () => {
404
- const error = createHttpError(422, 'Validation failed', {
405
- validationErrors: { email: 'Invalid' },
406
- });
407
-
408
- expect(error).toBeInstanceOf(UnprocessableEntityError);
409
- expect(error.details).toEqual({
410
- validationErrors: { email: 'Invalid' },
411
- });
412
- });
413
-
414
- it('should create error with allowed methods', () => {
415
- const error = createHttpError(405, 'Method not allowed', {
416
- allowedMethods: ['GET', 'POST'],
417
- });
418
-
419
- expect(error).toBeInstanceOf(MethodNotAllowedError);
420
- expect(error.details).toEqual({ allowedMethods: ['GET', 'POST'] });
421
- });
422
-
423
- it('should create generic error for unknown status code', () => {
424
- const error = createHttpError(418, 'I am a teapot');
425
-
426
- expect(error).toBeInstanceOf(HttpError);
427
- expect(error.statusCode).toBe(418);
428
- expect(error.message).toBe('I am a teapot');
429
- });
388
+ it('should create error with valid status code', () => {
389
+ const error = createHttpError(404, 'Not found');
390
+
391
+ expect(error).toBeInstanceOf(NotFoundError);
392
+ expect(error.statusCode).toBe(404);
393
+ expect(error.message).toBe('Not found');
394
+ });
395
+
396
+ it('should create error with type-safe options', () => {
397
+ const error = createHttpError(429, 'Rate limited', { retryAfter: 60 });
398
+
399
+ expect(error).toBeInstanceOf(TooManyRequestsError);
400
+ expect(error.details).toEqual({ retryAfter: 60 });
401
+ });
402
+
403
+ it('should create error with validation errors', () => {
404
+ const error = createHttpError(422, 'Validation failed', {
405
+ validationErrors: { email: 'Invalid' },
406
+ });
407
+
408
+ expect(error).toBeInstanceOf(UnprocessableEntityError);
409
+ expect(error.details).toEqual({
410
+ validationErrors: { email: 'Invalid' },
411
+ });
412
+ });
413
+
414
+ it('should create error with allowed methods', () => {
415
+ const error = createHttpError(405, 'Method not allowed', {
416
+ allowedMethods: ['GET', 'POST'],
417
+ });
418
+
419
+ expect(error).toBeInstanceOf(MethodNotAllowedError);
420
+ expect(error.details).toEqual({ allowedMethods: ['GET', 'POST'] });
421
+ });
422
+
423
+ it('should create generic error for unknown status code', () => {
424
+ const error = createHttpError(418, 'I am a teapot');
425
+
426
+ expect(error).toBeInstanceOf(HttpError);
427
+ expect(error.statusCode).toBe(418);
428
+ expect(error.message).toBe('I am a teapot');
429
+ });
430
430
  });
431
431
 
432
432
  describe('Type guards', () => {
433
- describe('isHttpError', () => {
434
- it('should return true for HttpError instances', () => {
435
- const error = new HttpError(400);
433
+ describe('isHttpError', () => {
434
+ it('should return true for HttpError instances', () => {
435
+ const error = new HttpError(400);
436
436
 
437
- expect(isHttpError(error)).toBe(true);
438
- });
437
+ expect(isHttpError(error)).toBe(true);
438
+ });
439
439
 
440
- it('should return true for HttpError subclasses', () => {
441
- const error = new NotFoundError();
440
+ it('should return true for HttpError subclasses', () => {
441
+ const error = new NotFoundError();
442
442
 
443
- expect(isHttpError(error)).toBe(true);
444
- });
443
+ expect(isHttpError(error)).toBe(true);
444
+ });
445
445
 
446
- it('should return true for duck-typed errors', () => {
447
- const error = {
448
- statusCode: 400,
449
- statusMessage: 'Bad Request',
450
- message: 'Error',
451
- isHttpError: true,
452
- };
446
+ it('should return true for duck-typed errors', () => {
447
+ const error = {
448
+ statusCode: 400,
449
+ statusMessage: 'Bad Request',
450
+ message: 'Error',
451
+ isHttpError: true,
452
+ };
453
453
 
454
- expect(isHttpError(error)).toBe(true);
455
- });
454
+ expect(isHttpError(error)).toBe(true);
455
+ });
456
456
 
457
- it('should return false for regular errors', () => {
458
- const error = new Error('Regular error');
457
+ it('should return false for regular errors', () => {
458
+ const error = new Error('Regular error');
459
459
 
460
- expect(isHttpError(error)).toBe(false);
461
- });
460
+ expect(isHttpError(error)).toBe(false);
461
+ });
462
462
 
463
- it('should return false for non-errors', () => {
464
- expect(isHttpError(null)).toBe(false);
465
- expect(isHttpError(undefined)).toBe(false);
466
- expect(isHttpError('string')).toBe(false);
467
- expect(isHttpError(123)).toBe(false);
468
- expect(isHttpError({})).toBe(false);
469
- });
470
- });
463
+ it('should return false for non-errors', () => {
464
+ expect(isHttpError(null)).toBe(false);
465
+ expect(isHttpError(undefined)).toBe(false);
466
+ expect(isHttpError('string')).toBe(false);
467
+ expect(isHttpError(123)).toBe(false);
468
+ expect(isHttpError({})).toBe(false);
469
+ });
470
+ });
471
471
 
472
- describe('isClientError', () => {
473
- it('should return true for 4xx errors', () => {
474
- const error = new BadRequestError();
472
+ describe('isClientError', () => {
473
+ it('should return true for 4xx errors', () => {
474
+ const error = new BadRequestError();
475
475
 
476
- expect(isClientError(error)).toBe(true);
477
- });
476
+ expect(isClientError(error)).toBe(true);
477
+ });
478
478
 
479
- it('should return false for 5xx errors', () => {
480
- const error = new InternalServerError();
479
+ it('should return false for 5xx errors', () => {
480
+ const error = new InternalServerError();
481
481
 
482
- expect(isClientError(error)).toBe(false);
483
- });
482
+ expect(isClientError(error)).toBe(false);
483
+ });
484
484
 
485
- it('should return false for non-http errors', () => {
486
- const error = new Error('Regular error');
485
+ it('should return false for non-http errors', () => {
486
+ const error = new Error('Regular error');
487
487
 
488
- expect(isClientError(error)).toBe(false);
489
- });
490
- });
488
+ expect(isClientError(error)).toBe(false);
489
+ });
490
+ });
491
491
 
492
- describe('isServerError', () => {
493
- it('should return true for 5xx errors', () => {
494
- const error = new InternalServerError();
492
+ describe('isServerError', () => {
493
+ it('should return true for 5xx errors', () => {
494
+ const error = new InternalServerError();
495
495
 
496
- expect(isServerError(error)).toBe(true);
497
- });
496
+ expect(isServerError(error)).toBe(true);
497
+ });
498
498
 
499
- it('should return false for 4xx errors', () => {
500
- const error = new NotFoundError();
499
+ it('should return false for 4xx errors', () => {
500
+ const error = new NotFoundError();
501
501
 
502
- expect(isServerError(error)).toBe(false);
503
- });
502
+ expect(isServerError(error)).toBe(false);
503
+ });
504
504
 
505
- it('should return false for non-http errors', () => {
506
- const error = new Error('Regular error');
505
+ it('should return false for non-http errors', () => {
506
+ const error = new Error('Regular error');
507
507
 
508
- expect(isServerError(error)).toBe(false);
509
- });
510
- });
508
+ expect(isServerError(error)).toBe(false);
509
+ });
510
+ });
511
511
  });
512
512
 
513
513
  describe('wrapError', () => {
514
- it('should return HttpError unchanged', () => {
515
- const original = new NotFoundError('Not found');
516
- const wrapped = wrapError(original);
517
-
518
- expect(wrapped).toBe(original);
519
- });
520
-
521
- it('should wrap unknown errors as 500', () => {
522
- const original = new Error('Something went wrong');
523
- const wrapped = wrapError(original);
524
-
525
- expect(wrapped).toBeInstanceOf(HttpError);
526
- expect(wrapped.statusCode).toBe(500);
527
- expect(wrapped.message).toBe('An unknown error occurred');
528
- expect(wrapped.details?.originalError).toBe(original);
529
- });
530
-
531
- it('should wrap with custom status code', () => {
532
- const original = new Error('Service unavailable');
533
- const wrapped = wrapError(original, 503);
534
-
535
- expect(wrapped.statusCode).toBe(503);
536
- });
537
-
538
- it('should wrap with custom message', () => {
539
- const original = new Error('Database error');
540
- const wrapped = wrapError(original, 500, 'Failed to query database');
541
-
542
- expect(wrapped.statusCode).toBe(500);
543
- expect(wrapped.message).toBe('Failed to query database');
544
- expect(wrapped.details?.originalError).toBe(original);
545
- });
546
-
547
- it('should handle non-error values', () => {
548
- const wrapped = wrapError('string error');
549
-
550
- expect(wrapped).toBeInstanceOf(HttpError);
551
- expect(wrapped.statusCode).toBe(500);
552
- expect(wrapped.details?.originalError).toBe('string error');
553
- });
514
+ it('should return HttpError unchanged', () => {
515
+ const original = new NotFoundError('Not found');
516
+ const wrapped = wrapError(original);
517
+
518
+ expect(wrapped).toBe(original);
519
+ });
520
+
521
+ it('should wrap unknown errors as 500', () => {
522
+ const original = new Error('Something went wrong');
523
+ const wrapped = wrapError(original);
524
+
525
+ expect(wrapped).toBeInstanceOf(HttpError);
526
+ expect(wrapped.statusCode).toBe(500);
527
+ expect(wrapped.message).toBe('An unknown error occurred');
528
+ expect(wrapped.details?.originalError).toBe(original);
529
+ });
530
+
531
+ it('should wrap with custom status code', () => {
532
+ const original = new Error('Service unavailable');
533
+ const wrapped = wrapError(original, 503);
534
+
535
+ expect(wrapped.statusCode).toBe(503);
536
+ });
537
+
538
+ it('should wrap with custom message', () => {
539
+ const original = new Error('Database error');
540
+ const wrapped = wrapError(original, 500, 'Failed to query database');
541
+
542
+ expect(wrapped.statusCode).toBe(500);
543
+ expect(wrapped.message).toBe('Failed to query database');
544
+ expect(wrapped.details?.originalError).toBe(original);
545
+ });
546
+
547
+ it('should handle non-error values', () => {
548
+ const wrapped = wrapError('string error');
549
+
550
+ expect(wrapped).toBeInstanceOf(HttpError);
551
+ expect(wrapped.statusCode).toBe(500);
552
+ expect(wrapped.details?.originalError).toBe('string error');
553
+ });
554
554
  });
555
555
 
556
556
  describe('HttpStatusCode enum', () => {
557
- it('should have 2xx success codes', () => {
558
- expect(HttpStatusCode.OK).toBe(200);
559
- expect(HttpStatusCode.CREATED).toBe(201);
560
- expect(HttpStatusCode.ACCEPTED).toBe(202);
561
- expect(HttpStatusCode.NO_CONTENT).toBe(204);
562
- });
563
-
564
- it('should have 3xx redirect codes', () => {
565
- expect(HttpStatusCode.MOVED_PERMANENTLY).toBe(301);
566
- expect(HttpStatusCode.FOUND).toBe(302);
567
- expect(HttpStatusCode.NOT_MODIFIED).toBe(304);
568
- });
569
-
570
- it('should have 4xx client error codes', () => {
571
- expect(HttpStatusCode.BAD_REQUEST).toBe(400);
572
- expect(HttpStatusCode.UNAUTHORIZED).toBe(401);
573
- expect(HttpStatusCode.FORBIDDEN).toBe(403);
574
- expect(HttpStatusCode.NOT_FOUND).toBe(404);
575
- expect(HttpStatusCode.METHOD_NOT_ALLOWED).toBe(405);
576
- expect(HttpStatusCode.NOT_ACCEPTABLE).toBe(406);
577
- expect(HttpStatusCode.REQUEST_TIMEOUT).toBe(408);
578
- expect(HttpStatusCode.CONFLICT).toBe(409);
579
- expect(HttpStatusCode.GONE).toBe(410);
580
- expect(HttpStatusCode.UNPROCESSABLE_ENTITY).toBe(422);
581
- expect(HttpStatusCode.TOO_MANY_REQUESTS).toBe(429);
582
- });
583
-
584
- it('should have 5xx server error codes', () => {
585
- expect(HttpStatusCode.INTERNAL_SERVER_ERROR).toBe(500);
586
- expect(HttpStatusCode.NOT_IMPLEMENTED).toBe(501);
587
- expect(HttpStatusCode.BAD_GATEWAY).toBe(502);
588
- expect(HttpStatusCode.SERVICE_UNAVAILABLE).toBe(503);
589
- expect(HttpStatusCode.GATEWAY_TIMEOUT).toBe(504);
590
- });
557
+ it('should have 2xx success codes', () => {
558
+ expect(HttpStatusCode.OK).toBe(200);
559
+ expect(HttpStatusCode.CREATED).toBe(201);
560
+ expect(HttpStatusCode.ACCEPTED).toBe(202);
561
+ expect(HttpStatusCode.NO_CONTENT).toBe(204);
562
+ });
563
+
564
+ it('should have 3xx redirect codes', () => {
565
+ expect(HttpStatusCode.MOVED_PERMANENTLY).toBe(301);
566
+ expect(HttpStatusCode.FOUND).toBe(302);
567
+ expect(HttpStatusCode.NOT_MODIFIED).toBe(304);
568
+ });
569
+
570
+ it('should have 4xx client error codes', () => {
571
+ expect(HttpStatusCode.BAD_REQUEST).toBe(400);
572
+ expect(HttpStatusCode.UNAUTHORIZED).toBe(401);
573
+ expect(HttpStatusCode.FORBIDDEN).toBe(403);
574
+ expect(HttpStatusCode.NOT_FOUND).toBe(404);
575
+ expect(HttpStatusCode.METHOD_NOT_ALLOWED).toBe(405);
576
+ expect(HttpStatusCode.NOT_ACCEPTABLE).toBe(406);
577
+ expect(HttpStatusCode.REQUEST_TIMEOUT).toBe(408);
578
+ expect(HttpStatusCode.CONFLICT).toBe(409);
579
+ expect(HttpStatusCode.GONE).toBe(410);
580
+ expect(HttpStatusCode.UNPROCESSABLE_ENTITY).toBe(422);
581
+ expect(HttpStatusCode.TOO_MANY_REQUESTS).toBe(429);
582
+ });
583
+
584
+ it('should have 5xx server error codes', () => {
585
+ expect(HttpStatusCode.INTERNAL_SERVER_ERROR).toBe(500);
586
+ expect(HttpStatusCode.NOT_IMPLEMENTED).toBe(501);
587
+ expect(HttpStatusCode.BAD_GATEWAY).toBe(502);
588
+ expect(HttpStatusCode.SERVICE_UNAVAILABLE).toBe(503);
589
+ expect(HttpStatusCode.GATEWAY_TIMEOUT).toBe(504);
590
+ });
591
591
  });