@@ -433,6 +433,117 @@ public void activateWithUnknownAttribute() throws Exception {
433
433
verify (mockEventHandler ).dispatchEvent (logEventToDispatch );
434
434
}
435
435
436
+ /**
437
+ * Verify that {@link Optimizely#activate(String, String, Map)} ignores null attributes.
438
+ */
439
+ @ Test
440
+ @ SuppressFBWarnings (
441
+ value ="NP_NONNULL_PARAM_VIOLATION" ,
442
+ justification ="testing nullness contract violation" )
443
+ public void activateWithNullAttributes () throws Exception {
444
+ String datafile = noAudienceProjectConfigJsonV2 ();
445
+ ProjectConfig projectConfig = noAudienceProjectConfigV2 ();
446
+ Experiment activatedExperiment = projectConfig .getExperiments ().get (0 );
447
+ Variation bucketedVariation = activatedExperiment .getVariations ().get (0 );
448
+
449
+ // setup a mock event builder to return expected impression params
450
+ EventBuilder mockEventBuilder = mock (EventBuilder .class );
451
+
452
+ Optimizely optimizely = Optimizely .builder (datafile , mockEventHandler )
453
+ .withBucketing (mockBucketer )
454
+ .withEventBuilder (mockEventBuilder )
455
+ .withConfig (projectConfig )
456
+ .withErrorHandler (mockErrorHandler )
457
+ .build ();
458
+
459
+ Map <String , String > testParams = new HashMap <String , String >();
460
+ testParams .put ("test" , "params" );
461
+ LogEvent logEventToDispatch = new LogEvent (RequestMethod .GET , "test_url" , testParams , "" );
462
+ when (mockEventBuilder .createImpressionEvent (eq (projectConfig ), eq (activatedExperiment ), eq (bucketedVariation ),
463
+ eq ("userId" ), eq (Collections .<String , String >emptyMap ()),
464
+ isNull (String .class )))
465
+ .thenReturn (logEventToDispatch );
466
+
467
+ when (mockBucketer .bucket (activatedExperiment , "userId" ))
468
+ .thenReturn (bucketedVariation );
469
+
470
+ // activate the experiment
471
+ Map <String , String > attributes = null ;
472
+ Variation actualVariation = optimizely .activate (activatedExperiment .getKey (), "userId" , attributes );
473
+
474
+ logbackVerifier .expectMessage (Level .WARN , "Attributes is null when non-null was expected. Defaulting to an empty attributes map." );
475
+
476
+ // verify that the bucketing algorithm was called correctly
477
+ verify (mockBucketer ).bucket (activatedExperiment , "userId" );
478
+ assertThat (actualVariation , is (bucketedVariation ));
479
+
480
+ // setup the attribute map captor (so we can verify its content)
481
+ ArgumentCaptor <Map > attributeCaptor = ArgumentCaptor .forClass (Map .class );
482
+ verify (mockEventBuilder ).createImpressionEvent (eq (projectConfig ), eq (activatedExperiment ),
483
+ eq (bucketedVariation ), eq ("userId" ), attributeCaptor .capture (),
484
+ isNull (String .class ));
485
+
486
+ Map <String , String > actualValue = attributeCaptor .getValue ();
487
+ assertThat (actualValue , is (Collections .<String , String >emptyMap ()));
488
+
489
+ // verify that dispatchEvent was called with the correct LogEvent object
490
+ verify (mockEventHandler ).dispatchEvent (logEventToDispatch );
491
+ }
492
+
493
+ /**
494
+ * Verify that {@link Optimizely#activate(String, String, Map)} gracefully handles null attribute values.
495
+ */
496
+ @ Test
497
+ public void activateWithNullAttributeValues () throws Exception {
498
+ String datafile = validConfigJsonV2 ();
499
+ ProjectConfig projectConfig = validProjectConfigV2 ();
500
+ Experiment activatedExperiment = projectConfig .getExperiments ().get (0 );
501
+ Variation bucketedVariation = activatedExperiment .getVariations ().get (0 );
502
+ Attribute attribute = projectConfig .getAttributes ().get (0 );
503
+
504
+ // setup a mock event builder to return expected impression params
505
+ EventBuilder mockEventBuilder = mock (EventBuilder .class );
506
+
507
+ Optimizely optimizely = Optimizely .builder (datafile , mockEventHandler )
508
+ .withBucketing (mockBucketer )
509
+ .withEventBuilder (mockEventBuilder )
510
+ .withConfig (projectConfig )
511
+ .withErrorHandler (mockErrorHandler )
512
+ .build ();
513
+
514
+ Map <String , String > testParams = new HashMap <String , String >();
515
+ testParams .put ("test" , "params" );
516
+ LogEvent logEventToDispatch = new LogEvent (RequestMethod .GET , "test_url" , testParams , "" );
517
+ when (mockEventBuilder .createImpressionEvent (eq (projectConfig ), eq (activatedExperiment ), eq (bucketedVariation ),
518
+ eq ("userId" ), anyMapOf (String .class , String .class ),
519
+ isNull (String .class )))
520
+ .thenReturn (logEventToDispatch );
521
+
522
+ when (mockBucketer .bucket (activatedExperiment , "userId" ))
523
+ .thenReturn (bucketedVariation );
524
+
525
+ // activate the experiment
526
+ Map <String , String > attributes = new HashMap <String , String >();
527
+ attributes .put (attribute .getKey (), null );
528
+ Variation actualVariation = optimizely .activate (activatedExperiment .getKey (), "userId" , attributes );
529
+
530
+ // verify that the bucketing algorithm was called correctly
531
+ verify (mockBucketer ).bucket (activatedExperiment , "userId" );
532
+ assertThat (actualVariation , is (bucketedVariation ));
533
+
534
+ // setup the attribute map captor (so we can verify its content)
535
+ ArgumentCaptor <Map > attributeCaptor = ArgumentCaptor .forClass (Map .class );
536
+ verify (mockEventBuilder ).createImpressionEvent (eq (projectConfig ), eq (activatedExperiment ),
537
+ eq (bucketedVariation ), eq ("userId" ), attributeCaptor .capture (),
538
+ isNull (String .class ));
539
+
540
+ Map <String , String > actualValue = attributeCaptor .getValue ();
541
+ assertThat (actualValue , hasEntry (attribute .getKey (), null ));
542
+
543
+ // verify that dispatchEvent was called with the correct LogEvent object
544
+ verify (mockEventHandler ).dispatchEvent (logEventToDispatch );
545
+ }
546
+
436
547
/**
437
548
* Verify that {@link Optimizely#activate(String, String)} returns null when the experiment id corresponds to a
438
549
* non-running experiment.
@@ -883,6 +994,111 @@ public void trackEventWithAttributes() throws Exception {
883
994
verify (mockEventHandler ).dispatchEvent (logEventToDispatch );
884
995
}
885
996
997
+ /**
998
+ * Verify that {@link Optimizely#track(String, String)} ignores null attributes.
999
+ */
1000
+ @ Test
1001
+ @ SuppressFBWarnings (
1002
+ value ="NP_NONNULL_PARAM_VIOLATION" ,
1003
+ justification ="testing nullness contract violation" )
1004
+ public void trackEventWithNullAttributes () throws Exception {
1005
+ String datafile = noAudienceProjectConfigJsonV2 ();
1006
+ ProjectConfig projectConfig = noAudienceProjectConfigV2 ();
1007
+ EventType eventType = projectConfig .getEventTypes ().get (0 );
1008
+
1009
+ // setup a mock event builder to return expected conversion params
1010
+ EventBuilder mockEventBuilder = mock (EventBuilder .class );
1011
+
1012
+ Optimizely optimizely = Optimizely .builder (datafile , mockEventHandler )
1013
+ .withBucketing (mockBucketer )
1014
+ .withEventBuilder (mockEventBuilder )
1015
+ .withConfig (projectConfig )
1016
+ .withErrorHandler (mockErrorHandler )
1017
+ .build ();
1018
+
1019
+ Map <String , String > testParams = new HashMap <String , String >();
1020
+ testParams .put ("test" , "params" );
1021
+ LogEvent logEventToDispatch = new LogEvent (RequestMethod .GET , "test_url" , testParams , "" );
1022
+ when (mockEventBuilder .createConversionEvent (eq (projectConfig ), eq (mockBucketer ), eq ("userId" ),
1023
+ eq (eventType .getId ()), eq (eventType .getKey ()),
1024
+ eq (Collections .<String , String >emptyMap ()), isNull (Long .class ),
1025
+ isNull (String .class )))
1026
+ .thenReturn (logEventToDispatch );
1027
+
1028
+ logbackVerifier .expectMessage (Level .INFO , "Tracking event \" clicked_cart\" for user \" userId\" ." );
1029
+ logbackVerifier .expectMessage (Level .DEBUG , "Dispatching conversion event to URL test_url with params " +
1030
+ testParams + " and payload \" \" " );
1031
+
1032
+ // call track
1033
+ Map <String , String > attributes = null ;
1034
+ optimizely .track (eventType .getKey (), "userId" , attributes );
1035
+
1036
+ logbackVerifier .expectMessage (Level .WARN , "Attributes is null when non-null was expected. Defaulting to an empty attributes map." );
1037
+
1038
+ // setup the attribute map captor (so we can verify its content)
1039
+ ArgumentCaptor <Map > attributeCaptor = ArgumentCaptor .forClass (Map .class );
1040
+
1041
+ // verify that the event builder was called with the expected attributes
1042
+ verify (mockEventBuilder ).createConversionEvent (eq (projectConfig ), eq (mockBucketer ), eq ("userId" ),
1043
+ eq (eventType .getId ()), eq (eventType .getKey ()),
1044
+ attributeCaptor .capture (), isNull (Long .class ),
1045
+ isNull (String .class ));
1046
+
1047
+ Map <String , String > actualValue = attributeCaptor .getValue ();
1048
+ assertThat (actualValue , is (Collections .<String , String >emptyMap ()));
1049
+
1050
+ verify (mockEventHandler ).dispatchEvent (logEventToDispatch );
1051
+ }
1052
+
1053
+ /**
1054
+ * Verify that {@link Optimizely#track(String, String)} gracefully handles null attribute values.
1055
+ */
1056
+ @ Test
1057
+ public void trackEventWithNullAttributeValues () throws Exception {
1058
+ String datafile = validConfigJsonV2 ();
1059
+ ProjectConfig projectConfig = validProjectConfigV2 ();
1060
+ EventType eventType = projectConfig .getEventTypes ().get (0 );
1061
+
1062
+ // setup a mock event builder to return expected conversion params
1063
+ EventBuilder mockEventBuilder = mock (EventBuilder .class );
1064
+
1065
+ Optimizely optimizely = Optimizely .builder (datafile , mockEventHandler )
1066
+ .withBucketing (mockBucketer )
1067
+ .withEventBuilder (mockEventBuilder )
1068
+ .withConfig (projectConfig )
1069
+ .withErrorHandler (mockErrorHandler )
1070
+ .build ();
1071
+
1072
+ Map <String , String > testParams = new HashMap <String , String >();
1073
+ testParams .put ("test" , "params" );
1074
+ LogEvent logEventToDispatch = new LogEvent (RequestMethod .GET , "test_url" , testParams , "" );
1075
+ when (mockEventBuilder .createConversionEvent (eq (projectConfig ), eq (mockBucketer ), eq ("userId" ),
1076
+ eq (eventType .getId ()), eq (eventType .getKey ()),
1077
+ anyMapOf (String .class , String .class ), isNull (Long .class ),
1078
+ isNull (String .class )))
1079
+ .thenReturn (logEventToDispatch );
1080
+
1081
+ logbackVerifier .expectMessage (Level .INFO , "Tracking event \" clicked_cart\" for user \" userId\" ." );
1082
+ logbackVerifier .expectMessage (Level .DEBUG , "Dispatching conversion event to URL test_url with params " +
1083
+ testParams + " and payload \" \" " );
1084
+
1085
+ // call track
1086
+ Map <String , String > attributes = new HashMap <String , String >();
1087
+ attributes .put ("test" , null );
1088
+ optimizely .track (eventType .getKey (), "userId" , attributes );
1089
+
1090
+ // setup the attribute map captor (so we can verify its content)
1091
+ ArgumentCaptor <Map > attributeCaptor = ArgumentCaptor .forClass (Map .class );
1092
+
1093
+ // verify that the event builder was called with the expected attributes
1094
+ verify (mockEventBuilder ).createConversionEvent (eq (projectConfig ), eq (mockBucketer ), eq ("userId" ),
1095
+ eq (eventType .getId ()), eq (eventType .getKey ()),
1096
+ attributeCaptor .capture (), isNull (Long .class ),
1097
+ isNull (String .class ));
1098
+
1099
+ verify (mockEventHandler ).dispatchEvent (logEventToDispatch );
1100
+ }
1101
+
886
1102
/**
887
1103
* Verify that {@link Optimizely#track(String, String)} handles the case where an unknown attribute
888
1104
* (i.e., not in the config) is passed through.
0 commit comments