diff --git a/.travis.yml b/.travis.yml index 5be8e640d..adb85e7dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ jdk: install: true script: - "./gradlew clean" + - "./gradlew exhaustiveTest" - "if [[ -n $TRAVIS_TAG ]]; then ./gradlew ship; else diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1d990de..b577c49a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 1.6.0 + +March 17, 2017 + +- Add event tags to `track` API and include in the event payload +- Deprecates the `eventValue` parameter from the `track` method. Should use event tags to pass in event value instead +- Gracefully handle a null attributes parameter +- Gracefully handle a null/empty datafile when using the Gson parser + ## 1.5.0 February 16, 2017 diff --git a/core-api/src/jmh/java/com/optimizely/ab/BenchmarkUtils.java b/core-api/src/jmh/java/com/optimizely/ab/BenchmarkUtils.java index a4b1b1ad3..8028a6830 100644 --- a/core-api/src/jmh/java/com/optimizely/ab/BenchmarkUtils.java +++ b/core-api/src/jmh/java/com/optimizely/ab/BenchmarkUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBenchmark.java b/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBenchmark.java index 631cf62f4..1e902f473 100644 --- a/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBenchmark.java +++ b/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBenchmark.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBuilderBenchmark.java b/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBuilderBenchmark.java index 5d9302dbc..596597f1b 100644 --- a/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBuilderBenchmark.java +++ b/core-api/src/jmh/java/com/optimizely/ab/OptimizelyBuilderBenchmark.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/Optimizely.java b/core-api/src/main/java/com/optimizely/ab/Optimizely.java index 334564508..a3d30261b 100644 --- a/core-api/src/main/java/com/optimizely/ab/Optimizely.java +++ b/core-api/src/main/java/com/optimizely/ab/Optimizely.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,9 @@ import com.optimizely.ab.event.internal.EventBuilderV1; import com.optimizely.ab.event.internal.EventBuilderV2; import com.optimizely.ab.event.internal.payload.Event.ClientEngine; +import com.optimizely.ab.internal.EventTagUtils; import com.optimizely.ab.internal.ProjectValidationUtils; +import com.optimizely.ab.internal.ReservedEventKey; import com.optimizely.ab.notification.NotificationListener; import com.optimizely.ab.notification.NotificationBroadcaster; @@ -117,22 +119,9 @@ private Optimizely(@Nonnull ProjectConfig projectConfig, return activate(experimentKey, userId, Collections.emptyMap()); } - public @Nullable Variation activate(@Nonnull String experimentKey, - @Nonnull String userId, - @CheckForNull String sessionId) throws UnknownExperimentException { - return activate(experimentKey, userId, Collections.emptyMap(), sessionId); - } - public @Nullable Variation activate(@Nonnull String experimentKey, @Nonnull String userId, @Nonnull Map attributes) throws UnknownExperimentException { - return activate(experimentKey, userId, attributes, null); - } - - public @Nullable Variation activate(@Nonnull String experimentKey, - @Nonnull String userId, - @Nonnull Map attributes, - @CheckForNull String sessionId) throws UnknownExperimentException { if (!validateUserId(userId)) { logger.info("Not activating user for experiment \"{}\".", experimentKey); @@ -148,7 +137,7 @@ private Optimizely(@Nonnull ProjectConfig projectConfig, return null; } - return activate(currentConfig, experiment, userId, attributes, sessionId); + return activate(currentConfig, experiment, userId, attributes); } public @Nullable Variation activate(@Nonnull Experiment experiment, @@ -156,34 +145,19 @@ private Optimizely(@Nonnull ProjectConfig projectConfig, return activate(experiment, userId, Collections.emptyMap()); } - public @Nullable Variation activate(@Nonnull Experiment experiment, - @Nonnull String userId, - @CheckForNull String sessionId) { - return activate(experiment, userId, Collections.emptyMap(), sessionId); - } - public @Nullable Variation activate(@Nonnull Experiment experiment, @Nonnull String userId, @Nonnull Map attributes) { - return activate(experiment, userId, attributes, null); - } - - public @Nullable Variation activate(@Nonnull Experiment experiment, - @Nonnull String userId, - @Nonnull Map attributes, - @CheckForNull String sessionId) { - ProjectConfig currentConfig = getProjectConfig(); - return activate(currentConfig, experiment, userId, attributes, sessionId); + return activate(currentConfig, experiment, userId, attributes); } private @Nullable Variation activate(@Nonnull ProjectConfig projectConfig, @Nonnull Experiment experiment, @Nonnull String userId, - @Nonnull Map attributes, - @CheckForNull String sessionId) { + @Nonnull Map attributes) { // determine whether all the given attributes are present in the project config. If not, filter out the unknown // attributes. attributes = filterAttributes(projectConfig, attributes); @@ -202,7 +176,7 @@ private Optimizely(@Nonnull ProjectConfig projectConfig, if (experiment.isRunning()) { LogEvent impressionEvent = eventBuilder.createImpressionEvent(projectConfig, experiment, variation, userId, - attributes, sessionId); + attributes); logger.info("Activating user \"{}\" in experiment \"{}\".", userId, experiment.getKey()); logger.debug( "Dispatching impression event to URL {} with params {} and payload \"{}\".", @@ -225,61 +199,39 @@ private Optimizely(@Nonnull ProjectConfig projectConfig, public void track(@Nonnull String eventName, @Nonnull String userId) throws UnknownEventTypeException { - track(eventName, userId, Collections.emptyMap(), null, null); - } - - public void track(@Nonnull String eventName, - @Nonnull String userId, - @CheckForNull String sessionId) throws UnknownEventTypeException { - track(eventName, userId, Collections.emptyMap(), null, sessionId); + track(eventName, userId, Collections.emptyMap(), Collections.emptyMap()); } public void track(@Nonnull String eventName, @Nonnull String userId, @Nonnull Map attributes) throws UnknownEventTypeException { - track(eventName, userId, attributes, null, null); - } - - public void track(@Nonnull String eventName, - @Nonnull String userId, - @Nonnull Map attributes, - @CheckForNull String sessionId) throws UnknownEventTypeException { - track(eventName, userId, attributes, null, sessionId); + track(eventName, userId, attributes, Collections.emptyMap()); } + /** + * @deprecated see {@link #track(String, String, Map)} and pass in the revenue value as an event tag instead. + */ public void track(@Nonnull String eventName, @Nonnull String userId, long eventValue) throws UnknownEventTypeException { - track(eventName, userId, Collections.emptyMap(), eventValue); - } - - public void track(@Nonnull String eventName, - @Nonnull String userId, - long eventValue, - @CheckForNull String sessionId) throws UnknownEventTypeException { - track(eventName, userId, Collections.emptyMap(), eventValue, sessionId); + track(eventName, userId, Collections.emptyMap(), Collections.singletonMap( + ReservedEventKey.REVENUE.toString(), eventValue)); } + /** + * @deprecated see {@link #track(String, String, Map, long)} and pass in the revenue value as an event tag instead. + */ public void track(@Nonnull String eventName, @Nonnull String userId, @Nonnull Map attributes, long eventValue) throws UnknownEventTypeException { - track(eventName, userId, attributes, (Long)eventValue, null); + track(eventName, userId, attributes, Collections.singletonMap(ReservedEventKey.REVENUE.toString(), eventValue)); } public void track(@Nonnull String eventName, - @Nonnull String userId, - @Nonnull Map attributes, - long eventValue, - @CheckForNull String sessionId) throws UnknownEventTypeException { - track(eventName, userId, attributes, (Long)eventValue, sessionId); - } - - private void track(@Nonnull String eventName, @Nonnull String userId, @Nonnull Map attributes, - @CheckForNull Long eventValue, - @CheckForNull String sessionId) throws UnknownEventTypeException { + @Nonnull Map eventTags) throws UnknownEventTypeException { ProjectConfig currentConfig = getProjectConfig(); @@ -294,10 +246,18 @@ private void track(@Nonnull String eventName, // attributes. attributes = filterAttributes(currentConfig, attributes); + Long eventValue = null; + if (eventTags == null) { + logger.warn("Event tags is null when non-null was expected. Defaulting to an empty event tags map."); + eventTags = Collections.emptyMap(); + } else { + eventValue = EventTagUtils.getRevenueValue(eventTags); + } + // create the conversion event request parameters, then dispatch LogEvent conversionEvent = eventBuilder.createConversionEvent(currentConfig, bucketer, userId, eventType.getId(), eventType.getKey(), attributes, - eventValue, sessionId); + eventTags); if (conversionEvent == null) { logger.info("There are no valid experiments for event \"{}\" to track.", eventName); @@ -498,6 +458,13 @@ private void track(@Nonnull String eventName, * @return a {@link ProjectConfig} instance given a json string */ private static ProjectConfig getProjectConfig(String datafile) throws ConfigParseException { + if (datafile == null) { + throw new ConfigParseException("Unable to parse null datafile."); + } + if (datafile.length() == 0) { + throw new ConfigParseException("Unable to parse empty datafile."); + } + return DefaultConfigParser.getInstance().parseProjectConfig(datafile); } @@ -624,10 +591,16 @@ private LiveVariable getLiveVariableOrThrow(ProjectConfig projectConfig, String * * @param projectConfig the current project config * @param attributes the attributes map to validate and potentially filter - * @return the filtered attributes map (containing only attributes that are present in the project config) - * + * @return the filtered attributes map (containing only attributes that are present in the project config) or an + * empty map if a null attributes object is passed in */ - private Map filterAttributes(ProjectConfig projectConfig, Map attributes) { + private Map filterAttributes(@Nonnull ProjectConfig projectConfig, + @Nonnull Map attributes) { + if (attributes == null) { + logger.warn("Attributes is null when non-null was expected. Defaulting to an empty attributes map."); + return Collections.emptyMap(); + } + List unknownAttributes = null; Map attributeKeyMapping = projectConfig.getAttributeKeyMapping(); diff --git a/core-api/src/main/java/com/optimizely/ab/OptimizelyRuntimeException.java b/core-api/src/main/java/com/optimizely/ab/OptimizelyRuntimeException.java index 1ab3780ef..f61ac0fae 100644 --- a/core-api/src/main/java/com/optimizely/ab/OptimizelyRuntimeException.java +++ b/core-api/src/main/java/com/optimizely/ab/OptimizelyRuntimeException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/UnknownEventTypeException.java b/core-api/src/main/java/com/optimizely/ab/UnknownEventTypeException.java index 75b0c1f16..9375b35e2 100644 --- a/core-api/src/main/java/com/optimizely/ab/UnknownEventTypeException.java +++ b/core-api/src/main/java/com/optimizely/ab/UnknownEventTypeException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/UnknownExperimentException.java b/core-api/src/main/java/com/optimizely/ab/UnknownExperimentException.java index 8b5399781..01a9aa774 100644 --- a/core-api/src/main/java/com/optimizely/ab/UnknownExperimentException.java +++ b/core-api/src/main/java/com/optimizely/ab/UnknownExperimentException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/UnknownLiveVariableException.java b/core-api/src/main/java/com/optimizely/ab/UnknownLiveVariableException.java index ac14587c2..bd8e0989b 100644 --- a/core-api/src/main/java/com/optimizely/ab/UnknownLiveVariableException.java +++ b/core-api/src/main/java/com/optimizely/ab/UnknownLiveVariableException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/annotations/VisibleForTesting.java b/core-api/src/main/java/com/optimizely/ab/annotations/VisibleForTesting.java index f190684df..4dc0bae67 100644 --- a/core-api/src/main/java/com/optimizely/ab/annotations/VisibleForTesting.java +++ b/core-api/src/main/java/com/optimizely/ab/annotations/VisibleForTesting.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/bucketing/Bucketer.java b/core-api/src/main/java/com/optimizely/ab/bucketing/Bucketer.java index b7199a113..c1f1c7cef 100644 --- a/core-api/src/main/java/com/optimizely/ab/bucketing/Bucketer.java +++ b/core-api/src/main/java/com/optimizely/ab/bucketing/Bucketer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/bucketing/UserProfile.java b/core-api/src/main/java/com/optimizely/ab/bucketing/UserProfile.java index c5405d9ae..8238bf1c2 100644 --- a/core-api/src/main/java/com/optimizely/ab/bucketing/UserProfile.java +++ b/core-api/src/main/java/com/optimizely/ab/bucketing/UserProfile.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/bucketing/internal/MurmurHash3.java b/core-api/src/main/java/com/optimizely/ab/bucketing/internal/MurmurHash3.java index e4f09747d..93e706f3f 100644 --- a/core-api/src/main/java/com/optimizely/ab/bucketing/internal/MurmurHash3.java +++ b/core-api/src/main/java/com/optimizely/ab/bucketing/internal/MurmurHash3.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/Attribute.java b/core-api/src/main/java/com/optimizely/ab/config/Attribute.java index 6d2702982..5bb6698bc 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/Attribute.java +++ b/core-api/src/main/java/com/optimizely/ab/config/Attribute.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/EventType.java b/core-api/src/main/java/com/optimizely/ab/config/EventType.java index b0c0bb455..961d15392 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/EventType.java +++ b/core-api/src/main/java/com/optimizely/ab/config/EventType.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/Experiment.java b/core-api/src/main/java/com/optimizely/ab/config/Experiment.java index b8bfc99e9..f82e78903 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/Experiment.java +++ b/core-api/src/main/java/com/optimizely/ab/config/Experiment.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/Group.java b/core-api/src/main/java/com/optimizely/ab/config/Group.java index 0dc01820d..cd41bc120 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/Group.java +++ b/core-api/src/main/java/com/optimizely/ab/config/Group.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/IdKeyMapped.java b/core-api/src/main/java/com/optimizely/ab/config/IdKeyMapped.java index c9331c83f..3e7578d77 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/IdKeyMapped.java +++ b/core-api/src/main/java/com/optimizely/ab/config/IdKeyMapped.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/IdMapped.java b/core-api/src/main/java/com/optimizely/ab/config/IdMapped.java index 88828bb6a..0a032c7c8 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/IdMapped.java +++ b/core-api/src/main/java/com/optimizely/ab/config/IdMapped.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/LiveVariable.java b/core-api/src/main/java/com/optimizely/ab/config/LiveVariable.java index 0937e1ecd..4f6049282 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/LiveVariable.java +++ b/core-api/src/main/java/com/optimizely/ab/config/LiveVariable.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/LiveVariableUsageInstance.java b/core-api/src/main/java/com/optimizely/ab/config/LiveVariableUsageInstance.java index be54a05e0..05378b808 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/LiveVariableUsageInstance.java +++ b/core-api/src/main/java/com/optimizely/ab/config/LiveVariableUsageInstance.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java b/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java index 254b70963..9f7b6b292 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java +++ b/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/ProjectConfigUtils.java b/core-api/src/main/java/com/optimizely/ab/config/ProjectConfigUtils.java index 7dde59fca..84cf6cab1 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/ProjectConfigUtils.java +++ b/core-api/src/main/java/com/optimizely/ab/config/ProjectConfigUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/TrafficAllocation.java b/core-api/src/main/java/com/optimizely/ab/config/TrafficAllocation.java index 2a6b4c9e7..a66cfb81a 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/TrafficAllocation.java +++ b/core-api/src/main/java/com/optimizely/ab/config/TrafficAllocation.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/Variation.java b/core-api/src/main/java/com/optimizely/ab/config/Variation.java index 9a31a8fb8..0991a0a5e 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/Variation.java +++ b/core-api/src/main/java/com/optimizely/ab/config/Variation.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java index 49754b2d8..b734f8a9c 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/AndCondition.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java b/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java index 8f4578b5e..dfb6c26a5 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/Audience.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java index c77080231..3a47453e4 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/Condition.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java index c342776d9..7e269c6f3 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/NotCondition.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java b/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java index 9710962d2..c7a85556e 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/OrCondition.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java b/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java index 2568a8609..34e49b0bb 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java +++ b/core-api/src/main/java/com/optimizely/ab/config/audience/UserAttribute.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceGsonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceGsonDeserializer.java index 0066d7496..8158aeb4b 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceGsonDeserializer.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceGsonDeserializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceJacksonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceJacksonDeserializer.java index 6fde7aafc..1d0efc3e7 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceJacksonDeserializer.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/AudienceJacksonDeserializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParseException.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParseException.java index 58ff40350..a85ca5d15 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParseException.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParseException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java index 466821273..eb24b68f3 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/DefaultConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/DefaultConfigParser.java index 9530f2c7b..2d19dfb28 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/DefaultConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/DefaultConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ExperimentGsonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ExperimentGsonDeserializer.java index 695a63b5b..6eea6be21 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/ExperimentGsonDeserializer.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ExperimentGsonDeserializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/GroupGsonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/GroupGsonDeserializer.java index c153c34c8..05959a464 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/GroupGsonDeserializer.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/GroupGsonDeserializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/GroupJacksonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/GroupJacksonDeserializer.java index b7957de8d..714326fcc 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/GroupJacksonDeserializer.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/GroupJacksonDeserializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java index 6a7f42a05..b87c0a16a 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,12 @@ final class GsonConfigParser implements ConfigParser { @Override public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException { + if (json == null) { + throw new ConfigParseException("Unable to parse null json."); + } + if (json.length() == 0) { + throw new ConfigParseException("Unable to parse empty json."); + } Gson gson = new GsonBuilder() .registerTypeAdapter(ProjectConfig.class, new ProjectConfigGsonDeserializer()) .registerTypeAdapter(Audience.class, new AudienceGsonDeserializer()) diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java index 0031a4098..7ebdb02d2 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonHelpers.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java index 2e90c49ed..67ab86771 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java index a614ae6ef..56dc808d2 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java index d0bbd435a..5ac5552ee 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/MissingJsonParserException.java b/core-api/src/main/java/com/optimizely/ab/config/parser/MissingJsonParserException.java index 41c173d6c..a07539713 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/MissingJsonParserException.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/MissingJsonParserException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java index 6b43fc281..7403f0031 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java index 7ae04077b..38c844457 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/error/ErrorHandler.java b/core-api/src/main/java/com/optimizely/ab/error/ErrorHandler.java index a5e74fa37..f38ee8db9 100644 --- a/core-api/src/main/java/com/optimizely/ab/error/ErrorHandler.java +++ b/core-api/src/main/java/com/optimizely/ab/error/ErrorHandler.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/error/NoOpErrorHandler.java b/core-api/src/main/java/com/optimizely/ab/error/NoOpErrorHandler.java index fc3318941..ef763a21f 100644 --- a/core-api/src/main/java/com/optimizely/ab/error/NoOpErrorHandler.java +++ b/core-api/src/main/java/com/optimizely/ab/error/NoOpErrorHandler.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/error/RaiseExceptionErrorHandler.java b/core-api/src/main/java/com/optimizely/ab/error/RaiseExceptionErrorHandler.java index 87ace738d..9aa6f17c1 100644 --- a/core-api/src/main/java/com/optimizely/ab/error/RaiseExceptionErrorHandler.java +++ b/core-api/src/main/java/com/optimizely/ab/error/RaiseExceptionErrorHandler.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/EventHandler.java b/core-api/src/main/java/com/optimizely/ab/event/EventHandler.java index 97f7ebfcc..7df38fabb 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/EventHandler.java +++ b/core-api/src/main/java/com/optimizely/ab/event/EventHandler.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/LogEvent.java b/core-api/src/main/java/com/optimizely/ab/event/LogEvent.java index f8e72fc0a..a353d9877 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/LogEvent.java +++ b/core-api/src/main/java/com/optimizely/ab/event/LogEvent.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/NoopEventHandler.java b/core-api/src/main/java/com/optimizely/ab/event/NoopEventHandler.java index 723c898a8..e84ff4c58 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/NoopEventHandler.java +++ b/core-api/src/main/java/com/optimizely/ab/event/NoopEventHandler.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/BuildVersionInfo.java b/core-api/src/main/java/com/optimizely/ab/event/internal/BuildVersionInfo.java index 9d4778797..c86568fe8 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/BuildVersionInfo.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/BuildVersionInfo.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java index 33bf72272..b2c4a39ef 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilder.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,24 +25,16 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import java.util.Collections; import java.util.Map; public abstract class EventBuilder { - public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, - @Nonnull Experiment activatedExperiment, - @Nonnull Variation variation, - @Nonnull String userId, - @Nonnull Map attributes) { - return createImpressionEvent(projectConfig, activatedExperiment, variation, userId, attributes, null); - } - public abstract LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull Experiment activatedExperiment, @Nonnull Variation variation, @Nonnull String userId, - @Nonnull Map attributes, - @CheckForNull String sessionId); + @Nonnull Map attributes); public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull Bucketer bucketer, @@ -50,18 +42,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull String eventId, @Nonnull String eventName, @Nonnull Map attributes) { - return createConversionEvent(projectConfig, bucketer, userId, eventId, eventName, attributes, null, null); - } - - public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, - @Nonnull Bucketer bucketer, - @Nonnull String userId, - @Nonnull String eventId, - @Nonnull String eventName, - @Nonnull Map attributes, - long eventValue) { - return createConversionEvent(projectConfig, bucketer, userId, eventId, eventName, attributes, (Long)eventValue, - null); + return createConversionEvent(projectConfig, bucketer, userId, eventId, eventName, attributes, Collections.emptyMap()); } public abstract LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, @@ -70,6 +51,5 @@ public abstract LogEvent createConversionEvent(@Nonnull ProjectConfig projectCon @Nonnull String eventId, @Nonnull String eventName, @Nonnull Map attributes, - @CheckForNull Long eventValue, - @CheckForNull String sessionId); + @Nonnull Map eventTags); } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV1.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV1.java index a1a4860b7..3aead7152 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV1.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV1.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.optimizely.ab.event.internal; import com.optimizely.ab.event.LogEvent; +import com.optimizely.ab.internal.EventTagUtils; import com.optimizely.ab.internal.ProjectValidationUtils; import com.optimizely.ab.bucketing.Bucketer; import com.optimizely.ab.config.Attribute; @@ -67,8 +68,7 @@ public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull Experiment activatedExperiment, @Nonnull Variation variation, @Nonnull String userId, - @Nonnull Map attributes, - @CheckForNull String sessionId) { + @Nonnull Map attributes) { Map requestParams = new HashMap(); addCommonRequestParams(requestParams, projectConfig, userId, attributes); @@ -85,8 +85,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull String eventId, @Nonnull String eventName, @Nonnull Map attributes, - @CheckForNull Long eventValue, - @CheckForNull String sessionId) { + @Nonnull Map eventTags) { Map requestParams = new HashMap(); List addedExperiments = @@ -96,6 +95,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, return null; } + Long eventValue = EventTagUtils.getRevenueValue(eventTags); addCommonRequestParams(requestParams, projectConfig, userId, attributes); addConversionGoal(requestParams, projectConfig, eventId, eventName, eventValue); diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java index 295dcf565..eb835d543 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/EventBuilderV2.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import com.optimizely.ab.event.internal.payload.LayerState; import com.optimizely.ab.event.internal.serializer.DefaultJsonSerializer; import com.optimizely.ab.event.internal.serializer.Serializer; +import com.optimizely.ab.internal.EventTagUtils; import com.optimizely.ab.internal.ProjectValidationUtils; import org.slf4j.Logger; @@ -76,8 +77,7 @@ public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull Experiment activatedExperiment, @Nonnull Variation variation, @Nonnull String userId, - @Nonnull Map attributes, - @CheckForNull String sessionId) { + @Nonnull Map attributes) { Impression impressionPayload = new Impression(); impressionPayload.setVisitorId(userId); @@ -93,12 +93,11 @@ public LogEvent createImpressionEvent(@Nonnull ProjectConfig projectConfig, impressionPayload.setLayerId(activatedExperiment.getLayerId()); impressionPayload.setAccountId(projectConfig.getAccountId()); - impressionPayload.setUserFeatures(createFeatures(attributes, projectConfig)); + impressionPayload.setUserFeatures(createUserFeatures(attributes, projectConfig)); impressionPayload.setClientEngine(clientEngine); impressionPayload.setClientVersion(clientVersion); impressionPayload.setAnonymizeIP(projectConfig.getAnonymizeIP()); impressionPayload.setRevision(projectConfig.getRevision()); - impressionPayload.setSessionId(sessionId); String payload = this.serializer.serialize(impressionPayload); return new LogEvent(RequestMethod.POST, IMPRESSION_ENDPOINT, Collections.emptyMap(), payload); @@ -110,15 +109,14 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, @Nonnull String eventId, @Nonnull String eventName, @Nonnull Map attributes, - @CheckForNull Long eventValue, - @CheckForNull String sessionId) { + @Nonnull Map eventTags) { Conversion conversionPayload = new Conversion(); conversionPayload.setVisitorId(userId); conversionPayload.setTimestamp(System.currentTimeMillis()); conversionPayload.setProjectId(projectConfig.getProjectId()); conversionPayload.setAccountId(projectConfig.getAccountId()); - conversionPayload.setUserFeatures(createFeatures(attributes, projectConfig)); + conversionPayload.setUserFeatures(createUserFeatures(attributes, projectConfig)); List layerStates = createLayerStates(projectConfig, bucketer, userId, eventName, attributes); if (layerStates.isEmpty()) { @@ -129,6 +127,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, conversionPayload.setEventEntityId(eventId); conversionPayload.setEventName(eventName); + Long eventValue = EventTagUtils.getRevenueValue(eventTags); if (eventValue != null) { conversionPayload.setEventMetrics( Collections.singletonList(new EventMetric(EventMetric.REVENUE_METRIC_TYPE, eventValue))); @@ -142,8 +141,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, conversionPayload.setClientEngine(clientEngine); conversionPayload.setClientVersion(clientVersion); conversionPayload.setRevision(projectConfig.getRevision()); - conversionPayload.setSessionId(sessionId); - + conversionPayload.setEventFeatures(createEventFeatures(eventTags)); String payload = this.serializer.serialize(conversionPayload); return new LogEvent(RequestMethod.POST, CONVERSION_ENDPOINT, Collections.emptyMap(), payload); @@ -155,7 +153,7 @@ public LogEvent createConversionEvent(@Nonnull ProjectConfig projectConfig, * @param attributes the {@code {attributeKey -> value}} mapping * @param projectConfig the current project config */ - private List createFeatures(Map attributes, ProjectConfig projectConfig) { + private List createUserFeatures(Map attributes, ProjectConfig projectConfig) { Map attributeKeyMapping = projectConfig.getAttributeKeyMapping(); List features = new ArrayList(); @@ -175,6 +173,21 @@ private List createFeatures(Map attributes, ProjectConf return features; } + /** + * Helper method to generate {@link Feature} objects from the given {@code {eventTagKey-> value}} mapping. + * + * @param eventTags the {@code {eventTagKey -> value}} mapping + */ + private List createEventFeatures(Map eventTags) { + List features = new ArrayList(); + + for (Map.Entry eventTagEntry : eventTags.entrySet()) { + String eventTagKey = eventTagEntry.getKey(); + features.add(new Feature("", eventTagKey, Feature.EVENT_FEATURE_TYPE, eventTagEntry.getValue(), false)); + } + return features; + } + /** * Helper method to create {@link LayerState} objects for all experiments mapped to an event. *

diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java index f2a02a7b1..9c54389bc 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Conversion.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java index 93314a890..d641552a1 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Decision.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java index 9237d5b96..974a72a4c 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Event.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java index 34ad16cf6..5303871c9 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/EventMetric.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java index 8f54be74e..22df21df9 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Feature.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,16 +19,17 @@ public class Feature { public static final String CUSTOM_ATTRIBUTE_FEATURE_TYPE = "custom"; + public static final String EVENT_FEATURE_TYPE = "custom"; private String id; private String name; private String type; - private String value; + private Object value; private boolean shouldIndex; public Feature() { } - public Feature(String id, String name, String type, String value, boolean shouldIndex) { + public Feature(String id, String name, String type, Object value, boolean shouldIndex) { this.id = id; this.name = name; this.type = type; @@ -60,7 +61,7 @@ public void setType(String type) { this.type = type; } - public String getValue() { + public Object getValue() { return value; } diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java index 771c1213c..33878a09f 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/Impression.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java index 02915ffdf..589197be7 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/payload/LayerState.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/DefaultJsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/DefaultJsonSerializer.java index e027f6784..717ac9e0b 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/DefaultJsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/DefaultJsonSerializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java index 5d06715fe..a5a7aafba 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/GsonSerializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java index 107aa47ff..afc316d34 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JacksonSerializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java index b8d329735..eb1fa71e9 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSerializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java index 625754200..549fc230e 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/SerializationException.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/SerializationException.java index de1110ff1..798d1a552 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/SerializationException.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/SerializationException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java index 24d703c45..01bd4c123 100644 --- a/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java +++ b/core-api/src/main/java/com/optimizely/ab/event/internal/serializer/Serializer.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/internal/EventTagUtils.java b/core-api/src/main/java/com/optimizely/ab/internal/EventTagUtils.java new file mode 100644 index 000000000..42dacfc5e --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/internal/EventTagUtils.java @@ -0,0 +1,50 @@ +/** + * + * Copyright 2016-2017, Optimizely and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.optimizely.ab.internal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.Map; + +public final class EventTagUtils { + + private static final Logger logger = LoggerFactory.getLogger(EventTagUtils.class); + + /** + * Grab the revenue value from the event tags. "revenue" is a reserved keyword. + * @param eventTags + * @return Long + */ + public static Long getRevenueValue(@Nonnull Map eventTags) { + Long eventValue = null; + if (eventTags.containsKey(ReservedEventKey.REVENUE.toString())) { + Object rawValue = eventTags.get(ReservedEventKey.REVENUE.toString()); + if (Long.class.isInstance(rawValue)) { + eventValue = (Long)rawValue; + logger.info("Parsed revenue value \"{}\" from event tags.", eventValue); + } else if (Integer.class.isInstance(rawValue)) { + eventValue = ((Integer)rawValue).longValue(); + logger.info("Parsed revenue value \"{}\" from event tags.", eventValue); + } else { + logger.warn("Failed to parse revenue value \"{}\" from event tags.", rawValue); + } + } + return eventValue; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/internal/ProjectValidationUtils.java b/core-api/src/main/java/com/optimizely/ab/internal/ProjectValidationUtils.java index 2ea00ea79..da283155d 100644 --- a/core-api/src/main/java/com/optimizely/ab/internal/ProjectValidationUtils.java +++ b/core-api/src/main/java/com/optimizely/ab/internal/ProjectValidationUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/internal/ReservedEventKey.java b/core-api/src/main/java/com/optimizely/ab/internal/ReservedEventKey.java new file mode 100644 index 000000000..ecc30f5b6 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/internal/ReservedEventKey.java @@ -0,0 +1,32 @@ +/** + * + * Copyright 2016-2017, Optimizely and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.optimizely.ab.internal; + +public enum ReservedEventKey { + REVENUE("revenue"); + + private final String key; + + ReservedEventKey(String key) { + this.key = key; + } + + @Override + public String toString() { + return key; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/notification/NotificationBroadcaster.java b/core-api/src/main/java/com/optimizely/ab/notification/NotificationBroadcaster.java index d9df413c0..bb77468df 100644 --- a/core-api/src/main/java/com/optimizely/ab/notification/NotificationBroadcaster.java +++ b/core-api/src/main/java/com/optimizely/ab/notification/NotificationBroadcaster.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/main/java/com/optimizely/ab/notification/NotificationListener.java b/core-api/src/main/java/com/optimizely/ab/notification/NotificationListener.java index a7c12820a..2fe6e6bc1 100644 --- a/core-api/src/main/java/com/optimizely/ab/notification/NotificationListener.java +++ b/core-api/src/main/java/com/optimizely/ab/notification/NotificationListener.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java index 69ed103e6..b9958a194 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyBuilderTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -164,6 +164,19 @@ public void withCustomClientVersion() throws Exception { assertThat(((EventBuilderV2)optimizelyClient.eventBuilder).clientVersion, is("0.0.0")); } + @SuppressFBWarnings(value="NP_NONNULL_PARAM_VIOLATION", justification="Testing nullness contract violation") + @Test + public void builderThrowsConfigParseExceptionForNullDatafile() throws Exception { + thrown.expect(ConfigParseException.class); + Optimizely.builder(null, mockEventHandler).build(); + } + + @Test + public void builderThrowsConfigParseExceptionForEmptyDatafile() throws Exception { + thrown.expect(ConfigParseException.class); + Optimizely.builder("", mockEventHandler).build(); + } + @Test public void builderThrowsConfigParseExceptionForInvalidDatafile() throws Exception { thrown.expect(ConfigParseException.class); diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV1.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV1.java index 55af6086d..47fa1b966 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV1.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV1.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import com.optimizely.ab.event.internal.EventBuilderV1; import com.optimizely.ab.internal.LogbackVerifier; import com.optimizely.ab.internal.ProjectValidationUtils; +import com.optimizely.ab.internal.ReservedEventKey; import org.junit.Rule; import org.junit.Test; @@ -122,7 +123,7 @@ public void activateEndToEnd() throws Exception { testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, "userId", - testUserAttributes, null)) + testUserAttributes)) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -251,7 +252,7 @@ public void activateWithExperimentKey() throws Exception { testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), eq(testUserAttributes), isNull(String.class))) + eq("userId"), eq(testUserAttributes))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -340,8 +341,7 @@ public void activateWithAttributes() throws Exception { testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), anyMapOf(String.class, String.class), - isNull(String.class))) + eq("userId"), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -361,8 +361,7 @@ public void activateWithAttributes() throws Exception { eq(activatedExperiment), eq(bucketedVariation), eq("userId"), - attributeCaptor.capture(), - isNull(String.class)); + attributeCaptor.capture()); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, hasEntry(attribute.getKey(), "attributeValue")); @@ -403,8 +402,7 @@ public void activateWithUnknownAttribute() throws Exception { testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), anyMapOf(String.class, String.class), - isNull(String.class))) + eq("userId"), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -426,8 +424,7 @@ public void activateWithUnknownAttribute() throws Exception { // verify that the event builder was called with the expected attributes verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), - eq(bucketedVariation), eq("userId"), attributeCaptor.capture(), - isNull(String.class)); + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, not(hasKey("unknownAttribute"))); @@ -794,8 +791,8 @@ public void trackEventWithAttributes() throws Exception { LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - anyMapOf(String.class, String.class), isNull(Long.class), - isNull(String.class))) + anyMapOf(String.class, String.class), + anyMapOf(String.class, Object.class))) .thenReturn(logEventToDispatch); logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); @@ -811,8 +808,8 @@ public void trackEventWithAttributes() throws Exception { // verify that the event builder was called with the expected attributes verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - attributeCaptor.capture(), isNull(Long.class), - isNull(String.class)); + attributeCaptor.capture(), + eq(Collections.emptyMap())); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, hasEntry(attribute.getKey(), "attributeValue")); @@ -848,8 +845,8 @@ public void trackEventWithUnknownAttribute() throws Exception { LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - anyMapOf(String.class, String.class), isNull(Long.class), - isNull(String.class))) + anyMapOf(String.class, String.class), + anyMapOf(String.class, Object.class))) .thenReturn(logEventToDispatch); logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); @@ -866,8 +863,8 @@ public void trackEventWithUnknownAttribute() throws Exception { // verify that the event builder was called with the expected attributes verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - attributeCaptor.capture(), isNull(Long.class), - isNull(String.class)); + attributeCaptor.capture(), + eq(Collections.emptyMap())); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, not(hasKey("unknownAttribute"))); @@ -900,23 +897,23 @@ public void trackEventWithRevenue() throws Exception { LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - eq(Collections.emptyMap()), eq(revenue), - isNull(String.class))) + eq(Collections.emptyMap()), + eq(Collections.singletonMap(ReservedEventKey.REVENUE.toString(), revenue)))) .thenReturn(logEventToDispatch); // call track optimizely.track(eventType.getKey(), "userId", revenue); - // setup the revenue captor (so we can verify its content) - ArgumentCaptor revenueCaptor = ArgumentCaptor.forClass(Long.class); + // setup the event tag map captor (capture the revenue value, which is a reserved keyword) + ArgumentCaptor eventTagCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected revenue verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), eq(Collections.emptyMap()), - revenueCaptor.capture(), isNull(String.class)); + eventTagCaptor.capture()); - Long actualValue = revenueCaptor.getValue(); + Long actualValue = (Long)eventTagCaptor.getValue().get(ReservedEventKey.REVENUE.toString()); assertThat(actualValue, is(revenue)); verify(mockEventHandler).dispatchEvent(logEventToDispatch); diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV2.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV2.java index 9d3c8d9ba..fedbfb88e 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV2.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV2.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,9 @@ import com.optimizely.ab.event.internal.EventBuilderV2; import com.optimizely.ab.internal.LogbackVerifier; import com.optimizely.ab.internal.ProjectValidationUtils; +import com.optimizely.ab.internal.ReservedEventKey; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -45,9 +47,9 @@ import org.mockito.junit.MockitoRule; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.HashMap; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -94,6 +96,19 @@ public class OptimizelyTestV2 { @Mock Bucketer mockBucketer; @Mock ErrorHandler mockErrorHandler; + private static final String genericUserId = "genericUserId"; + private static String validDatafile; + private static String noAudienceDatafile; + private static ProjectConfig validProjectConfig; + private static ProjectConfig noAudienceProjectConfig; + + @BeforeClass + public static void setUp() throws Exception { + validDatafile = validConfigJsonV2(); + noAudienceDatafile = noAudienceProjectConfigJsonV2(); + validProjectConfig = validProjectConfigV2(); + noAudienceProjectConfig = noAudienceProjectConfigV2(); + } //======== activate tests ========// /** @@ -102,16 +117,14 @@ public class OptimizelyTestV2 { */ @Test public void activateEndToEnd() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -121,8 +134,8 @@ public void activateEndToEnd() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, "userId", - testUserAttributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, bucketedVariation, "userId", + testUserAttributes)) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -149,14 +162,12 @@ public void activateEndToEnd() throws Exception { */ @Test public void initializationOccursForBucketerWhenBuildingOptly() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely.builder(datafile, mockEventHandler) + Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -169,13 +180,11 @@ public void initializationOccursForBucketerWhenBuildingOptly() throws Exception */ @Test public void activateForNullVariation() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -208,14 +217,12 @@ public void activateForNullVariation() throws Exception { */ @Test public void activateWhenExperimentIsNotInProject() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); Experiment unknownExperiment = createUnknownExperiment(); Variation bucketedVariation = unknownExperiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -231,16 +238,14 @@ public void activateWhenExperimentIsNotInProject() throws Exception { */ @Test public void activateWithExperimentKey() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -250,8 +255,8 @@ public void activateWithExperimentKey() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), eq(testUserAttributes), isNull(String.class))) + when(mockEventBuilder.createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), eq(testUserAttributes))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -274,12 +279,10 @@ public void activateWithExperimentKey() throws Exception { */ @Test public void activateWithUnknownExperimentKeyAndNoOpErrorHandler() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.ERROR, "Experiment \"unknown_experiment\" is not in the datafile."); @@ -301,12 +304,11 @@ public void activateWithUnknownExperimentKeyAndNoOpErrorHandler() throws Excepti public void activateWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() throws Exception { thrown.expect(UnknownExperimentException.class); - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); + ProjectConfig validProjectConfig = validProjectConfigV2(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -320,28 +322,25 @@ public void activateWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() thro @Test @SuppressWarnings("unchecked") public void activateWithAttributes() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Attribute attribute = projectConfig.getAttributes().get(0); + Attribute attribute = validProjectConfig.getAttributes().get(0); // setup a mock event builder to return expected impression params EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), anyMapOf(String.class, String.class), - isNull(String.class))) + when(mockEventBuilder.createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -357,9 +356,8 @@ public void activateWithAttributes() throws Exception { // setup the attribute map captor (so we can verify its content) ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); - verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), - eq(bucketedVariation), eq("userId"), attributeCaptor.capture(), - isNull(String.class)); + verify(mockEventBuilder).createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, hasEntry(attribute.getKey(), "attributeValue")); @@ -377,18 +375,16 @@ public void activateWithAttributes() throws Exception { @Test @SuppressWarnings("unchecked") public void activateWithUnknownAttribute() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); // setup a mock event builder to return mock params and endpoint EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -399,9 +395,8 @@ public void activateWithUnknownAttribute() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), anyMapOf(String.class, String.class), - isNull(String.class))) + when(mockEventBuilder.createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -422,9 +417,8 @@ public void activateWithUnknownAttribute() throws Exception { ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), - eq(bucketedVariation), eq("userId"), attributeCaptor.capture(), - isNull(String.class)); + verify(mockEventBuilder).createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, not(hasKey("unknownAttribute"))); @@ -434,17 +428,122 @@ public void activateWithUnknownAttribute() throws Exception { } /** - * Verify that {@link Optimizely#activate(String, String)} returns null when the experiment id corresponds to a - * non-running experiment. + * Verify that {@link Optimizely#activate(String, String, Map)} ignores null attributes. */ @Test - public void activateDraftExperiment() throws Exception { + @SuppressFBWarnings( + value="NP_NONNULL_PARAM_VIOLATION", + justification="testing nullness contract violation") + public void activateWithNullAttributes() throws Exception { + String datafile = noAudienceProjectConfigJsonV2(); + ProjectConfig projectConfig = noAudienceProjectConfigV2(); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + + // setup a mock event builder to return expected impression params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), eq(Collections.emptyMap()))) + .thenReturn(logEventToDispatch); + + when(mockBucketer.bucket(activatedExperiment, "userId")) + .thenReturn(bucketedVariation); + + // activate the experiment + Map attributes = null; + Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), "userId", attributes); + + logbackVerifier.expectMessage(Level.WARN, "Attributes is null when non-null was expected. Defaulting to an empty attributes map."); + + // verify that the bucketing algorithm was called correctly + verify(mockBucketer).bucket(activatedExperiment, "userId"); + assertThat(actualVariation, is(bucketedVariation)); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); + + Map actualValue = attributeCaptor.getValue(); + assertThat(actualValue, is(Collections.emptyMap())); + + // verify that dispatchEvent was called with the correct LogEvent object + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#activate(String, String, Map)} gracefully handles null attribute values. + */ + @Test + public void activateWithNullAttributeValues() throws Exception { String datafile = validConfigJsonV2(); ProjectConfig projectConfig = validProjectConfigV2(); - Experiment draftExperiment = projectConfig.getExperiments().get(1); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + Attribute attribute = projectConfig.getAttributes().get(0); + + // setup a mock event builder to return expected impression params + EventBuilder mockEventBuilder = mock(EventBuilder.class); Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), anyMapOf(String.class, String.class))) + .thenReturn(logEventToDispatch); + + when(mockBucketer.bucket(activatedExperiment, "userId")) + .thenReturn(bucketedVariation); + + // activate the experiment + Map attributes = new HashMap(); + attributes.put(attribute.getKey(), null); + Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), "userId", attributes); + + // verify that the bucketing algorithm was called correctly + verify(mockBucketer).bucket(activatedExperiment, "userId"); + assertThat(actualVariation, is(bucketedVariation)); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); + + Map actualValue = attributeCaptor.getValue(); + assertThat(actualValue, hasEntry(attribute.getKey(), null)); + + // verify that dispatchEvent was called with the correct LogEvent object + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#activate(String, String)} returns null when the experiment id corresponds to a + * non-running experiment. + */ + @Test + public void activateDraftExperiment() throws Exception { + Experiment draftExperiment = validProjectConfig.getExperiments().get(1); + + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "Experiment \"etag2\" is not running."); @@ -461,12 +560,10 @@ public void activateDraftExperiment() throws Exception { */ @Test public void activateUserInAudience() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experimentToCheck = projectConfig.getExperiments().get(0); + Experiment experimentToCheck = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -482,12 +579,10 @@ public void activateUserInAudience() throws Exception { */ @Test public void activateUserNotInAudience() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experimentToCheck = projectConfig.getExperiments().get(0); + Experiment experimentToCheck = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -508,11 +603,9 @@ public void activateUserNotInAudience() throws Exception { */ @Test public void activateUserWithNoAudiences() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment experimentToCheck = projectConfig.getExperiments().get(0); + Experiment experimentToCheck = noAudienceProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withErrorHandler(mockErrorHandler) .build(); @@ -525,11 +618,9 @@ public void activateUserWithNoAudiences() throws Exception { */ @Test public void activateUserNoAttributesWithAudiences() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .build(); logbackVerifier.expectMessage(Level.INFO, @@ -544,13 +635,11 @@ public void activateUserNoAttributesWithAudiences() throws Exception { */ @Test public void activateWithEmptyUserId() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); String experimentKey = experiment.getKey(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -565,9 +654,7 @@ public void activateWithEmptyUserId() throws Exception { */ @Test public void activateForGroupExperimentWithMatchingAttributes() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getGroups() + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); @@ -575,8 +662,8 @@ public void activateForGroupExperimentWithMatchingAttributes() throws Exception when(mockBucketer.bucket(experiment, "user")).thenReturn(variation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -590,15 +677,13 @@ public void activateForGroupExperimentWithMatchingAttributes() throws Exception */ @Test public void activateForGroupExperimentWithNonMatchingAttributes() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getGroups() + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); String experimentKey = experiment.getKey(); @@ -617,13 +702,11 @@ public void activateForGroupExperimentWithNonMatchingAttributes() throws Excepti */ @Test public void activateForcedVariationPrecedesAudienceEval() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = validProjectConfig.getExperiments().get(0); Variation expectedVariation = experiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "User \"testUser1\" is forced in variation \"vtag1\"."); @@ -637,12 +720,10 @@ public void activateForcedVariationPrecedesAudienceEval() throws Exception { */ @Test public void activateExperimentStatusPrecedesForcedVariation() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(1); + Experiment experiment = validProjectConfig.getExperiments().get(1); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "Experiment \"etag2\" is not running."); @@ -657,14 +738,12 @@ public void activateExperimentStatusPrecedesForcedVariation() throws Exception { */ @Test public void activateDispatchEventThrowsException() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); doThrow(new Exception("Test Exception")).when(mockEventHandler).dispatchEvent(any(LogEvent.class)); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .build(); logbackVerifier.expectMessage(Level.ERROR, "Unexpected exception in event dispatcher"); @@ -677,13 +756,11 @@ public void activateDispatchEventThrowsException() throws Exception { */ @Test public void activateLaunchedExperimentDoesNotDispatchEvent() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment launchedExperiment = projectConfig.getExperiments().get(2); + Experiment launchedExperiment = noAudienceProjectConfig.getExperiments().get(2); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(noAudienceProjectConfig) .build(); Variation expectedVariation = launchedExperiment.getVariations().get(0); @@ -702,41 +779,6 @@ public void activateLaunchedExperimentDoesNotDispatchEvent() throws Exception { verify(mockEventHandler, never()).dispatchEvent(any(LogEvent.class)); } - /** - * Verify that {@link Optimizely#activate(String, String, String)} passes the session ID to - * {@link EventBuilder#createImpressionEvent(ProjectConfig, Experiment, Variation, String, Map, String)} - */ - @Test - public void activateWithSessionId() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); - - EventBuilder mockEventBuilder = mock(EventBuilder.class); - - Map testParams = new HashMap(); - testParams.put("test", "params"); - LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - - when(mockEventBuilder.createImpressionEvent(eq(projectConfig), any(Experiment.class), any(Variation.class), - eq("userId"), eq(Collections.emptyMap()), - eq("test_session_id"))) - .thenReturn(logEventToDispatch); - - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) - .withEventBuilder(mockEventBuilder) - .build(); - - optimizely.activate(experiment.getKey(), "userId", "test_session_id"); - - // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), any(Experiment.class), - any(Variation.class), eq("userId"), - eq(Collections.emptyMap()), - eq("test_session_id")); - } - //======== track tests ========// /** @@ -745,21 +787,19 @@ public void activateWithSessionId() throws Exception { */ @Test public void trackEventEndToEnd() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - List allExperiments = projectConfig.getExperiments(); - EventType eventType = projectConfig.getEventTypes().get(0); + List allExperiments = noAudienceProjectConfig.getExperiments(); + EventType eventType = noAudienceProjectConfig.getEventTypes().get(0); EventBuilder eventBuilderV2 = new EventBuilderV2(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(eventBuilderV2) - .withConfig(projectConfig) + .withConfig(noAudienceProjectConfig) .withErrorHandler(mockErrorHandler) .build(); - List experimentIds = projectConfig.getExperimentIdsForGoal(eventType.getKey()); + List experimentIds = noAudienceProjectConfig.getExperimentIdsForGoal(eventType.getKey()); // Bucket to the first variation for all experiments. However, only a subset of the experiments will actually // call the bucket function. @@ -775,7 +815,7 @@ public void trackEventEndToEnd() throws Exception { // verify that the bucketing algorithm was called only on experiments corresponding to the specified goal. for (Experiment experiment : allExperiments) { - if (ProjectValidationUtils.validatePreconditions(projectConfig, experiment, "userId", emptyAttributes) && + if (ProjectValidationUtils.validatePreconditions(noAudienceProjectConfig, experiment, "userId", emptyAttributes) && experimentIds.contains(experiment.getId())) { verify(mockBucketer).bucket(experiment, "userId"); } else { @@ -793,12 +833,10 @@ public void trackEventEndToEnd() throws Exception { */ @Test public void trackEventWithUnknownEventKeyAndNoOpErrorHandler() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); EventType unknownEventType = createUnknownEventType(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(new NoOpErrorHandler()) .build(); @@ -818,12 +856,10 @@ public void trackEventWithUnknownEventKeyAndNoOpErrorHandler() throws Exception public void trackEventWithUnknownEventKeyAndRaiseExceptionErrorHandler() throws Exception { thrown.expect(UnknownEventTypeException.class); - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); EventType unknownEventType = createUnknownEventType(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -837,28 +873,25 @@ public void trackEventWithUnknownEventKeyAndRaiseExceptionErrorHandler() throws @Test @SuppressWarnings("unchecked") public void trackEventWithAttributes() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Attribute attribute = projectConfig.getAttributes().get(0); - EventType eventType = projectConfig.getEventTypes().get(0); + Attribute attribute = validProjectConfig.getAttributes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); // setup a mock event builder to return expected conversion params EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - anyMapOf(String.class, String.class), isNull(Long.class), - isNull(String.class))) + anyMapOf(String.class, String.class), eq(Collections.emptyMap()))) .thenReturn(logEventToDispatch); logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); @@ -872,10 +905,9 @@ public void trackEventWithAttributes() throws Exception { ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + verify(mockEventBuilder).createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - attributeCaptor.capture(), isNull(Long.class), - isNull(String.class)); + attributeCaptor.capture(), eq(Collections.emptyMap())); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, hasEntry(attribute.getKey(), "attributeValue")); @@ -883,6 +915,107 @@ public void trackEventWithAttributes() throws Exception { verify(mockEventHandler).dispatchEvent(logEventToDispatch); } + /** + * Verify that {@link Optimizely#track(String, String)} ignores null attributes. + */ + @Test + @SuppressFBWarnings( + value="NP_NONNULL_PARAM_VIOLATION", + justification="testing nullness contract violation") + public void trackEventWithNullAttributes() throws Exception { + String datafile = noAudienceProjectConfigJsonV2(); + ProjectConfig projectConfig = noAudienceProjectConfigV2(); + EventType eventType = projectConfig.getEventTypes().get(0); + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eq(Collections.emptyMap()))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + // call track + Map attributes = null; + optimizely.track(eventType.getKey(), "userId", attributes); + + logbackVerifier.expectMessage(Level.WARN, "Attributes is null when non-null was expected. Defaulting to an empty attributes map."); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + attributeCaptor.capture(), eq(Collections.emptyMap())); + + Map actualValue = attributeCaptor.getValue(); + assertThat(actualValue, is(Collections.emptyMap())); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#track(String, String)} gracefully handles null attribute values. + */ + @Test + public void trackEventWithNullAttributeValues() throws Exception { + String datafile = validConfigJsonV2(); + ProjectConfig projectConfig = validProjectConfigV2(); + EventType eventType = projectConfig.getEventTypes().get(0); + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + anyMapOf(String.class, String.class), eq(Collections.emptyMap()))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + // call track + Map attributes = new HashMap(); + attributes.put("test", null); + optimizely.track(eventType.getKey(), "userId", attributes); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + attributeCaptor.capture(), eq(Collections.emptyMap())); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + /** * Verify that {@link Optimizely#track(String, String)} handles the case where an unknown attribute * (i.e., not in the config) is passed through. @@ -892,27 +1025,24 @@ public void trackEventWithAttributes() throws Exception { @Test @SuppressWarnings("unchecked") public void trackEventWithUnknownAttribute() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - EventType eventType = projectConfig.getEventTypes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); // setup a mock event builder to return expected conversion params EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - anyMapOf(String.class, String.class), isNull(Long.class), - isNull(String.class))) + anyMapOf(String.class, String.class), eq(Collections.emptyMap()))) .thenReturn(logEventToDispatch); logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); @@ -927,10 +1057,9 @@ public void trackEventWithUnknownAttribute() throws Exception { ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + verify(mockEventBuilder).createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - attributeCaptor.capture(), isNull(Long.class), - isNull(String.class)); + attributeCaptor.capture(), eq(Collections.emptyMap())); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, not(hasKey("unknownAttribute"))); @@ -939,61 +1068,163 @@ public void trackEventWithUnknownAttribute() throws Exception { } /** - * Verify that {@link Optimizely#track(String, String)} passes through revenue. + * Verify that {@link Optimizely#track(String, String, long)} passes through revenue. */ @Test public void trackEventWithRevenue() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - EventType eventType = projectConfig.getEventTypes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); long revenue = 1234L; // setup a mock event builder to return expected conversion params EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); Map testParams = new HashMap(); testParams.put("test", "params"); + Map eventTags = new HashMap(); + eventTags.put(ReservedEventKey.REVENUE.toString(), revenue); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - eq(Collections.emptyMap()), eq(revenue), - isNull(String.class))) + eq(Collections.emptyMap()), eq(eventTags))) .thenReturn(logEventToDispatch); // call track optimizely.track(eventType.getKey(), "userId", revenue); - // setup the revenue captor (so we can verify its content) - ArgumentCaptor revenueCaptor = ArgumentCaptor.forClass(Long.class); + // setup the event tag map captor (so we can verify its content) + ArgumentCaptor eventTagCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected revenue - verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + verify(mockEventBuilder).createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), eq(Collections.emptyMap()), - revenueCaptor.capture(), isNull(String.class)); + eventTagCaptor.capture()); - Long actualValue = revenueCaptor.getValue(); + Long actualValue = (Long)eventTagCaptor.getValue().get(ReservedEventKey.REVENUE.toString()); assertThat(actualValue, is(revenue)); verify(mockEventHandler).dispatchEvent(logEventToDispatch); } + /** + * Verify that {@link Optimizely#track(String, String, Map, Map)} passes event features to + * @{link EventBuilder#createConversionEvent(ProjectConfig, Bucketer, String, String, String, Map, Map)} + */ + @Test + public void trackEventWithEventTags() throws Exception { + String datafile = validConfigJsonV2(); + ProjectConfig projectConfig = validProjectConfigV2(); + EventType eventType = projectConfig.getEventTypes().get(0); + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + anyMapOf(String.class, String.class), anyMapOf(String.class, Object.class))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + Map eventTags = new HashMap(); + eventTags.put("int_param", 123); + eventTags.put("string_param", "123"); + eventTags.put("boolean_param", false); + eventTags.put("float_param", 12.3f); + + // call track + optimizely.track(eventType.getKey(), "userId", Collections.emptyMap(), eventTags); + + // setup the event map captor (so we can verify its content) + ArgumentCaptor eventTagCaptor = ArgumentCaptor.forClass(Map.class); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eventTagCaptor.capture()); + + Map actualValue = eventTagCaptor.getValue(); + assertThat(actualValue, hasEntry("int_param", eventTags.get("int_param"))); + assertThat(actualValue, hasEntry("string_param", eventTags.get("string_param"))); + assertThat(actualValue, hasEntry("boolean_param", eventTags.get("boolean_param"))); + assertThat(actualValue, hasEntry("float_param", eventTags.get("float_param"))); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#track(String, String, Map, Map)} called with null event tags will default to + * an empty map when calling @{link EventBuilder#createConversionEvent(ProjectConfig, Bucketer, String, String, String, Map, Map)} + */ + @Test + @SuppressFBWarnings( + value="NP_NONNULL_PARAM_VIOLATION", + justification="testing nullness contract violation") + public void trackEventWithNullEventTags() throws Exception { + String datafile = validConfigJsonV2(); + ProjectConfig projectConfig = validProjectConfigV2(); + EventType eventType = projectConfig.getEventTypes().get(0); + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eq(Collections.emptyMap()))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + // call track + optimizely.track(eventType.getKey(), "userId", Collections.emptyMap(), null); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eq(Collections.emptyMap())); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + /** * Verify that {@link Optimizely#track(String, String, Map)} doesn't dispatch an event when no valid experiments * correspond to an event. */ @Test public void trackEventWithNoValidExperiments() throws Exception { - String datafile = validConfigJsonV2(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler).build(); + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler).build(); Map attributes = new HashMap(); attributes.put("browser_type", "firefox"); @@ -1012,14 +1243,12 @@ public void trackEventWithNoValidExperiments() throws Exception { */ @Test public void trackDispatchEventThrowsException() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - EventType eventType = projectConfig.getEventTypes().get(0); + EventType eventType = noAudienceProjectConfig.getEventTypes().get(0); doThrow(new Exception("Test Exception")).when(mockEventHandler).dispatchEvent(any(LogEvent.class)); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .build(); logbackVerifier.expectMessage(Level.ERROR, "Unexpected exception in event dispatcher"); @@ -1032,12 +1261,10 @@ public void trackDispatchEventThrowsException() throws Exception { */ @Test public void trackLaunchedExperimentDoesNotDispatchEvent() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - EventType eventType = projectConfig.getEventTypes().get(3); + EventType eventType = noAudienceProjectConfig.getEventTypes().get(3); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .build(); optimizely.track(eventType.getKey(), "userId"); @@ -1046,45 +1273,6 @@ public void trackLaunchedExperimentDoesNotDispatchEvent() throws Exception { verify(mockEventHandler, never()).dispatchEvent(any(LogEvent.class)); } - /** - * Verify that {@link Optimizely#track(String, String, String)} passes the session ID to - * {@link EventBuilder#createConversionEvent(ProjectConfig, Bucketer, String, String, String, Map, Long, String)} - */ - @Test - public void trackEventWithSessionId() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - EventType eventType = projectConfig.getEventTypes().get(0); - - // setup a mock event builder to return expected conversion params - EventBuilder mockEventBuilder = mock(EventBuilder.class); - - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withBucketing(mockBucketer) - .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) - .withErrorHandler(mockErrorHandler) - .build(); - - Map testParams = new HashMap(); - testParams.put("test", "params"); - LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), - eq(eventType.getId()), eq(eventType.getKey()), - eq(Collections.emptyMap()), isNull(Long.class), - eq("test_session_id"))) - .thenReturn(logEventToDispatch); - - // call track - optimizely.track(eventType.getKey(), "userId", "test_session_id"); - - // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), - eq(eventType.getId()), eq(eventType.getKey()), - eq(Collections.emptyMap()), isNull(Long.class), - eq("test_session_id")); - } - //======== getVariation tests ========// /** @@ -1093,14 +1281,13 @@ public void trackEventWithSessionId() throws Exception { */ @Test public void getVariation() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + ProjectConfig validProjectConfig = validProjectConfigV2(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1127,14 +1314,12 @@ public void getVariation() throws Exception { */ @Test public void getVariationWithExperimentKey() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = noAudienceProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(noAudienceProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1157,10 +1342,9 @@ public void getVariationWithExperimentKey() throws Exception { */ @Test public void getVariationWithUnknownExperimentKeyAndNoOpErrorHandler() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withErrorHandler(new NoOpErrorHandler()) .build(); @@ -1179,15 +1363,14 @@ public void getVariationWithUnknownExperimentKeyAndNoOpErrorHandler() throws Exc */ @Test public void getVariationWithAudiences() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + ProjectConfig validProjectConfig = validProjectConfigV2(); + Experiment experiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = experiment.getVariations().get(0); when(mockBucketer.bucket(experiment, "userId")).thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .withErrorHandler(mockErrorHandler) .build(); @@ -1207,11 +1390,10 @@ public void getVariationWithAudiences() throws Exception { */ @Test public void getVariationWithAudiencesNoAttributes() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + ProjectConfig validProjectConfig = validProjectConfigV2(); + Experiment experiment = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withErrorHandler(mockErrorHandler) .build(); @@ -1228,15 +1410,13 @@ public void getVariationWithAudiencesNoAttributes() throws Exception { */ @Test public void getVariationNoAudiences() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); Variation bucketedVariation = experiment.getVariations().get(0); when(mockBucketer.bucket(experiment, "userId")).thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withBucketing(mockBucketer) .withErrorHandler(mockErrorHandler) .build(); @@ -1255,12 +1435,10 @@ public void getVariationNoAudiences() throws Exception { public void getVariationWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() throws Exception { thrown.expect(UnknownExperimentException.class); - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -1274,12 +1452,10 @@ public void getVariationWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() */ @Test public void getVariationWithEmptyUserId() throws Exception { - String datafile = noAudienceProjectConfigJsonV2(); - ProjectConfig projectConfig = noAudienceProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -1293,9 +1469,7 @@ public void getVariationWithEmptyUserId() throws Exception { */ @Test public void getVariationForGroupExperimentWithMatchingAttributes() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getGroups() + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); @@ -1303,8 +1477,8 @@ public void getVariationForGroupExperimentWithMatchingAttributes() throws Except when(mockBucketer.bucket(experiment, "user")).thenReturn(variation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -1318,15 +1492,14 @@ public void getVariationForGroupExperimentWithMatchingAttributes() throws Except */ @Test public void getVariationForGroupExperimentWithNonMatchingAttributes() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getGroups() + ProjectConfig validProjectConfig = validProjectConfigV2(); + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); assertNull(optimizely.getVariation(experiment.getKey(), "user", @@ -1339,13 +1512,12 @@ public void getVariationForGroupExperimentWithNonMatchingAttributes() throws Exc */ @Test public void getVariationForcedVariationPrecedesAudienceEval() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(0); + ProjectConfig validProjectConfig = validProjectConfigV2(); + Experiment experiment = validProjectConfig.getExperiments().get(0); Variation expectedVariation = experiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "User \"testUser1\" is forced in variation \"vtag1\"."); @@ -1359,12 +1531,11 @@ public void getVariationForcedVariationPrecedesAudienceEval() throws Exception { */ @Test public void getVariationExperimentStatusPrecedesForcedVariation() throws Exception { - String datafile = validConfigJsonV2(); - ProjectConfig projectConfig = validProjectConfigV2(); - Experiment experiment = projectConfig.getExperiments().get(1); + ProjectConfig validProjectConfig = validProjectConfigV2(); + Experiment experiment = validProjectConfig.getExperiments().get(1); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "Experiment \"etag2\" is not running."); diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV3.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV3.java index de8212376..f3f556856 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV3.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyTestV3.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,8 +36,10 @@ import com.optimizely.ab.event.internal.EventBuilderV2; import com.optimizely.ab.internal.LogbackVerifier; import com.optimizely.ab.internal.ProjectValidationUtils; +import com.optimizely.ab.internal.ReservedEventKey; import com.optimizely.ab.notification.NotificationListener; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -98,6 +100,20 @@ public class OptimizelyTestV3 { @Mock Bucketer mockBucketer; @Mock ErrorHandler mockErrorHandler; + private static final String genericUserId = "genericUserId"; + private static String validDatafile; + private static String noAudienceDatafile; + private static ProjectConfig validProjectConfig; + private static ProjectConfig noAudienceProjectConfig; + + @BeforeClass + public static void setUp() throws Exception { + validDatafile = validConfigJsonV3(); + noAudienceDatafile = noAudienceProjectConfigJsonV3(); + validProjectConfig = validProjectConfigV3(); + noAudienceProjectConfig = noAudienceProjectConfigV3(); + } + //======== activate tests ========// /** @@ -106,16 +122,14 @@ public class OptimizelyTestV3 { */ @Test public void activateEndToEnd() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -125,8 +139,8 @@ public void activateEndToEnd() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, "userId", - testUserAttributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, bucketedVariation, "userId", + testUserAttributes)) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -153,14 +167,14 @@ public void activateEndToEnd() throws Exception { */ @Test public void initializationOccursForBucketerWhenBuildingOptly() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely.builder(datafile, mockEventHandler) + Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -173,13 +187,13 @@ public void initializationOccursForBucketerWhenBuildingOptly() throws Exception */ @Test public void activateForNullVariation() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -212,14 +226,14 @@ public void activateForNullVariation() throws Exception { */ @Test public void activateWhenExperimentIsNotInProject() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); Experiment unknownExperiment = createUnknownExperiment(); Variation bucketedVariation = unknownExperiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -235,16 +249,16 @@ public void activateWhenExperimentIsNotInProject() throws Exception { */ @Test public void activateWithExperimentKey() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -254,8 +268,8 @@ public void activateWithExperimentKey() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), eq(testUserAttributes), isNull(String.class))) + when(mockEventBuilder.createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), eq(testUserAttributes))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -278,12 +292,12 @@ public void activateWithExperimentKey() throws Exception { */ @Test public void activateWithUnknownExperimentKeyAndNoOpErrorHandler() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.ERROR, "Experiment \"unknown_experiment\" is not in the datafile."); @@ -305,12 +319,12 @@ public void activateWithUnknownExperimentKeyAndNoOpErrorHandler() throws Excepti public void activateWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() throws Exception { thrown.expect(UnknownExperimentException.class); - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -324,28 +338,27 @@ public void activateWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() thro @Test @SuppressWarnings("unchecked") public void activateWithAttributes() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Attribute attribute = projectConfig.getAttributes().get(0); + Attribute attribute = validProjectConfig.getAttributes().get(0); // setup a mock event builder to return expected impression params EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), anyMapOf(String.class, String.class), - isNull(String.class))) + when(mockEventBuilder.createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -361,9 +374,8 @@ public void activateWithAttributes() throws Exception { // setup the attribute map captor (so we can verify its content) ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); - verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), - eq(bucketedVariation), eq("userId"), attributeCaptor.capture(), - isNull(String.class)); + verify(mockEventBuilder).createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, hasEntry(attribute.getKey(), "attributeValue")); @@ -381,18 +393,18 @@ public void activateWithAttributes() throws Exception { @Test @SuppressWarnings("unchecked") public void activateWithUnknownAttribute() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); // setup a mock event builder to return mock params and endpoint EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -403,9 +415,8 @@ public void activateWithUnknownAttribute() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), - eq("userId"), anyMapOf(String.class, String.class), - isNull(String.class))) + when(mockEventBuilder.createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, "userId")) @@ -426,9 +437,8 @@ public void activateWithUnknownAttribute() throws Exception { ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), - eq(bucketedVariation), eq("userId"), attributeCaptor.capture(), - isNull(String.class)); + verify(mockEventBuilder).createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, not(hasKey("unknownAttribute"))); @@ -438,17 +448,124 @@ public void activateWithUnknownAttribute() throws Exception { } /** - * Verify that {@link Optimizely#activate(String, String)} returns null when the experiment id corresponds to a - * non-running experiment. + * Verify that {@link Optimizely#activate(String, String, Map)} ignores null attributes. */ @Test - public void activateDraftExperiment() throws Exception { + @SuppressFBWarnings( + value="NP_NONNULL_PARAM_VIOLATION", + justification="testing nullness contract violation") + public void activateWithNullAttributes() throws Exception { + String datafile = noAudienceProjectConfigJsonV3(); + ProjectConfig projectConfig = noAudienceProjectConfigV3(); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + + // setup a mock event builder to return expected impression params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), eq(Collections.emptyMap()))) + .thenReturn(logEventToDispatch); + + when(mockBucketer.bucket(activatedExperiment, "userId")) + .thenReturn(bucketedVariation); + + // activate the experiment + Map attributes = null; + Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), "userId", attributes); + + logbackVerifier.expectMessage(Level.WARN, "Attributes is null when non-null was expected. Defaulting to an empty attributes map."); + + // verify that the bucketing algorithm was called correctly + verify(mockBucketer).bucket(activatedExperiment, "userId"); + assertThat(actualVariation, is(bucketedVariation)); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); + + Map actualValue = attributeCaptor.getValue(); + assertThat(actualValue, is(Collections.emptyMap())); + + // verify that dispatchEvent was called with the correct LogEvent object + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#activate(String, String, Map)} gracefully handles null attribute values. + */ + @Test + public void activateWithNullAttributeValues() throws Exception { String datafile = validConfigJsonV3(); ProjectConfig projectConfig = validProjectConfigV3(); - Experiment draftExperiment = projectConfig.getExperiments().get(1); + Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Variation bucketedVariation = activatedExperiment.getVariations().get(0); + Attribute attribute = projectConfig.getAttributes().get(0); + + // setup a mock event builder to return expected impression params + EventBuilder mockEventBuilder = mock(EventBuilder.class); Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation), + eq("userId"), anyMapOf(String.class, String.class))) + .thenReturn(logEventToDispatch); + + when(mockBucketer.bucket(activatedExperiment, "userId")) + .thenReturn(bucketedVariation); + + // activate the experiment + Map attributes = new HashMap(); + attributes.put(attribute.getKey(), null); + Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), "userId", attributes); + + // verify that the bucketing algorithm was called correctly + verify(mockBucketer).bucket(activatedExperiment, "userId"); + assertThat(actualVariation, is(bucketedVariation)); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockEventBuilder).createImpressionEvent(eq(projectConfig), eq(activatedExperiment), + eq(bucketedVariation), eq("userId"), attributeCaptor.capture()); + + Map actualValue = attributeCaptor.getValue(); + assertThat(actualValue, hasEntry(attribute.getKey(), null)); + + // verify that dispatchEvent was called with the correct LogEvent object + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#activate(String, String)} returns null when the experiment id corresponds to a + * non-running experiment. + */ + @Test + public void activateDraftExperiment() throws Exception { + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment draftExperiment = validProjectConfig.getExperiments().get(1); + + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "Experiment \"etag2\" is not running."); @@ -465,12 +582,12 @@ public void activateDraftExperiment() throws Exception { */ @Test public void activateUserInAudience() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experimentToCheck = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experimentToCheck = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -486,12 +603,12 @@ public void activateUserInAudience() throws Exception { */ @Test public void activateUserNotInAudience() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experimentToCheck = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experimentToCheck = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -512,11 +629,9 @@ public void activateUserNotInAudience() throws Exception { */ @Test public void activateUserWithNoAudiences() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - Experiment experimentToCheck = projectConfig.getExperiments().get(0); + Experiment experimentToCheck = noAudienceProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withErrorHandler(mockErrorHandler) .build(); @@ -529,11 +644,11 @@ public void activateUserWithNoAudiences() throws Exception { */ @Test public void activateUserNoAttributesWithAudiences() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .build(); logbackVerifier.expectMessage(Level.INFO, @@ -548,13 +663,11 @@ public void activateUserNoAttributesWithAudiences() throws Exception { */ @Test public void activateWithEmptyUserId() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); String experimentKey = experiment.getKey(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -569,9 +682,9 @@ public void activateWithEmptyUserId() throws Exception { */ @Test public void activateForGroupExperimentWithMatchingAttributes() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getGroups() + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); @@ -579,8 +692,8 @@ public void activateForGroupExperimentWithMatchingAttributes() throws Exception when(mockBucketer.bucket(experiment, "user")).thenReturn(variation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -594,15 +707,15 @@ public void activateForGroupExperimentWithMatchingAttributes() throws Exception */ @Test public void activateForGroupExperimentWithNonMatchingAttributes() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getGroups() + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); String experimentKey = experiment.getKey(); @@ -621,13 +734,13 @@ public void activateForGroupExperimentWithNonMatchingAttributes() throws Excepti */ @Test public void activateForcedVariationPrecedesAudienceEval() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getExperiments().get(0); Variation expectedVariation = experiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "User \"testUser1\" is forced in variation \"vtag1\"."); @@ -641,12 +754,12 @@ public void activateForcedVariationPrecedesAudienceEval() throws Exception { */ @Test public void activateExperimentStatusPrecedesForcedVariation() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(1); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getExperiments().get(1); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "Experiment \"etag2\" is not running."); @@ -661,14 +774,12 @@ public void activateExperimentStatusPrecedesForcedVariation() throws Exception { */ @Test public void activateDispatchEventThrowsException() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); doThrow(new Exception("Test Exception")).when(mockEventHandler).dispatchEvent(any(LogEvent.class)); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .build(); logbackVerifier.expectMessage(Level.ERROR, "Unexpected exception in event dispatcher"); @@ -683,21 +794,19 @@ public void activateDispatchEventThrowsException() throws Exception { */ @Test public void trackEventEndToEnd() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - List allExperiments = projectConfig.getExperiments(); - EventType eventType = projectConfig.getEventTypes().get(0); + List allExperiments = noAudienceProjectConfig.getExperiments(); + EventType eventType = noAudienceProjectConfig.getEventTypes().get(0); EventBuilder eventBuilderV2 = new EventBuilderV2(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(eventBuilderV2) - .withConfig(projectConfig) + .withConfig(noAudienceProjectConfig) .withErrorHandler(mockErrorHandler) .build(); - List experimentIds = projectConfig.getExperimentIdsForGoal(eventType.getKey()); + List experimentIds = noAudienceProjectConfig.getExperimentIdsForGoal(eventType.getKey()); // Bucket to the first variation for all experiments. However, only a subset of the experiments will actually // call the bucket function. @@ -713,7 +822,7 @@ public void trackEventEndToEnd() throws Exception { // verify that the bucketing algorithm was called only on experiments corresponding to the specified goal. for (Experiment experiment : allExperiments) { - if (ProjectValidationUtils.validatePreconditions(projectConfig, experiment, "userId", emptyAttributes) && + if (ProjectValidationUtils.validatePreconditions(noAudienceProjectConfig, experiment, "userId", emptyAttributes) && experimentIds.contains(experiment.getId())) { verify(mockBucketer).bucket(experiment, "userId"); } else { @@ -731,12 +840,12 @@ public void trackEventEndToEnd() throws Exception { */ @Test public void trackEventWithUnknownEventKeyAndNoOpErrorHandler() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); EventType unknownEventType = createUnknownEventType(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(new NoOpErrorHandler()) .build(); @@ -756,12 +865,12 @@ public void trackEventWithUnknownEventKeyAndNoOpErrorHandler() throws Exception public void trackEventWithUnknownEventKeyAndRaiseExceptionErrorHandler() throws Exception { thrown.expect(UnknownEventTypeException.class); - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); EventType unknownEventType = createUnknownEventType(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -775,28 +884,27 @@ public void trackEventWithUnknownEventKeyAndRaiseExceptionErrorHandler() throws @Test @SuppressWarnings("unchecked") public void trackEventWithAttributes() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Attribute attribute = projectConfig.getAttributes().get(0); - EventType eventType = projectConfig.getEventTypes().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Attribute attribute = validProjectConfig.getAttributes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); // setup a mock event builder to return expected conversion params EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - anyMapOf(String.class, String.class), isNull(Long.class), - isNull(String.class))) + anyMapOf(String.class, String.class), anyMapOf(String.class, Object.class))) .thenReturn(logEventToDispatch); logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); @@ -810,10 +918,9 @@ public void trackEventWithAttributes() throws Exception { ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + verify(mockEventBuilder).createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - attributeCaptor.capture(), isNull(Long.class), - isNull(String.class)); + attributeCaptor.capture(), anyMapOf(String.class, Object.class)); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, hasEntry(attribute.getKey(), "attributeValue")); @@ -821,6 +928,107 @@ public void trackEventWithAttributes() throws Exception { verify(mockEventHandler).dispatchEvent(logEventToDispatch); } + /** + * Verify that {@link Optimizely#track(String, String)} ignores null attributes. + */ + @Test + @SuppressFBWarnings( + value="NP_NONNULL_PARAM_VIOLATION", + justification="testing nullness contract violation") + public void trackEventWithNullAttributes() throws Exception { + String datafile = noAudienceProjectConfigJsonV3(); + ProjectConfig projectConfig = noAudienceProjectConfigV3(); + EventType eventType = projectConfig.getEventTypes().get(0); + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eq(Collections.emptyMap()))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + // call track + Map attributes = null; + optimizely.track(eventType.getKey(), "userId", attributes); + + logbackVerifier.expectMessage(Level.WARN, "Attributes is null when non-null was expected. Defaulting to an empty attributes map."); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + attributeCaptor.capture(), eq(Collections.emptyMap())); + + Map actualValue = attributeCaptor.getValue(); + assertThat(actualValue, is(Collections.emptyMap())); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#track(String, String)} gracefully handles null attribute values. + */ + @Test + public void trackEventWithNullAttributeValues() throws Exception { + String datafile = validConfigJsonV3(); + ProjectConfig projectConfig = validProjectConfigV3(); + EventType eventType = projectConfig.getEventTypes().get(0); + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + anyMapOf(String.class, String.class), anyMapOf(String.class, Object.class))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + // call track + Map attributes = new HashMap(); + attributes.put("test", null); + optimizely.track(eventType.getKey(), "userId", attributes); + + // setup the attribute map captor (so we can verify its content) + ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + attributeCaptor.capture(), eq(Collections.emptyMap())); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + /** * Verify that {@link Optimizely#track(String, String)} handles the case where an unknown attribute * (i.e., not in the config) is passed through. @@ -830,27 +1038,26 @@ public void trackEventWithAttributes() throws Exception { @Test @SuppressWarnings("unchecked") public void trackEventWithUnknownAttribute() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - EventType eventType = projectConfig.getEventTypes().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + EventType eventType = validProjectConfig.getEventTypes().get(0); // setup a mock event builder to return expected conversion params EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - anyMapOf(String.class, String.class), isNull(Long.class), - isNull(String.class))) + anyMapOf(String.class, String.class), anyMapOf(String.class, Object.class))) .thenReturn(logEventToDispatch); logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); @@ -865,10 +1072,9 @@ public void trackEventWithUnknownAttribute() throws Exception { ArgumentCaptor attributeCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected attributes - verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + verify(mockEventBuilder).createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - attributeCaptor.capture(), isNull(Long.class), - isNull(String.class)); + attributeCaptor.capture(), anyMapOf(String.class, Object.class)); Map actualValue = attributeCaptor.getValue(); assertThat(actualValue, not(hasKey("unknownAttribute"))); @@ -877,14 +1083,14 @@ public void trackEventWithUnknownAttribute() throws Exception { } /** - * Verify that {@link Optimizely#track(String, String)} passes through revenue. + * Verify that {@link Optimizely#track(String, String, Map, Map)} passes event features to + * @{link EventBuilder#createConversionEvent(ProjectConfig, Bucketer, String, String, String, Map, Map)} */ @Test - public void trackEventWithRevenue() throws Exception { + public void trackEventWithEventTags() throws Exception { String datafile = validConfigJsonV3(); ProjectConfig projectConfig = validProjectConfigV3(); EventType eventType = projectConfig.getEventTypes().get(0); - long revenue = 1234L; // setup a mock event builder to return expected conversion params EventBuilder mockEventBuilder = mock(EventBuilder.class); @@ -898,26 +1104,132 @@ public void trackEventWithRevenue() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); + + Map eventTags = new HashMap(); + eventTags.put("int_param", 123); + eventTags.put("string_param", "123"); + eventTags.put("boolean_param", false); + eventTags.put("float_param", 12.3f); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + anyMapOf(String.class, String.class), eq(eventTags))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + // call track + optimizely.track(eventType.getKey(), "userId", Collections.emptyMap(), eventTags); + + // setup the event map captor (so we can verify its content) + ArgumentCaptor eventTagCaptor = ArgumentCaptor.forClass(Map.class); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eventTagCaptor.capture()); + + Map actualValue = eventTagCaptor.getValue(); + assertThat(actualValue, hasEntry("int_param", eventTags.get("int_param"))); + assertThat(actualValue, hasEntry("string_param", eventTags.get("string_param"))); + assertThat(actualValue, hasEntry("boolean_param", eventTags.get("boolean_param"))); + assertThat(actualValue, hasEntry("float_param", eventTags.get("float_param"))); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#track(String, String, Map, Map)} called with null event tags will default to + * an empty map when calling @{link EventBuilder#createConversionEvent(ProjectConfig, Bucketer, String, String, String, Map, Map)} + */ + @Test + @SuppressFBWarnings( + value="NP_NONNULL_PARAM_VIOLATION", + justification="testing nullness contract violation") + public void trackEventWithNullEventTags() throws Exception { + String datafile = validConfigJsonV3(); + ProjectConfig projectConfig = validProjectConfigV3(); + EventType eventType = projectConfig.getEventTypes().get(0); + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(projectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eq(Collections.emptyMap()))) + .thenReturn(logEventToDispatch); + + logbackVerifier.expectMessage(Level.INFO, "Tracking event \"clicked_cart\" for user \"userId\"."); + logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " + + testParams + " and payload \"\""); + + // call track + optimizely.track(eventType.getKey(), "userId", Collections.emptyMap(), null); + + // verify that the event builder was called with the expected attributes + verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + eq(eventType.getId()), eq(eventType.getKey()), + eq(Collections.emptyMap()), eq(Collections.emptyMap())); + + verify(mockEventHandler).dispatchEvent(logEventToDispatch); + } + + /** + * Verify that {@link Optimizely#track(String, String)} passes through revenue. + */ + @Test + public void trackEventWithRevenue() throws Exception { + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + EventType eventType = validProjectConfig.getEventTypes().get(0); + long revenue = 1234L; + + // setup a mock event builder to return expected conversion params + EventBuilder mockEventBuilder = mock(EventBuilder.class); + + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withBucketing(mockBucketer) + .withEventBuilder(mockEventBuilder) + .withConfig(validProjectConfig) + .withErrorHandler(mockErrorHandler) + .build(); + + Map testParams = new HashMap(); + testParams.put("test", "params"); + Map eventTags= new HashMap(); + eventTags.put(ReservedEventKey.REVENUE.toString(), revenue); + LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), - eq(Collections.emptyMap()), eq(revenue), - isNull(String.class))) + eq(Collections.emptyMap()), eq(eventTags))) .thenReturn(logEventToDispatch); // call track optimizely.track(eventType.getKey(), "userId", revenue); - // setup the revenue captor (so we can verify its content) - ArgumentCaptor revenueCaptor = ArgumentCaptor.forClass(Long.class); + // setup the event tag map captor (so we can verify its content) + ArgumentCaptor eventTagCaptor = ArgumentCaptor.forClass(Map.class); // verify that the event builder was called with the expected revenue - verify(mockEventBuilder).createConversionEvent(eq(projectConfig), eq(mockBucketer), eq("userId"), + verify(mockEventBuilder).createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq("userId"), eq(eventType.getId()), eq(eventType.getKey()), eq(Collections.emptyMap()), - revenueCaptor.capture(), isNull(String.class)); + eventTagCaptor.capture()); - Long actualValue = revenueCaptor.getValue(); + Long actualValue = (Long)eventTagCaptor.getValue().get(ReservedEventKey.REVENUE.toString()); assertThat(actualValue, is(revenue)); verify(mockEventHandler).dispatchEvent(logEventToDispatch); @@ -929,9 +1241,9 @@ public void trackEventWithRevenue() throws Exception { */ @Test public void trackEventWithNoValidExperiments() throws Exception { - String datafile = validConfigJsonV3(); + String validDatafile = validConfigJsonV3(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler).build(); + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler).build(); Map attributes = new HashMap(); attributes.put("browser_type", "firefox"); @@ -950,14 +1262,12 @@ public void trackEventWithNoValidExperiments() throws Exception { */ @Test public void trackDispatchEventThrowsException() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - EventType eventType = projectConfig.getEventTypes().get(0); + EventType eventType = noAudienceProjectConfig.getEventTypes().get(0); doThrow(new Exception("Test Exception")).when(mockEventHandler).dispatchEvent(any(LogEvent.class)); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .build(); logbackVerifier.expectMessage(Level.ERROR, "Unexpected exception in event dispatcher"); @@ -972,11 +1282,11 @@ public void trackDispatchEventThrowsException() throws Exception { */ @Test public void getVariableInvalidVariableKeyNoOpErrorHandler() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.ERROR, "Live variable \"invalid_key\" is not in the datafile."); @@ -992,11 +1302,11 @@ public void getVariableInvalidVariableKeyNoOpErrorHandler() throws Exception { public void getVariableInvalidVariableKeyRaiseExceptionErrorHandler() throws Exception { thrown.expect(UnknownLiveVariableException.class); - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -1009,17 +1319,17 @@ public void getVariableInvalidVariableKeyRaiseExceptionErrorHandler() throws Exc */ @Test public void getVariableStringActivateExperimentTrue() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); when(mockBucketer.bucket(activatedExperiment, "userId")) .thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -1037,17 +1347,17 @@ public void getVariableStringActivateExperimentTrue() throws Exception { */ @Test public void getVariableStringActivateExperimentFalse() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); when(mockBucketer.bucket(activatedExperiment, "userId")) .thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -1064,11 +1374,11 @@ public void getVariableStringActivateExperimentFalse() throws Exception { */ @Test public void getVariableStringReturnsDefaultValueNoExperimentsUsingLiveVariable() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.WARN, "No experiment is using variable \"unused_string_variable\"."); @@ -1081,15 +1391,15 @@ public void getVariableStringReturnsDefaultValueNoExperimentsUsingLiveVariable() */ @Test public void getVariableStringReturnsDefaultValueUserNotInVariation() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); // user isn't bucketed into a variation in any experiment when(mockBucketer.bucket(any(Experiment.class), any(String.class))) .thenReturn(null); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -1104,17 +1414,17 @@ public void getVariableStringReturnsDefaultValueUserNotInVariation() throws Exce */ @Test public void getVariableBoolean() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); when(mockBucketer.bucket(activatedExperiment, "userId")) .thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -1128,17 +1438,17 @@ public void getVariableBoolean() throws Exception { */ @Test public void getVariableDouble() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); when(mockBucketer.bucket(activatedExperiment, "userId")) .thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -1154,17 +1464,17 @@ public void getVariableDouble() throws Exception { */ @Test public void getVariableInteger() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); when(mockBucketer.bucket(activatedExperiment, "userId")) .thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -1182,14 +1492,14 @@ public void getVariableInteger() throws Exception { */ @Test public void getVariation() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1216,14 +1526,12 @@ public void getVariation() throws Exception { */ @Test public void getVariationWithExperimentKey() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + Experiment activatedExperiment = noAudienceProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withBucketing(mockBucketer) - .withConfig(projectConfig) + .withConfig(noAudienceProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1246,10 +1554,9 @@ public void getVariationWithExperimentKey() throws Exception { */ @Test public void getVariationWithUnknownExperimentKeyAndNoOpErrorHandler() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) .withErrorHandler(new NoOpErrorHandler()) .build(); @@ -1268,15 +1575,15 @@ public void getVariationWithUnknownExperimentKeyAndNoOpErrorHandler() throws Exc */ @Test public void getVariationWithAudiences() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = experiment.getVariations().get(0); when(mockBucketer.bucket(experiment, "userId")).thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .withErrorHandler(mockErrorHandler) .build(); @@ -1296,11 +1603,11 @@ public void getVariationWithAudiences() throws Exception { */ @Test public void getVariationWithAudiencesNoAttributes() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withErrorHandler(mockErrorHandler) .build(); @@ -1317,15 +1624,13 @@ public void getVariationWithAudiencesNoAttributes() throws Exception { */ @Test public void getVariationNoAudiences() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); Variation bucketedVariation = experiment.getVariations().get(0); when(mockBucketer.bucket(experiment, "userId")).thenReturn(bucketedVariation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withBucketing(mockBucketer) .withErrorHandler(mockErrorHandler) .build(); @@ -1344,12 +1649,10 @@ public void getVariationNoAudiences() throws Exception { public void getVariationWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() throws Exception { thrown.expect(UnknownExperimentException.class); - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); Experiment unknownExperiment = createUnknownExperiment(); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -1363,12 +1666,10 @@ public void getVariationWithUnknownExperimentKeyAndRaiseExceptionErrorHandler() */ @Test public void getVariationWithEmptyUserId() throws Exception { - String datafile = noAudienceProjectConfigJsonV3(); - ProjectConfig projectConfig = noAudienceProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + Experiment experiment = noAudienceProjectConfig.getExperiments().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(noAudienceDatafile, mockEventHandler) + .withConfig(noAudienceProjectConfig) .withErrorHandler(new RaiseExceptionErrorHandler()) .build(); @@ -1382,9 +1683,9 @@ public void getVariationWithEmptyUserId() throws Exception { */ @Test public void getVariationForGroupExperimentWithMatchingAttributes() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getGroups() + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); @@ -1392,8 +1693,8 @@ public void getVariationForGroupExperimentWithMatchingAttributes() throws Except when(mockBucketer.bucket(experiment, "user")).thenReturn(variation); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .withBucketing(mockBucketer) .build(); @@ -1407,15 +1708,15 @@ public void getVariationForGroupExperimentWithMatchingAttributes() throws Except */ @Test public void getVariationForGroupExperimentWithNonMatchingAttributes() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getGroups() + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getGroups() .get(0) .getExperiments() .get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); assertNull(optimizely.getVariation(experiment.getKey(), "user", @@ -1428,13 +1729,13 @@ public void getVariationForGroupExperimentWithNonMatchingAttributes() throws Exc */ @Test public void getVariationForcedVariationPrecedesAudienceEval() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getExperiments().get(0); Variation expectedVariation = experiment.getVariations().get(0); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "User \"testUser1\" is forced in variation \"vtag1\"."); @@ -1448,12 +1749,12 @@ public void getVariationForcedVariationPrecedesAudienceEval() throws Exception { */ @Test public void getVariationExperimentStatusPrecedesForcedVariation() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment experiment = projectConfig.getExperiments().get(1); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment experiment = validProjectConfig.getExperiments().get(1); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) - .withConfig(projectConfig) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) + .withConfig(validProjectConfig) .build(); logbackVerifier.expectMessage(Level.INFO, "Experiment \"etag2\" is not running."); @@ -1470,16 +1771,16 @@ public void getVariationExperimentStatusPrecedesForcedVariation() throws Excepti */ @Test public void addNotificationListener() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1489,15 +1790,15 @@ public void addNotificationListener() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, - bucketedVariation, userId, attributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, + bucketedVariation, userId, attributes)) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, userId)) .thenReturn(bucketedVariation); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, - bucketedVariation, userId, attributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, + bucketedVariation, userId, attributes)) .thenReturn(logEventToDispatch); // Add listener @@ -1516,13 +1817,12 @@ public void addNotificationListener() throws Exception { .onExperimentActivated(activatedExperiment, userId, attributes, actualVariation); // Check if listener is notified after an event is tracked - EventType eventType = projectConfig.getEventTypes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); String eventKey = eventType.getKey(); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq(userId), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq(userId), eq(eventType.getId()), eq(eventKey), - anyMapOf(String.class, String.class), isNull(Long.class), - isNull(String.class))) + anyMapOf(String.class, String.class), anyMapOf(String.class, Object.class))) .thenReturn(logEventToDispatch); optimizely.track(eventKey, userId, attributes); @@ -1537,16 +1837,16 @@ public void addNotificationListener() throws Exception { */ @Test public void removeNotificationListener() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1556,15 +1856,15 @@ public void removeNotificationListener() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, - bucketedVariation, userId, attributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, + bucketedVariation, userId, attributes)) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, userId)) .thenReturn(bucketedVariation); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, userId, - attributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, bucketedVariation, userId, + attributes)) .thenReturn(logEventToDispatch); // Add and remove listener @@ -1584,10 +1884,10 @@ public void removeNotificationListener() throws Exception { .onExperimentActivated(activatedExperiment, userId, attributes, actualVariation); // Check if listener is notified after an event is tracked - EventType eventType = projectConfig.getEventTypes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); String eventKey = eventType.getKey(); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq(userId), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq(userId), eq(eventType.getId()), eq(eventKey), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); @@ -1604,16 +1904,16 @@ public void removeNotificationListener() throws Exception { */ @Test public void clearNotificationListeners() throws Exception { - String datafile = validConfigJsonV3(); - ProjectConfig projectConfig = validProjectConfigV3(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); + String validDatafile = validConfigJsonV3(); + ProjectConfig validProjectConfig = validProjectConfigV3(); + Experiment activatedExperiment = validProjectConfig.getExperiments().get(0); Variation bucketedVariation = activatedExperiment.getVariations().get(0); EventBuilder mockEventBuilder = mock(EventBuilder.class); - Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler) + Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) .withBucketing(mockBucketer) .withEventBuilder(mockEventBuilder) - .withConfig(projectConfig) + .withConfig(validProjectConfig) .withErrorHandler(mockErrorHandler) .build(); @@ -1623,15 +1923,15 @@ public void clearNotificationListeners() throws Exception { Map testParams = new HashMap(); testParams.put("test", "params"); LogEvent logEventToDispatch = new LogEvent(RequestMethod.GET, "test_url", testParams, ""); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, - bucketedVariation, userId, attributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, + bucketedVariation, userId, attributes)) .thenReturn(logEventToDispatch); when(mockBucketer.bucket(activatedExperiment, userId)) .thenReturn(bucketedVariation); - when(mockEventBuilder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, userId, - attributes, null)) + when(mockEventBuilder.createImpressionEvent(validProjectConfig, activatedExperiment, bucketedVariation, userId, + attributes)) .thenReturn(logEventToDispatch); NotificationListener listener = mock(NotificationListener.class); @@ -1650,10 +1950,10 @@ public void clearNotificationListeners() throws Exception { .onExperimentActivated(activatedExperiment, userId, attributes, actualVariation); // Check if listener is notified after a event is tracked - EventType eventType = projectConfig.getEventTypes().get(0); + EventType eventType = validProjectConfig.getEventTypes().get(0); String eventKey = eventType.getKey(); - when(mockEventBuilder.createConversionEvent(eq(projectConfig), eq(mockBucketer), eq(userId), + when(mockEventBuilder.createConversionEvent(eq(validProjectConfig), eq(mockBucketer), eq(userId), eq(eventType.getId()), eq(eventKey), anyMapOf(String.class, String.class))) .thenReturn(logEventToDispatch); diff --git a/core-api/src/test/java/com/optimizely/ab/bucketing/BucketerTest.java b/core-api/src/test/java/com/optimizely/ab/bucketing/BucketerTest.java index 1e571945e..f98763505 100644 --- a/core-api/src/test/java/com/optimizely/ab/bucketing/BucketerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/bucketing/BucketerTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ */ package com.optimizely.ab.bucketing; +import ch.qos.logback.classic.Level; import com.optimizely.ab.bucketing.internal.MurmurHash3; import com.optimizely.ab.categories.ExhaustiveTest; import com.optimizely.ab.config.Experiment; @@ -23,7 +24,8 @@ import com.optimizely.ab.config.TrafficAllocation; import com.optimizely.ab.config.Variation; import com.optimizely.ab.internal.LogbackVerifier; - +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.junit.Assume; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -36,9 +38,6 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import ch.qos.logback.classic.Level; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import static com.optimizely.ab.config.ProjectConfigTestUtils.validProjectConfigV2; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -79,6 +78,7 @@ public void generateBucketValueForNegativeHashCodes() throws Exception { @Test @Category(ExhaustiveTest.class) public void generateBucketValueDistribution() throws Exception { + Assume.assumeTrue(Boolean.valueOf(System.getenv("CI"))); long lowerHalfCount = 0; long totalCount = 0; int outOfRangeCount = 0; diff --git a/core-api/src/test/java/com/optimizely/ab/bucketing/internal/MurmurHash3Test.java b/core-api/src/test/java/com/optimizely/ab/bucketing/internal/MurmurHash3Test.java index 65f7b9ae9..bf86a31f9 100644 --- a/core-api/src/test/java/com/optimizely/ab/bucketing/internal/MurmurHash3Test.java +++ b/core-api/src/test/java/com/optimizely/ab/bucketing/internal/MurmurHash3Test.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/categories/ExhaustiveTest.java b/core-api/src/test/java/com/optimizely/ab/categories/ExhaustiveTest.java index 9e9494fae..484112a79 100644 --- a/core-api/src/test/java/com/optimizely/ab/categories/ExhaustiveTest.java +++ b/core-api/src/test/java/com/optimizely/ab/categories/ExhaustiveTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTest.java b/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTest.java index a91e09452..69ca282a9 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java b/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java index 483100f47..261da80f9 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java +++ b/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/config/VariationTest.java b/core-api/src/test/java/com/optimizely/ab/config/VariationTest.java index 1e145e07d..5a76f51ca 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/VariationTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/VariationTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java b/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java index 6bbb91d36..101193558 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/audience/AudienceConditionEvaluationTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/DefaultConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/DefaultConfigParserTest.java index 0fa1db64e..5911932f8 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/DefaultConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/DefaultConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,4 +27,4 @@ public class DefaultConfigParserTest { public void createThrowException() throws Exception { // FIXME - mdodsworth: hmmm, this isn't going to be the easiest thing to test } -} \ No newline at end of file +} diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java index feff38be2..9a182c371 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import com.optimizely.ab.config.ProjectConfig; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -86,4 +87,27 @@ public void validJsonRequiredFieldMissingExceptionWrapping() throws Exception { GsonConfigParser parser = new GsonConfigParser(); parser.parseProjectConfig("{\"valid\": \"json\"}"); } -} \ No newline at end of file + + /** + * Verify that empty string JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + public void emptyJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + GsonConfigParser parser = new GsonConfigParser(); + parser.parseProjectConfig(""); + } + + /** + * Verify that null JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + @SuppressFBWarnings(value="NP_NONNULL_PARAM_VIOLATION", justification="Testing nullness contract violation") + public void nullJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + GsonConfigParser parser = new GsonConfigParser(); + parser.parseProjectConfig(null); + } +} diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java index 18e558082..f2c8ce8d0 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import com.optimizely.ab.config.ProjectConfig; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -86,4 +87,27 @@ public void validJsonRequiredFieldMissingExceptionWrapping() throws Exception { JacksonConfigParser parser = new JacksonConfigParser(); parser.parseProjectConfig("{\"valid\": \"json\"}"); } -} \ No newline at end of file + + /** + * Verify that empty string JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + public void emptyJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + JacksonConfigParser parser = new JacksonConfigParser(); + parser.parseProjectConfig(""); + } + + /** + * Verify that null JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + @SuppressFBWarnings(value="NP_NONNULL_PARAM_VIOLATION", justification="Testing nullness contract violation") + public void nullJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + JacksonConfigParser parser = new JacksonConfigParser(); + parser.parseProjectConfig(null); + } +} diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java index 0e49c8204..cb9088ea0 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import com.optimizely.ab.config.ProjectConfig; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -86,4 +87,27 @@ public void validJsonRequiredFieldMissingExceptionWrapping() throws Exception { JsonConfigParser parser = new JsonConfigParser(); parser.parseProjectConfig("{\"valid\": \"json\"}"); } -} \ No newline at end of file + + /** + * Verify that empty string JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + public void emptyJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + JsonConfigParser parser = new JsonConfigParser(); + parser.parseProjectConfig(""); + } + + /** + * Verify that null JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + @SuppressFBWarnings(value="NP_NONNULL_PARAM_VIOLATION", justification="Testing nullness contract violation") + public void nullJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + JsonConfigParser parser = new JsonConfigParser(); + parser.parseProjectConfig(null); + } +} diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java index 57a07f78d..b8bc9c373 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import com.optimizely.ab.config.ProjectConfig; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -86,4 +87,25 @@ public void validJsonRequiredFieldMissingExceptionWrapping() throws Exception { JsonSimpleConfigParser parser = new JsonSimpleConfigParser(); parser.parseProjectConfig("{\"valid\": \"json\"}"); } -} \ No newline at end of file + /** + * Verify that empty string JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + public void emptyJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + JsonSimpleConfigParser parser = new JsonSimpleConfigParser(); + parser.parseProjectConfig(""); + } + /** + * Verify that null JSON results in a {@link ConfigParseException} being thrown. + */ + @Test + @SuppressFBWarnings(value="NP_NONNULL_PARAM_VIOLATION", justification="Testing nullness contract violation") + public void nullJsonExceptionWrapping() throws Exception { + thrown.expect(ConfigParseException.class); + + JsonSimpleConfigParser parser = new JsonSimpleConfigParser(); + parser.parseProjectConfig(null); + } +} diff --git a/core-api/src/test/java/com/optimizely/ab/event/NoopEventHandlerTest.java b/core-api/src/test/java/com/optimizely/ab/event/NoopEventHandlerTest.java index 972120565..9b1f29e9c 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/NoopEventHandlerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/NoopEventHandlerTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV1Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV1Test.java index 64f86a401..fc3c3cc09 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV1Test.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV1Test.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import com.optimizely.ab.config.Variation; import com.optimizely.ab.internal.ProjectValidationUtils; +import com.optimizely.ab.internal.ReservedEventKey; import org.junit.Test; import java.util.Arrays; @@ -162,9 +163,10 @@ public void createConversionParamsWithRevenue() throws Exception { } Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + Map eventTagsMap = Collections.singletonMap(ReservedEventKey.REVENUE.toString(), revenue); LogEvent conversionEvent = builder.createConversionEvent(projectConfig, mockBucketAlgorithm, "userId", eventType.getId(), eventType.getKey(), attributeMap, - revenue); + eventTagsMap); Map requestParams = conversionEvent.getRequestParams(); // we're not going to verify everything, just revenue and the associated goals diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java index 3b76e8184..ffcc8ddc0 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/EventBuilderV2Test.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,16 +37,19 @@ import com.optimizely.ab.internal.LogbackVerifier; import com.optimizely.ab.internal.ProjectValidationUtils; +import com.optimizely.ab.internal.ReservedEventKey; import org.junit.Rule; import org.junit.Test; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.closeTo; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; @@ -176,27 +179,6 @@ public void createImpressionEventAndroidTVClientEngineClientVersion() throws Exc assertThat(impression.getClientVersion(), is(clientVersion)); } - /** - * Verify that passing a non-null session ID to - * {@link EventBuilder#createImpressionEvent(ProjectConfig, Experiment, Variation, String, Map, String)} properly - * constructs an impression payload with the session ID specified. - */ - @Test - public void createImpressionEventWithSessionId() throws Exception { - ProjectConfig projectConfig = ProjectConfigTestUtils.validProjectConfigV2(); - Experiment activatedExperiment = projectConfig.getExperiments().get(0); - Variation bucketedVariation = activatedExperiment.getVariations().get(0); - Attribute attribute = projectConfig.getAttributes().get(0); - String userId = "userId"; - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - String sessionId = "sessionid"; - - LogEvent impressionEvent = builder.createImpressionEvent(projectConfig, activatedExperiment, bucketedVariation, - userId, attributeMap, sessionId); - Impression impression = gson.fromJson(impressionEvent.getBody(), Impression.class); - assertThat(impression.getSessionId(), is(sessionId)); - } - /** * Verify {@link Conversion} event creation */ @@ -221,8 +203,11 @@ public void createConversionEvent() throws Exception { } Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + Map eventTagMap = new HashMap(); + eventTagMap.put("boolean_param", false); + eventTagMap.put("string_param", "123"); LogEvent conversionEvent = builder.createConversionEvent(projectConfig, mockBucketAlgorithm, userId, - eventType.getId(), eventType.getKey(), attributeMap); + eventType.getId(), eventType.getKey(), attributeMap, eventTagMap); List expectedLayerStates = new ArrayList(); @@ -252,12 +237,21 @@ public void createConversionEvent() throws Exception { Feature feature = new Feature(attribute.getId(), attribute.getKey(), Feature.CUSTOM_ATTRIBUTE_FEATURE_TYPE, "value", true); List expectedUserFeatures = Collections.singletonList(feature); + + // Event Features + List expectedEventFeatures = new ArrayList(); + expectedEventFeatures.add(new Feature("", "boolean_param", Feature.EVENT_FEATURE_TYPE, + false, false)); + expectedEventFeatures.add(new Feature("", "string_param", Feature.EVENT_FEATURE_TYPE, + "123", false)); + assertThat(conversion.getUserFeatures(), is(expectedUserFeatures)); assertThat(conversion.getLayerStates(), is(expectedLayerStates)); assertThat(conversion.getEventEntityId(), is(eventType.getId())); assertThat(conversion.getEventName(), is(eventType.getKey())); assertThat(conversion.getEventMetrics(), is(Collections.emptyList())); - assertThat(conversion.getEventFeatures(), is(Collections.emptyList())); + assertTrue(conversion.getEventFeatures().containsAll(expectedEventFeatures)); + assertTrue(expectedEventFeatures.containsAll(conversion.getEventFeatures())); assertFalse(conversion.getIsGlobalHoldback()); assertThat(conversion.getAnonymizeIP(), is(projectConfig.getAnonymizeIP())); assertThat(conversion.getClientEngine(), is(ClientEngine.JAVA_SDK.getClientEngineValue())); @@ -285,9 +279,11 @@ public void createConversionParamsWithRevenue() throws Exception { } Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); + Map eventTagMap = new HashMap(); + eventTagMap.put(ReservedEventKey.REVENUE.toString(), revenue); LogEvent conversionEvent = builder.createConversionEvent(projectConfig, mockBucketAlgorithm, "userId", eventType.getId(), eventType.getKey(), attributeMap, - revenue); + eventTagMap); Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); @@ -472,33 +468,4 @@ public void createConversionEventForEventUsingLaunchedExperiment() throws Except // event will be null assertNull(conversionEvent); } - - /** - * Verify that passing a non-null session ID to - * {@link EventBuilder#createConversionEvent(ProjectConfig, Bucketer, String, String, String, Map, Long, String)} - * properly constructs an impression payload with the session ID specified. - */ - @Test - public void createConversionEventWithSessionId() throws Exception { - EventBuilderV2 builder = new EventBuilderV2(ClientEngine.ANDROID_SDK, "0.0.0"); - ProjectConfig projectConfig = ProjectConfigTestUtils.validProjectConfigV2(); - Attribute attribute = projectConfig.getAttributes().get(0); - EventType eventType = projectConfig.getEventTypes().get(0); - String userId = "userId"; - String sessionId = "sessionid"; - - Bucketer mockBucketAlgorithm = mock(Bucketer.class); - for (Experiment experiment : projectConfig.getExperiments()) { - when(mockBucketAlgorithm.bucket(experiment, userId)) - .thenReturn(experiment.getVariations().get(0)); - } - - Map attributeMap = Collections.singletonMap(attribute.getKey(), "value"); - LogEvent conversionEvent = builder.createConversionEvent(projectConfig, mockBucketAlgorithm, userId, - eventType.getId(), eventType.getKey(), attributeMap, - null, sessionId); - - Conversion conversion = gson.fromJson(conversionEvent.getBody(), Conversion.class); - assertThat(conversion.getSessionId(), is(sessionId)); - } } diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java index e7977092a..a69340482 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/GsonSerializerTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java index e29c5ebaa..03792b9e2 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JacksonSerializerTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java index 64c7988e9..f76d02f77 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSerializerTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java index c2e0775f5..da286b2ba 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/JsonSimpleSerializerTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java index 456ec149b..e39464fc5 100644 --- a/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java +++ b/core-api/src/test/java/com/optimizely/ab/event/internal/serializer/SerializerTestUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV1.java b/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV1.java index 771635034..5bfce4fe2 100644 --- a/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV1.java +++ b/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV1.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV2.java b/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV2.java index f7828eb77..c4b954742 100644 --- a/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV2.java +++ b/core-api/src/test/java/com/optimizely/ab/internal/ProjectValidationUtilsTestV2.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-api/src/test/java/com/optimizely/ab/notification/NotificationBroadcasterTest.java b/core-api/src/test/java/com/optimizely/ab/notification/NotificationBroadcasterTest.java index 9bfa36d05..d754c4233 100644 --- a/core-api/src/test/java/com/optimizely/ab/notification/NotificationBroadcasterTest.java +++ b/core-api/src/test/java/com/optimizely/ab/notification/NotificationBroadcasterTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/HttpClientUtils.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/HttpClientUtils.java index d26249b55..bb7ca3e76 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/HttpClientUtils.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/HttpClientUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/NamedThreadFactory.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/NamedThreadFactory.java index 098d4f605..8e1443c8a 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/NamedThreadFactory.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/NamedThreadFactory.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/event/AsyncEventHandler.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/event/AsyncEventHandler.java index 881dabe72..6cce2fc00 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/event/AsyncEventHandler.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/event/AsyncEventHandler.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016, Optimizely and contributors + * Copyright 2016-2017, Optimizely and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/gradle.properties b/gradle.properties index 59036d0e7..7fb8b8b76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Maven version -version = 1.5.0-SNAPSHOT +version = 1.6.0-SNAPSHOT # Artifact paths mavenS3Bucket = optimizely-maven diff --git a/gradle/HEADER b/gradle/HEADER index 95949fc29..1e8f53dac 100644 --- a/gradle/HEADER +++ b/gradle/HEADER @@ -1,5 +1,5 @@ - Copyright ${year}, Optimizely and contributors + Copyright ${start_year}-${end_year}, Optimizely and contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/gradle/license.gradle b/gradle/license.gradle index 7ae4e4bf3..5e46c7a89 100644 --- a/gradle/license.gradle +++ b/gradle/license.gradle @@ -3,6 +3,7 @@ subprojects { license { header rootProject.file('gradle/HEADER') excludes(['**/*.properties', '**/*.txt', '**/*.conf', '**/*.xml', '**/*.json', '**/LogbackVerifier.java']) - ext.year = '2016' // year that the project was created + ext.start_year = '2016' // year that the project was created + ext.end_year = Calendar.getInstance().get(Calendar.YEAR) } }