4
4
preparePayload ,
5
5
sendDataToMicrosoftBingAds ,
6
6
handleHttpError ,
7
- handleMultistatusResponse
7
+ handleMultistatusResponse ,
8
+ categorizePayloadByAction
8
9
} from '../utils'
9
10
import { BASE_URL } from '../constants'
10
11
import { MultiStatusResponse , HTTPError , RequestClient , IntegrationError } from '@segment/actions-core'
@@ -47,6 +48,56 @@ describe('preparePayload', () => {
47
48
} )
48
49
} )
49
50
51
+ describe ( 'categorizePayloadByAction' , ( ) => {
52
+ it ( 'handles duplicate identifiers correctly' , ( ) => {
53
+ const payload : Payload [ ] = [
54
+ {
55
+ audience_id : 'a1' ,
56
+ identifier_type : 'Email' ,
57
+
58
+ enable_batching : true ,
59
+ batch_size : 1000 ,
60
+ traits_or_props : { a1 : true } ,
61
+ audience_key : 'a1' ,
62
+ computation_class : 'Default'
63
+ } ,
64
+ {
65
+ audience_id : 'a1' ,
66
+ identifier_type : 'Email' ,
67
+ email :
'[email protected] ' , // Same email as first payload
68
+ enable_batching : true ,
69
+ batch_size : 1000 ,
70
+ traits_or_props : { a1 : true } ,
71
+ audience_key : 'a1' ,
72
+ computation_class : 'Default'
73
+ } ,
74
+ {
75
+ audience_id : 'a1' ,
76
+ identifier_type : 'Email' ,
77
+
78
+ enable_batching : true ,
79
+ batch_size : 1000 ,
80
+ traits_or_props : { } ,
81
+ audience_key : 'a1' ,
82
+ computation_class : 'Default'
83
+ }
84
+ ]
85
+
86
+ const addMap : Map < string , number [ ] > = new Map ( )
87
+ const removeMap : Map < string , number [ ] > = new Map ( )
88
+
89
+ categorizePayloadByAction ( payload , addMap , removeMap )
90
+
91
+ const hashedEmail = hashEmail ( '[email protected] ' )
92
+ const hashedOtherEmail = hashEmail ( '[email protected] ' )
93
+
94
+ expect ( addMap . get ( hashedEmail ) ) . toEqual ( [ 0 , 1 ] )
95
+
96
+ // Check that the remove map contains the index for the other email
97
+ expect ( removeMap . get ( hashedOtherEmail ) ) . toEqual ( [ 2 ] )
98
+ } )
99
+ } )
100
+
50
101
describe ( 'sendDataToMicrosoftBingAds' , ( ) => {
51
102
it ( 'sends data to Microsoft Bing Ads' , async ( ) => {
52
103
nock ( BASE_URL ) . post ( '/CustomerListUserData/Apply' , { foo : 'bar' } ) . reply ( 200 , { ok : true } )
@@ -64,7 +115,7 @@ describe('sendDataToMicrosoftBingAds', () => {
64
115
describe ( 'handleHttpError' , ( ) => {
65
116
it ( 'sets error responses for all items' , async ( ) => {
66
117
const msResponse = createMockMsResponse ( )
67
- const listItemsMap = new Map < string , number > ( [ [ 'abc' , 0 ] ] )
118
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'abc' , [ 0 ] ] ] )
68
119
const payload : Payload [ ] = [
69
120
{
70
121
audience_id : 'a1' ,
@@ -97,10 +148,56 @@ describe('handleHttpError', () => {
97
148
} )
98
149
99
150
describe ( 'handleMultistatusResponse' , ( ) => {
151
+ it ( 'uses default Code (400) when error.Code is missing' , ( ) => {
152
+ const msResponse = createMockMsResponse ( )
153
+ const items = [ 'item1' ]
154
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'item1' , [ 0 ] ] ] )
155
+ const payload : Payload [ ] = [
156
+ {
157
+ audience_id : 'a1' ,
158
+ identifier_type : 'Email' ,
159
+
160
+ enable_batching : true ,
161
+ batch_size : 1000 ,
162
+ traits_or_props : { } ,
163
+ audience_key : 'a1' ,
164
+ computation_class : 'Default'
165
+ }
166
+ ]
167
+
168
+ const response = {
169
+ data : {
170
+ PartialErrors : [
171
+ {
172
+ FieldPath : null ,
173
+ ErrorCode : 'InvalidInput' ,
174
+ Message : 'The input is invalid' ,
175
+ Code : undefined , // Missing Code
176
+ Details : null ,
177
+ Index : 0 ,
178
+ Type : 'Error' ,
179
+ ForwardCompatibilityMap : null
180
+ }
181
+ ]
182
+ }
183
+ }
184
+
185
+ handleMultistatusResponse ( msResponse , response as unknown as ModifiedResponse , items , listItemsMap , payload , true )
186
+
187
+ // Should use default 400 status code
188
+ expect ( msResponse . setErrorResponseAtIndex ) . toHaveBeenCalledWith (
189
+ 0 ,
190
+ expect . objectContaining ( {
191
+ status : 400 , // Default value
192
+ errormessage : 'InvalidInput: The input is invalid'
193
+ } )
194
+ )
195
+ } )
196
+
100
197
it ( 'throws IntegrationError when not in batch mode and there are partial errors' , ( ) => {
101
198
const msResponse = createMockMsResponse ( )
102
199
const items = [ 'item1' ]
103
- const listItemsMap = new Map < string , number > ( [ [ 'item1' , 0 ] ] )
200
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'item1' , [ 0 ] ] ] )
104
201
const payload : Payload [ ] = [
105
202
{
106
203
audience_id : 'a1' ,
@@ -146,10 +243,10 @@ describe('handleMultistatusResponse', () => {
146
243
it ( 'processes partial errors in batch mode' , ( ) => {
147
244
const msResponse = createMockMsResponse ( )
148
245
const items = [ 'item1' , 'item2' , 'item3' ]
149
- const listItemsMap = new Map < string , number > ( [
150
- [ 'item1' , 0 ] ,
151
- [ 'item2' , 1 ] ,
152
- [ 'item3' , 2 ]
246
+ const listItemsMap = new Map < string , number [ ] > ( [
247
+ [ 'item1' , [ 0 ] ] ,
248
+ [ 'item2' , [ 1 ] ] ,
249
+ [ 'item3' , [ 2 ] ]
153
250
] )
154
251
const payload : Payload [ ] = [
155
252
{
@@ -230,9 +327,9 @@ describe('handleMultistatusResponse', () => {
230
327
it ( 'marks all items as successful when there are no partial errors' , ( ) => {
231
328
const msResponse = createMockMsResponse ( )
232
329
const items = [ 'item1' , 'item2' ]
233
- const listItemsMap = new Map < string , number > ( [
234
- [ 'item1' , 0 ] ,
235
- [ 'item2' , 1 ]
330
+ const listItemsMap = new Map < string , number [ ] > ( [
331
+ [ 'item1' , [ 0 ] ] ,
332
+ [ 'item2' , [ 1 ] ]
236
333
] )
237
334
const payload : Payload [ ] = [
238
335
{
@@ -279,7 +376,7 @@ describe('handleMultistatusResponse', () => {
279
376
it ( 'handles partial errors with invalid indices' , ( ) => {
280
377
const msResponse = createMockMsResponse ( )
281
378
const items = [ 'item1' ]
282
- const listItemsMap = new Map < string , number > ( [ [ 'item1' , 0 ] ] )
379
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'item1' , [ 0 ] ] ] )
283
380
const payload : Payload [ ] = [
284
381
{
285
382
audience_id : 'a1' ,
@@ -320,4 +417,205 @@ describe('handleMultistatusResponse', () => {
320
417
} )
321
418
)
322
419
} )
420
+
421
+ it ( 'uses default Message ("No error message provided") when Message is missing in partial errors' , ( ) => {
422
+ const msResponse = createMockMsResponse ( )
423
+ const items = [ 'item1' ]
424
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'item1' , [ 0 ] ] ] )
425
+ const payload : Payload [ ] = [
426
+ {
427
+ audience_id : 'a1' ,
428
+ identifier_type : 'Email' ,
429
+
430
+ enable_batching : true ,
431
+ batch_size : 1000 ,
432
+ traits_or_props : { } ,
433
+ audience_key : 'a1' ,
434
+ computation_class : 'Default'
435
+ }
436
+ ]
437
+
438
+ const response = {
439
+ data : {
440
+ PartialErrors : [
441
+ {
442
+ FieldPath : null ,
443
+ ErrorCode : 'InvalidInput' ,
444
+ // Message is missing
445
+ Code : 403 ,
446
+ Details : null ,
447
+ Index : 0 ,
448
+ Type : 'Error' ,
449
+ ForwardCompatibilityMap : null
450
+ }
451
+ ]
452
+ }
453
+ }
454
+
455
+ handleMultistatusResponse ( msResponse , response as unknown as ModifiedResponse , items , listItemsMap , payload , true )
456
+
457
+ // Check that the default Message "No error message provided" is used
458
+ expect ( msResponse . setErrorResponseAtIndex ) . toHaveBeenCalledWith (
459
+ 0 ,
460
+ expect . objectContaining ( {
461
+ status : 403 ,
462
+ errormessage : 'InvalidInput: No error message provided'
463
+ } )
464
+ )
465
+ } )
466
+
467
+ it ( 'uses default ErrorCode ("UnknownError") when ErrorCode is missing in partial errors' , ( ) => {
468
+ const msResponse = createMockMsResponse ( )
469
+ const items = [ 'item1' ]
470
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'item1' , [ 0 ] ] ] )
471
+ const payload : Payload [ ] = [
472
+ {
473
+ audience_id : 'a1' ,
474
+ identifier_type : 'Email' ,
475
+
476
+ enable_batching : true ,
477
+ batch_size : 1000 ,
478
+ traits_or_props : { } ,
479
+ audience_key : 'a1' ,
480
+ computation_class : 'Default'
481
+ }
482
+ ]
483
+
484
+ const response = {
485
+ data : {
486
+ PartialErrors : [
487
+ {
488
+ FieldPath : null ,
489
+ // ErrorCode is missing
490
+ Message : 'The input is invalid' ,
491
+ Code : 403 ,
492
+ Details : null ,
493
+ Index : 0 ,
494
+ Type : 'Error' ,
495
+ ForwardCompatibilityMap : null
496
+ }
497
+ ]
498
+ }
499
+ }
500
+
501
+ handleMultistatusResponse ( msResponse , response as unknown as ModifiedResponse , items , listItemsMap , payload , true )
502
+
503
+ // Check that the default ErrorCode "UnknownError" is used
504
+ expect ( msResponse . setErrorResponseAtIndex ) . toHaveBeenCalledWith (
505
+ 0 ,
506
+ expect . objectContaining ( {
507
+ status : 403 ,
508
+ errormessage : 'UnknownError: The input is invalid'
509
+ } )
510
+ )
511
+ } )
512
+
513
+ it ( 'uses default status code (400) when Code is missing in partial errors' , ( ) => {
514
+ const msResponse = createMockMsResponse ( )
515
+ const items = [ 'item1' ]
516
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'item1' , [ 0 ] ] ] )
517
+ const payload : Payload [ ] = [
518
+ {
519
+ audience_id : 'a1' ,
520
+ identifier_type : 'Email' ,
521
+
522
+ enable_batching : true ,
523
+ batch_size : 1000 ,
524
+ traits_or_props : { } ,
525
+ audience_key : 'a1' ,
526
+ computation_class : 'Default'
527
+ }
528
+ ]
529
+
530
+ const response = {
531
+ data : {
532
+ PartialErrors : [
533
+ {
534
+ FieldPath : null ,
535
+ ErrorCode : 'InvalidInput' ,
536
+ Message : 'The input is invalid' ,
537
+ // Code is missing
538
+ Details : null ,
539
+ Index : 0 ,
540
+ Type : 'Error' ,
541
+ ForwardCompatibilityMap : null
542
+ }
543
+ ]
544
+ }
545
+ }
546
+
547
+ handleMultistatusResponse ( msResponse , response as unknown as ModifiedResponse , items , listItemsMap , payload , true )
548
+
549
+ // Check that the default status code 400 is used
550
+ expect ( msResponse . setErrorResponseAtIndex ) . toHaveBeenCalledWith (
551
+ 0 ,
552
+ expect . objectContaining ( {
553
+ status : 400 , // Default value
554
+ errormessage : 'InvalidInput: The input is invalid'
555
+ } )
556
+ )
557
+ } )
558
+
559
+ it ( 'handles duplicate identifiers correctly in partial errors' , ( ) => {
560
+ const msResponse = createMockMsResponse ( )
561
+ const items = [ 'dupItem' ]
562
+ const listItemsMap = new Map < string , number [ ] > ( [ [ 'dupItem' , [ 0 , 1 ] ] ] ) // dupItem maps to two payload indices
563
+ const payload : Payload [ ] = [
564
+ {
565
+ audience_id : 'a1' ,
566
+ identifier_type : 'Email' ,
567
+
568
+ enable_batching : true ,
569
+ batch_size : 1000 ,
570
+ traits_or_props : { } ,
571
+ audience_key : 'a1' ,
572
+ computation_class : 'Default'
573
+ } ,
574
+ {
575
+ audience_id : 'a1' ,
576
+ identifier_type : 'Email' ,
577
+
578
+ enable_batching : true ,
579
+ batch_size : 1000 ,
580
+ traits_or_props : { } ,
581
+ audience_key : 'a1' ,
582
+ computation_class : 'Default'
583
+ }
584
+ ]
585
+
586
+ const response = {
587
+ data : {
588
+ PartialErrors : [
589
+ {
590
+ FieldPath : null ,
591
+ ErrorCode : 'InvalidInput' ,
592
+ Message : 'The input is invalid' ,
593
+ Code : 400 ,
594
+ Details : null ,
595
+ Index : 0 ,
596
+ Type : 'Error' ,
597
+ ForwardCompatibilityMap : null
598
+ }
599
+ ]
600
+ }
601
+ }
602
+
603
+ handleMultistatusResponse ( msResponse , response as any as ModifiedResponse , items , listItemsMap , payload , true )
604
+
605
+ // Check that the error item was handled correctly
606
+ expect ( msResponse . setErrorResponseAtIndex ) . toHaveBeenCalledWith (
607
+ 0 ,
608
+ expect . objectContaining ( {
609
+ status : 400 ,
610
+ errormessage : 'InvalidInput: The input is invalid'
611
+ } )
612
+ )
613
+ expect ( msResponse . setErrorResponseAtIndex ) . toHaveBeenCalledWith (
614
+ 1 ,
615
+ expect . objectContaining ( {
616
+ status : 400 ,
617
+ errormessage : 'InvalidInput: The input is invalid'
618
+ } )
619
+ )
620
+ } )
323
621
} )
0 commit comments