piper-utils 1.1.30 → 1.1.33

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,417 +1,387 @@
1
- import {
2
- failure, success, successHtml,
3
- getCurrentUser, getCurrentUserNameFromCognitoEvent, parseBody, parseEvent, detectSequelizeError, detectJoyError
4
- } from './requestResponse.js';
5
-
6
- describe('requestResponse', () => {
7
- describe('success', () => {
8
- it('should return 200', () => {
9
- const res = success({ foo: 'bar' }, {});
10
- expect(res).toEqual({
11
- statusCode: 200, headers: {
12
- 'Access-Control-Allow-Origin': '*',
13
- 'Access-Control-Allow-Credentials': true
14
- }, body: '{"foo":"bar"}'
15
- });
16
- });
17
- });
18
- describe('success', () => {
19
- it('should call dbclose', () => {
20
- const options = {
21
- dbClose: () => {
22
-
23
- }
24
- };
25
- const closeSpy = spyOn(options, 'dbClose');
26
- const res = success({ foo: 'bar' }, options);
27
- expect(closeSpy).toHaveBeenCalled();
28
- });
29
- });
30
- describe('successHtml', () => {
31
- it('should return 200', () => {
32
- const res = successHtml('<html>test</html>', {});
33
- expect(res).toEqual({
34
- statusCode: 200, headers: {
35
- 'Content-Type': 'text/html',
36
- 'Access-Control-Allow-Origin': '*',
37
- 'Access-Control-Allow-Credentials': true
38
- }, body: '<html>test</html>'
39
- });
40
- });
41
- it('should call dbclose', () => {
42
- const options = {
43
- dbClose: () => {
44
-
45
- }
46
- };
47
- const closeSpy = spyOn(options, 'dbClose');
48
- const res = successHtml('<html>test</html>', options);
49
- expect(closeSpy).toHaveBeenCalled();
50
- });
51
- });
52
- describe('getCurrentUserNameFromCognitoEvent', () => {
53
- it('should return username', () => {
54
- const event = {
55
- request: {
56
- userAttributes: {
57
- 'cognito:email_alias': 'kevin@test.com'
58
- }
59
- }
60
- };
61
- const res = getCurrentUserNameFromCognitoEvent(event);
62
- expect(res).toEqual('kevin@test.com');
63
- });
64
- it('should return username', () => {
65
- const event = {
66
- request: {
67
- userAttributes: {}
68
- }
69
- };
70
- const res = getCurrentUserNameFromCognitoEvent(event);
71
- expect(res).toEqual('UNKNOWN USER');
72
- });
73
- });
74
- describe('getCurrentUser', () => {
75
- it('should return username', () => {
76
- const event = {
77
- requestContext: {
78
- authorizer: {
79
- 'custom:UID': '1',
80
- 'email': 'kevin@test.com'
81
- }
82
- }
83
- };
84
- const res = getCurrentUser(event);
85
- expect(res).toEqual({
86
- username: 'kevin@test.com',
87
- id: 1
88
- });
89
- });
90
- it('should return username', () => {
91
- const event = {
92
- requestContext: {
93
- authorizer: {}
94
- }
95
- };
96
- const res = getCurrentUser(event);
97
- expect(res).toEqual({
98
- username: 'localtestuser@gexample.com',
99
- id: 0
100
- });
101
- });
102
- });
103
- describe('failure', () => {
104
- it('should return an error', () => {
105
- const res = failure({ foo: 'bar' }, {});
106
- expect(res).toEqual({
107
- statusCode: 500, headers: {
108
- 'Access-Control-Allow-Origin': '*',
109
- 'Access-Control-Allow-Credentials': true
110
- }, body: '{"statusCode":500,"errorCode":"5XX","message":""}'
111
- });
112
- });
113
- it('should look for errors in details return an error', () => {
114
- const res = failure({ details: 'bar' }, {});
115
-
116
- expect(res).toEqual({
117
- statusCode: 400, headers: {
118
- 'Access-Control-Allow-Origin': '*',
119
- 'Access-Control-Allow-Credentials': true
120
- }, body: '{"statusCode":400,"errorCode":"4000","message":"INTERNAL UTIL ERROR"}'
121
- });
122
- });
123
- it('should deal with error code on body, but no details', () => {
124
- const res = failure({ errorCode: 333 }, {});
125
- expect(res).toEqual({
126
- statusCode: 500, headers: {
127
- 'Access-Control-Allow-Origin': '*',
128
- 'Access-Control-Allow-Credentials': true
129
- }, body: '{"statusCode":500,"errorCode":"5XX","message":""}'
130
- });
131
- });
132
- it('should no details and no codes', () => {
133
- const res = failure({ errorCode: 333, statusCode: 222 }, {});
134
- expect(res).toEqual({
135
- statusCode: 222, headers: {
136
- 'Access-Control-Allow-Origin': '*',
137
- 'Access-Control-Allow-Credentials': true
138
- }, body: '{"statusCode":222,"errorCode":333,"message":"INTERNAL UTIL ERROR"}'
139
- });
140
- });
141
- });
142
- describe('parseBody', () => {
143
- it('parse a json string', () => {
144
- const event = {
145
- body: '{"foo":"bar"}'
146
- };
147
-
148
- const res = parseBody(event);
149
- expect(res).toEqual({ foo: 'bar' });
150
- });
151
- it('should handle no parseable things', () => {
152
- const event = {
153
- body: 'DRY DUCK'
154
- };
155
-
156
- try {
157
- parseBody(event);
158
- } catch (e) {
159
- expect(e.message).toEqual('INVALID JSON');
160
- }
161
-
162
- });
163
- });
164
- describe('parseEvent', () => {
165
- it('parse a json string', () => {
166
- const event = '{"foo":"bar"}';
167
-
168
- const res = parseEvent(event, () => {
169
- });
170
- expect(res).toEqual({ foo: 'bar' });
171
- });
172
- it('should handle non parsable things', () => {
173
- const event = {
174
- body: 'DRY DUCK'
175
- };
176
-
177
- try {
178
- parseEvent(event, () => {
179
- });
180
- } catch (e) {
181
- expect(e.message).toEqual('INVALID JSON');
182
- }
183
-
184
- });
185
- it('should handle no parsable things', () => {
186
- const event = 'DRY DUCK';
187
-
188
- try {
189
- parseEvent(event, () => {
190
- });
191
- } catch (e) {
192
- expect(e.message).toEqual('INVALID JSON');
193
- }
194
- });
195
- it('should handle no call back', () => {
196
- const event = 'DRY DUCK';
197
-
198
- try {
199
- parseEvent(event);
200
- } catch (e) {
201
- expect(e.message).toEqual('INVALID JSON');
202
- }
203
- });
204
- it('should handle event already an object', () => {
205
- const event = { good: 'boy' };
206
-
207
- const res = parseEvent(event);
208
- expect(res).toEqual({ good: 'boy' });
209
- });
210
- it('should handle parsable stuff', () => {
211
- const event = '{"good":"boy"}';
212
-
213
- const res = parseEvent(event);
214
- expect(res).toEqual({ good: 'boy' });
215
- });
216
- it('should handle undefined', () => {
217
- const event = undefined;
218
-
219
- const res = parseEvent(event);
220
- expect(res).toEqual({});
221
- });
222
- });
223
- describe('detectSequelizeError', () => {
224
- it('should find sequelize errors', () => {
225
- const body = {
226
- errors: [{ message: 'I' }],
227
- parent: 'eggs',
228
- original: { detail: 'like' },
229
- TypeError: 'green'
230
- };
231
-
232
- const res = detectSequelizeError(body);
233
- expect(res).toEqual({ message: 'Ilikegreeneggs' });
234
- });
235
-
236
- it('should return a friendly message for foreign key constraint errors', () => {
237
- const body = {
238
- name: 'SequelizeForeignKeyConstraintError',
239
- parent: { detail: 'Key (id)=(5) is still referenced from table "orders".' },
240
- original: { detail: 'Key (id)=(5) is still referenced from table "orders".' }
241
- };
242
-
243
- const res = detectSequelizeError(body);
244
- expect(res).toEqual({
245
- message: 'You cannot delete this item as it is in use',
246
- statusCode: 409,
247
- errorCode: '4090'
248
- });
249
- });
250
-
251
- it('should return a friendly message for unique constraint errors with field name', () => {
252
- const body = {
253
- name: 'SequelizeUniqueConstraintError',
254
- errors: [{ message: 'sku must be unique', path: 'sku' }]
255
- };
256
-
257
- const res = detectSequelizeError(body);
258
- expect(res).toEqual({
259
- message: 'A record with this sku already exists',
260
- statusCode: 409,
261
- errorCode: '4091'
262
- });
263
- });
264
-
265
- it('should return a generic message for unique constraint errors with no field path', () => {
266
- const body = {
267
- name: 'SequelizeUniqueConstraintError',
268
- errors: [{ message: 'must be unique' }]
269
- };
270
-
271
- const res = detectSequelizeError(body);
272
- expect(res).toEqual({
273
- message: 'A record with this value already exists',
274
- statusCode: 409,
275
- errorCode: '4091'
276
- });
277
- });
278
-
279
- it('should return validation messages for validation errors', () => {
280
- const body = {
281
- name: 'SequelizeValidationError',
282
- errors: [
283
- { message: 'name cannot be null' },
284
- { message: 'price must be a number' }
285
- ]
286
- };
287
-
288
- const res = detectSequelizeError(body);
289
- expect(res).toEqual({
290
- message: 'name cannot be null, price must be a number',
291
- statusCode: 400,
292
- errorCode: '4001'
293
- });
294
- });
295
- });
296
- describe('detectJoyError', () => {
297
- it('should find nested Joy errors', () => {
298
- const body = {
299
- '_original': {
300
- 'encryptedCard': 'hEmm0vwnyqTukCdY/XZ06VlgE22LhlZpZDy5JZrYcP01onPlWVuCe4lmRGkZnWglklrktXSNqpcbTg4QRRTg0M0GFKUnz9NXpqEmxfNX+OPt6gHY/iDhPz74QDcjW0i6vZj1ZzY56rqhPT5bPsShXvCCbCd+3VLCfLAaBODIe2b0qYviMs7D5IFBPOW5awSJ8P4f8qmvPrflHaTWBBxy0BnqzRPcNUYEOy3menB4js+YDRNN2eaXSDnCLfrgyBGNoJH+xvM/GmnyXdb/AlrxqCr6flyP/vnzb3OidBErKBUUv0ixxK1Kp7ABmG2rgBM36NTj5gTel2Qjtf8iAAizbg==',
301
- 'encryptedCvv': 'EKCnymPpzMTUswmMsDy6lTdgwasniXMLIRmquY+VurQN6PKHg4RtEG2PCUbN2L3YLLVoSQ9v5XmPJQ4DLujDYrkoY8Qzlyhuv6qh4fQFCUeVtmxIymPTO9PjJFLlgELWp/jywqJwK6GJyeV3YatKgdwYTikcJftfIofEZ5XSKNKLkbDD4FgIdreGyARJVog0d6a/QKmcURVHkny8Qk/I91EEhy/pW5hnAM7Z8hODjdpCmF6A4+n5olDMm3//LG4WCgns/T5VcCo4yuSCtuxK9E03UcgcZhlar6RXd7sK+sw5jKU3WXisXPll2nS7Fkpky9/mZ2G3KFiQBs6B8vlk3g==',
302
- 'descriptor': 'VH Local',
303
- 'cardHolderName': 'test',
304
- 'expMonth': '12',
305
- 'expYear': '36'
306
- },
307
- 'details': [
308
- {
309
- 'message': '"value" does not match any of the allowed types',
310
- 'path': [],
311
- 'type': 'alternatives.any',
312
- 'context': {
313
- 'details': [
314
- {
315
- 'message': '"businessId" is required',
316
- 'details': [
317
- {
318
- 'message': '"businessId" is required',
319
- 'path': [
320
- 'businessId'
321
- ],
322
- 'type': 'any.required',
323
- 'context': {
324
- 'label': 'businessId',
325
- 'key': 'businessId'
326
- }
327
- }
328
- ]
329
- },
330
- {
331
- 'message': '"encryptedAccount" is required',
332
- 'details': [
333
- {
334
- 'message': '"encryptedAccount" is required',
335
- 'path': [
336
- 'encryptedAccount'
337
- ],
338
- 'type': 'any.required',
339
- 'context': {
340
- 'label': 'encryptedAccount',
341
- 'key': 'encryptedAccount'
342
- }
343
- }
344
- ]
345
- }
346
- ],
347
- 'label': 'value',
348
- 'value': {
349
- 'encryptedCard': 'hEmm0vwnyqTukCdY/XZ06VlgE22LhlZpZDy5JZrYcP01onPlWVuCe4lmRGkZnWglklrktXSNqpcbTg4QRRTg0M0GFKUnz9NXpqEmxfNX+OPt6gHY/iDhPz74QDcjW0i6vZj1ZzY56rqhPT5bPsShXvCCbCd+3VLCfLAaBODIe2b0qYviMs7D5IFBPOW5awSJ8P4f8qmvPrflHaTWBBxy0BnqzRPcNUYEOy3menB4js+YDRNN2eaXSDnCLfrgyBGNoJH+xvM/GmnyXdb/AlrxqCr6flyP/vnzb3OidBErKBUUv0ixxK1Kp7ABmG2rgBM36NTj5gTel2Qjtf8iAAizbg==',
350
- 'encryptedCvv': 'EKCnymPpzMTUswmMsDy6lTdgwasniXMLIRmquY+VurQN6PKHg4RtEG2PCUbN2L3YLLVoSQ9v5XmPJQ4DLujDYrkoY8Qzlyhuv6qh4fQFCUeVtmxIymPTO9PjJFLlgELWp/jywqJwK6GJyeV3YatKgdwYTikcJftfIofEZ5XSKNKLkbDD4FgIdreGyARJVog0d6a/QKmcURVHkny8Qk/I91EEhy/pW5hnAM7Z8hODjdpCmF6A4+n5olDMm3//LG4WCgns/T5VcCo4yuSCtuxK9E03UcgcZhlar6RXd7sK+sw5jKU3WXisXPll2nS7Fkpky9/mZ2G3KFiQBs6B8vlk3g==',
351
- 'descriptor': 'VH Local',
352
- 'cardHolderName': 'test',
353
- 'expMonth': '12',
354
- 'expYear': '36'
355
- }
356
- }
357
- }
358
- ]
359
- };
360
- const r = detectJoyError(body);
361
- expect(r).toEqual({ message: '"businessId" is required, "encryptedAccount" is required', statusCode: 400, errorCode: '4000' });
362
- });
363
- it('should extract leaf messages from deeply nested Joy errors', () => {
364
- const body = {
365
- details: [
366
- {
367
- message: '"customer.contacts[2]" does not match any of the allowed types',
368
- path: ['customer', 'contacts', 2],
369
- type: 'alternatives.any',
370
- context: {
371
- details: [
372
- {
373
- message: '"customer.contacts[2]" does not match any of the allowed types',
374
- details: [
375
- {
376
- message: '"customer.contacts[2].email" is required',
377
- path: ['customer', 'contacts', 2, 'email'],
378
- type: 'any.required',
379
- context: { label: 'customer.contacts[2].email', key: 'email' }
380
- },
381
- {
382
- message: '"customer.contacts[2].phone" is required',
383
- path: ['customer', 'contacts', 2, 'phone'],
384
- type: 'any.required',
385
- context: { label: 'customer.contacts[2].phone', key: 'phone' }
386
- }
387
- ]
388
- }
389
- ],
390
- label: 'customer.contacts[2]'
391
- }
392
- }
393
- ]
394
- };
395
- const r = detectJoyError(body);
396
- expect(r).toEqual({
397
- message: '"customer.contacts[2].email" is required, "customer.contacts[2].phone" is required',
398
- statusCode: 400,
399
- errorCode: '4000'
400
- });
401
- });
402
- it('should handle simple non-nested Joy errors', () => {
403
- const body = {
404
- details: [
405
- {
406
- message: '"name" is required',
407
- path: ['name'],
408
- type: 'any.required',
409
- context: { label: 'name', key: 'name' }
410
- }
411
- ]
412
- };
413
- const r = detectJoyError(body);
414
- expect(r).toEqual({ message: '"name" is required', statusCode: 400, errorCode: '4000' });
415
- });
416
- });
1
+ import {
2
+ failure, success, successHtml,
3
+ getCurrentUser, getCurrentUserNameFromCognitoEvent, parseBody, parseEvent, detectSequelizeError, detectJoyError
4
+ } from './requestResponse.js';
5
+
6
+ describe('requestResponse', () => {
7
+ describe('success', () => {
8
+ it('should return 200', () => {
9
+ const res = success({ foo: 'bar' }, {});
10
+ expect(res).toEqual({
11
+ statusCode: 200, headers: {
12
+ 'Access-Control-Allow-Origin': '*',
13
+ 'Access-Control-Allow-Credentials': true
14
+ }, body: '{"foo":"bar"}'
15
+ });
16
+ });
17
+ });
18
+ describe('success', () => {
19
+ it('should call dbclose', () => {
20
+ const options = {
21
+ dbClose: () => {
22
+
23
+ }
24
+ };
25
+ const closeSpy = spyOn(options, 'dbClose');
26
+ const res = success({ foo: 'bar' }, options);
27
+ expect(closeSpy).toHaveBeenCalled();
28
+ });
29
+ });
30
+ describe('successHtml', () => {
31
+ it('should return 200', () => {
32
+ const res = successHtml('<html>test</html>', {});
33
+ expect(res).toEqual({
34
+ statusCode: 200, headers: {
35
+ 'Content-Type': 'text/html',
36
+ 'Access-Control-Allow-Origin': '*',
37
+ 'Access-Control-Allow-Credentials': true
38
+ }, body: '<html>test</html>'
39
+ });
40
+ });
41
+ it('should call dbclose', () => {
42
+ const options = {
43
+ dbClose: () => {
44
+
45
+ }
46
+ };
47
+ const closeSpy = spyOn(options, 'dbClose');
48
+ const res = successHtml('<html>test</html>', options);
49
+ expect(closeSpy).toHaveBeenCalled();
50
+ });
51
+ });
52
+ describe('getCurrentUserNameFromCognitoEvent', () => {
53
+ it('should return username', () => {
54
+ const event = {
55
+ request: {
56
+ userAttributes: {
57
+ 'cognito:email_alias': 'kevin@test.com'
58
+ }
59
+ }
60
+ };
61
+ const res = getCurrentUserNameFromCognitoEvent(event);
62
+ expect(res).toEqual('kevin@test.com');
63
+ });
64
+ it('should return username', () => {
65
+ const event = {
66
+ request: {
67
+ userAttributes: {}
68
+ }
69
+ };
70
+ const res = getCurrentUserNameFromCognitoEvent(event);
71
+ expect(res).toEqual('UNKNOWN USER');
72
+ });
73
+ });
74
+ describe('getCurrentUser', () => {
75
+ it('should return username', () => {
76
+ const event = {
77
+ requestContext: {
78
+ authorizer: {
79
+ 'custom:UID': '1',
80
+ 'email': 'kevin@test.com'
81
+ }
82
+ }
83
+ };
84
+ const res = getCurrentUser(event);
85
+ expect(res).toEqual({
86
+ username: 'kevin@test.com',
87
+ id: 1
88
+ });
89
+ });
90
+ it('should return username', () => {
91
+ const event = {
92
+ requestContext: {
93
+ authorizer: {}
94
+ }
95
+ };
96
+ const res = getCurrentUser(event);
97
+ expect(res).toEqual({
98
+ username: 'localtestuser@gexample.com',
99
+ id: 0
100
+ });
101
+ });
102
+ });
103
+ describe('failure', () => {
104
+ it('should return an error', () => {
105
+ const res = failure({ foo: 'bar' }, {});
106
+ expect(res).toEqual({
107
+ statusCode: 500, headers: {
108
+ 'Access-Control-Allow-Origin': '*',
109
+ 'Access-Control-Allow-Credentials': true
110
+ }, body: '{"statusCode":500,"errorCode":"5XX","message":""}'
111
+ });
112
+ });
113
+ it('should look for errors in details return an error', () => {
114
+ const res = failure({ details: 'bar' }, {});
115
+
116
+ expect(res).toEqual({
117
+ statusCode: 400, headers: {
118
+ 'Access-Control-Allow-Origin': '*',
119
+ 'Access-Control-Allow-Credentials': true
120
+ }, body: '{"statusCode":400,"errorCode":"4000","message":"INTERNAL UTIL ERROR"}'
121
+ });
122
+ });
123
+ it('should deal with error code on body, but no details', () => {
124
+ const res = failure({ errorCode: 333 }, {});
125
+ expect(res).toEqual({
126
+ statusCode: 500, headers: {
127
+ 'Access-Control-Allow-Origin': '*',
128
+ 'Access-Control-Allow-Credentials': true
129
+ }, body: '{"statusCode":500,"errorCode":"5XX","message":""}'
130
+ });
131
+ });
132
+ it('should no details and no codes', () => {
133
+ const res = failure({ errorCode: 333, statusCode: 222 }, {});
134
+ expect(res).toEqual({
135
+ statusCode: 222, headers: {
136
+ 'Access-Control-Allow-Origin': '*',
137
+ 'Access-Control-Allow-Credentials': true
138
+ }, body: '{"statusCode":222,"errorCode":333,"message":"INTERNAL UTIL ERROR"}'
139
+ });
140
+ });
141
+ });
142
+ describe('parseBody', () => {
143
+ it('parse a json string', () => {
144
+ const event = {
145
+ body: '{"foo":"bar"}'
146
+ };
147
+
148
+ const res = parseBody(event);
149
+ expect(res).toEqual({ foo: 'bar' });
150
+ });
151
+ it('should handle no parseable things', () => {
152
+ const event = {
153
+ body: 'DRY DUCK'
154
+ };
155
+
156
+ try {
157
+ parseBody(event);
158
+ } catch (e) {
159
+ expect(e.message).toEqual('INVALID JSON');
160
+ }
161
+
162
+ });
163
+ });
164
+ describe('parseEvent', () => {
165
+ it('parse a json string', () => {
166
+ const event = '{"foo":"bar"}';
167
+
168
+ const res = parseEvent(event, () => {
169
+ });
170
+ expect(res).toEqual({ foo: 'bar' });
171
+ });
172
+ it('should handle non parsable things', () => {
173
+ const event = {
174
+ body: 'DRY DUCK'
175
+ };
176
+
177
+ try {
178
+ parseEvent(event, () => {
179
+ });
180
+ } catch (e) {
181
+ expect(e.message).toEqual('INVALID JSON');
182
+ }
183
+
184
+ });
185
+ it('should handle no parsable things', () => {
186
+ const event = 'DRY DUCK';
187
+
188
+ try {
189
+ parseEvent(event, () => {
190
+ });
191
+ } catch (e) {
192
+ expect(e.message).toEqual('INVALID JSON');
193
+ }
194
+ });
195
+ it('should handle no call back', () => {
196
+ const event = 'DRY DUCK';
197
+
198
+ try {
199
+ parseEvent(event);
200
+ } catch (e) {
201
+ expect(e.message).toEqual('INVALID JSON');
202
+ }
203
+ });
204
+ it('should handle event already an object', () => {
205
+ const event = { good: 'boy' };
206
+
207
+ const res = parseEvent(event);
208
+ expect(res).toEqual({ good: 'boy' });
209
+ });
210
+ it('should handle parsable stuff', () => {
211
+ const event = '{"good":"boy"}';
212
+
213
+ const res = parseEvent(event);
214
+ expect(res).toEqual({ good: 'boy' });
215
+ });
216
+ it('should handle undefined', () => {
217
+ const event = undefined;
218
+
219
+ const res = parseEvent(event);
220
+ expect(res).toEqual({});
221
+ });
222
+ });
223
+ describe('detectSequelizeError', () => {
224
+ it('should find sequelize errors', () => {
225
+ const body = {
226
+ errors: [{ message: 'I' }],
227
+ parent: 'eggs',
228
+ original: { detail: 'like' },
229
+ TypeError: 'green'
230
+ };
231
+
232
+ const res = detectSequelizeError(body);
233
+ expect(res).toEqual({ message: 'Ilikegreeneggs' });
234
+ });
235
+
236
+ it('should return a friendly message for foreign key constraint errors', () => {
237
+ const body = {
238
+ name: 'SequelizeForeignKeyConstraintError',
239
+ parent: { detail: 'Key (id)=(5) is still referenced from table "orders".' },
240
+ original: { detail: 'Key (id)=(5) is still referenced from table "orders".' }
241
+ };
242
+
243
+ const res = detectSequelizeError(body);
244
+ expect(res).toEqual({
245
+ message: 'You cannot delete this item as it is in use',
246
+ statusCode: 409,
247
+ errorCode: '4090'
248
+ });
249
+ });
250
+
251
+ it('should return a friendly message for unique constraint errors with field name', () => {
252
+ const body = {
253
+ name: 'SequelizeUniqueConstraintError',
254
+ errors: [{ message: 'sku must be unique', path: 'sku' }]
255
+ };
256
+
257
+ const res = detectSequelizeError(body);
258
+ expect(res).toEqual({
259
+ message: 'A record with this sku already exists',
260
+ statusCode: 409,
261
+ errorCode: '4091'
262
+ });
263
+ });
264
+
265
+ it('should return a generic message for unique constraint errors with no field path', () => {
266
+ const body = {
267
+ name: 'SequelizeUniqueConstraintError',
268
+ errors: [{ message: 'must be unique' }]
269
+ };
270
+
271
+ const res = detectSequelizeError(body);
272
+ expect(res).toEqual({
273
+ message: 'A record with this value already exists',
274
+ statusCode: 409,
275
+ errorCode: '4091'
276
+ });
277
+ });
278
+
279
+ it('should return validation messages for validation errors', () => {
280
+ const body = {
281
+ name: 'SequelizeValidationError',
282
+ errors: [
283
+ { message: 'name cannot be null' },
284
+ { message: 'price must be a number' }
285
+ ]
286
+ };
287
+
288
+ const res = detectSequelizeError(body);
289
+ expect(res).toEqual({
290
+ message: 'name cannot be null, price must be a number',
291
+ statusCode: 400,
292
+ errorCode: '4001'
293
+ });
294
+ });
295
+ });
296
+ describe('detectJoyError', () => {
297
+ it('should find nested Joy errors', () => {
298
+ const body = {
299
+ '_original': {
300
+ 'encryptedCard': 'hEmm0vwnyqTukCdY/XZ06VlgE22LhlZpZDy5JZrYcP01onPlWVuCe4lmRGkZnWglklrktXSNqpcbTg4QRRTg0M0GFKUnz9NXpqEmxfNX+OPt6gHY/iDhPz74QDcjW0i6vZj1ZzY56rqhPT5bPsShXvCCbCd+3VLCfLAaBODIe2b0qYviMs7D5IFBPOW5awSJ8P4f8qmvPrflHaTWBBxy0BnqzRPcNUYEOy3menB4js+YDRNN2eaXSDnCLfrgyBGNoJH+xvM/GmnyXdb/AlrxqCr6flyP/vnzb3OidBErKBUUv0ixxK1Kp7ABmG2rgBM36NTj5gTel2Qjtf8iAAizbg==',
301
+ 'encryptedCvv': 'EKCnymPpzMTUswmMsDy6lTdgwasniXMLIRmquY+VurQN6PKHg4RtEG2PCUbN2L3YLLVoSQ9v5XmPJQ4DLujDYrkoY8Qzlyhuv6qh4fQFCUeVtmxIymPTO9PjJFLlgELWp/jywqJwK6GJyeV3YatKgdwYTikcJftfIofEZ5XSKNKLkbDD4FgIdreGyARJVog0d6a/QKmcURVHkny8Qk/I91EEhy/pW5hnAM7Z8hODjdpCmF6A4+n5olDMm3//LG4WCgns/T5VcCo4yuSCtuxK9E03UcgcZhlar6RXd7sK+sw5jKU3WXisXPll2nS7Fkpky9/mZ2G3KFiQBs6B8vlk3g==',
302
+ 'descriptor': 'VH Local',
303
+ 'cardHolderName': 'test',
304
+ 'expMonth': '12',
305
+ 'expYear': '36'
306
+ },
307
+ 'details': [
308
+ {
309
+ 'message': '"value" does not match any of the allowed types',
310
+ 'path': [],
311
+ 'type': 'alternatives.any',
312
+ 'context': {
313
+ 'details': [
314
+ {
315
+ 'message': '"businessId" is required',
316
+ 'details': [
317
+ {
318
+ 'message': '"businessId" is required',
319
+ 'path': [
320
+ 'businessId'
321
+ ],
322
+ 'type': 'any.required',
323
+ 'context': {
324
+ 'label': 'businessId',
325
+ 'key': 'businessId'
326
+ }
327
+ }
328
+ ]
329
+ },
330
+ {
331
+ 'message': '"encryptedAccount" is required',
332
+ 'details': [
333
+ {
334
+ 'message': '"encryptedAccount" is required',
335
+ 'path': [
336
+ 'encryptedAccount'
337
+ ],
338
+ 'type': 'any.required',
339
+ 'context': {
340
+ 'label': 'encryptedAccount',
341
+ 'key': 'encryptedAccount'
342
+ }
343
+ }
344
+ ]
345
+ }
346
+ ],
347
+ 'label': 'value',
348
+ 'value': {
349
+ 'encryptedCard': 'hEmm0vwnyqTukCdY/XZ06VlgE22LhlZpZDy5JZrYcP01onPlWVuCe4lmRGkZnWglklrktXSNqpcbTg4QRRTg0M0GFKUnz9NXpqEmxfNX+OPt6gHY/iDhPz74QDcjW0i6vZj1ZzY56rqhPT5bPsShXvCCbCd+3VLCfLAaBODIe2b0qYviMs7D5IFBPOW5awSJ8P4f8qmvPrflHaTWBBxy0BnqzRPcNUYEOy3menB4js+YDRNN2eaXSDnCLfrgyBGNoJH+xvM/GmnyXdb/AlrxqCr6flyP/vnzb3OidBErKBUUv0ixxK1Kp7ABmG2rgBM36NTj5gTel2Qjtf8iAAizbg==',
350
+ 'encryptedCvv': 'EKCnymPpzMTUswmMsDy6lTdgwasniXMLIRmquY+VurQN6PKHg4RtEG2PCUbN2L3YLLVoSQ9v5XmPJQ4DLujDYrkoY8Qzlyhuv6qh4fQFCUeVtmxIymPTO9PjJFLlgELWp/jywqJwK6GJyeV3YatKgdwYTikcJftfIofEZ5XSKNKLkbDD4FgIdreGyARJVog0d6a/QKmcURVHkny8Qk/I91EEhy/pW5hnAM7Z8hODjdpCmF6A4+n5olDMm3//LG4WCgns/T5VcCo4yuSCtuxK9E03UcgcZhlar6RXd7sK+sw5jKU3WXisXPll2nS7Fkpky9/mZ2G3KFiQBs6B8vlk3g==',
351
+ 'descriptor': 'VH Local',
352
+ 'cardHolderName': 'test',
353
+ 'expMonth': '12',
354
+ 'expYear': '36'
355
+ }
356
+ }
357
+ }
358
+ ]
359
+ };
360
+ const r = detectJoyError(body);
361
+ expect(r).toEqual({ message: '"businessId" is required, "encryptedAccount" is required', statusCode: 400, errorCode: '4000' });
362
+ });
363
+ it('should include the failing value for array.includes errors', () => {
364
+ const body = {
365
+ details: [
366
+ {
367
+ message: '"customer.contacts[0]" does not match any of the allowed types',
368
+ path: ['customer', 'contacts', 0],
369
+ type: 'array.includes',
370
+ context: {
371
+ pos: 0,
372
+ value: { type: 'emaild', address: 'someemail@test.com' },
373
+ label: 'customer.contacts[0]',
374
+ key: 0
375
+ }
376
+ }
377
+ ]
378
+ };
379
+ const r = detectJoyError(body);
380
+ expect(r).toEqual({
381
+ message: '"customer.contacts[0]" does not match any of the allowed types, received: {"type":"emaild","address":"someemail@test.com"}',
382
+ statusCode: 400,
383
+ errorCode: '4000'
384
+ });
385
+ });
386
+ });
417
387
  });