Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ public UserProfileService getUserProfileService() {
* {@link ProjectConfig}.
*
* @param projectConfig the current project config
* @param attributes the attributes map to validate and potentially filter. Attributes which starts with reserved key
* @param attributes the attributes map to validate and potentially filter. Attributes which starts with reserved key or has null values
* {@link ProjectConfig#RESERVED_ATTRIBUTE_PREFIX} are kept.
* @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
Expand All @@ -836,25 +836,25 @@ public UserProfileService getUserProfileService() {
}

// List of attribute keys
List<String> unknownAttributes = null;
List<String> unknownAndNullValueAttributes = null;

Map<String, Attribute> attributeKeyMapping = projectConfig.getAttributeKeyMapping();
for (Map.Entry<String, ?> attribute : attributes.entrySet()) {
if (!attributeKeyMapping.containsKey(attribute.getKey()) &&
!attribute.getKey().startsWith(ProjectConfig.RESERVED_ATTRIBUTE_PREFIX)) {
if (unknownAttributes == null) {
unknownAttributes = new ArrayList<String>();
if ((!attributeKeyMapping.containsKey(attribute.getKey()) &&
!attribute.getKey().startsWith(ProjectConfig.RESERVED_ATTRIBUTE_PREFIX)) || attribute.getValue() == null) {
if (unknownAndNullValueAttributes == null) {
unknownAndNullValueAttributes = new ArrayList<String>();
}
unknownAttributes.add(attribute.getKey());
unknownAndNullValueAttributes.add(attribute.getKey());
}
}

if (unknownAttributes != null) {
logger.warn("Attribute(s) {} not in the datafile.", unknownAttributes);
// make a copy of the passed through attributes, then remove the unknown list
if (unknownAndNullValueAttributes != null) {
logger.warn("Attribute(s) {} not in the datafile or has NULL value.", unknownAndNullValueAttributes);
// make a copy of the passed through attributes, then remove the unknown and null values attributes list
attributes = new HashMap<>(attributes);
for (String unknownAttribute : unknownAttributes) {
attributes.remove(unknownAttribute);
for (String unknownOrNullValueAttribute : unknownAndNullValueAttributes) {
attributes.remove(unknownOrNullValueAttribute);
}
}

Expand Down
31 changes: 21 additions & 10 deletions core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import static com.optimizely.ab.event.LogEvent.RequestMethod;
import static com.optimizely.ab.event.internal.EventFactoryTest.createExperimentVariationMap;
import static java.util.Arrays.asList;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
Expand Down Expand Up @@ -676,7 +677,7 @@ public void activateWithUnknownAttribute() throws Exception {

logbackVerifier.expectMessage(Level.INFO, "Activating user \"userId\" in experiment \"" +
activatedExperiment.getKey() + "\".");
logbackVerifier.expectMessage(Level.WARN, "Attribute(s) [unknownAttribute] not in the datafile.");
logbackVerifier.expectMessage(Level.WARN, "Attribute(s) [unknownAttribute] not in the datafile or has NULL value.");
logbackVerifier.expectMessage(Level.DEBUG, "Dispatching impression event to URL test_url with params " +
testParams + " and payload \"\"");

Expand Down Expand Up @@ -755,29 +756,38 @@ public void activateWithNullAttributes() throws Exception {
*/
@Test
public void activateWithNullAttributeValues() throws Exception {
Experiment activatedExperiment = validProjectConfig.getExperiments().get(0);
ProjectConfig projectConfig;
String datafile;
if (datafileVersion == 4) {
projectConfig = validProjectConfig;
datafile = validDatafile;
} else {
projectConfig = noAudienceProjectConfig;
datafile = noAudienceDatafile;
}
Experiment activatedExperiment = projectConfig.getExperiments().get(0);
Attribute attribute = projectConfig.getAttributes().get(0);
Variation bucketedVariation = activatedExperiment.getVariations().get(0);
Attribute attribute = validProjectConfig.getAttributes().get(0);

// setup a mock event builder to return expected impression params
EventFactory mockEventFactory = mock(EventFactory.class);

Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler)
Optimizely optimizely = Optimizely.builder(datafile, mockEventHandler)
.withBucketing(mockBucketer)
.withEventBuilder(mockEventFactory)
.withConfig(validProjectConfig)
.withConfig(projectConfig)
.withErrorHandler(mockErrorHandler)
.build();

when(mockEventFactory.createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment), eq(bucketedVariation),
when(mockEventFactory.createImpressionEvent(eq(projectConfig), eq(activatedExperiment), eq(bucketedVariation),
eq(testUserId), anyMapOf(String.class, String.class)))
.thenReturn(logEventToDispatch);

when(mockBucketer.bucket(activatedExperiment, testUserId))
.thenReturn(bucketedVariation);

// activate the experiment
Map<String, String> attributes = new HashMap<String, String>();
Map<String, String> attributes = new HashMap<>();
attributes.put(attribute.getKey(), null);
Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), testUserId, attributes);

Expand All @@ -787,11 +797,12 @@ public void activateWithNullAttributeValues() throws Exception {

// setup the attribute map captor (so we can verify its content)
ArgumentCaptor<Map> attributeCaptor = ArgumentCaptor.forClass(Map.class);
verify(mockEventFactory).createImpressionEvent(eq(validProjectConfig), eq(activatedExperiment),
verify(mockEventFactory).createImpressionEvent(eq(projectConfig), eq(activatedExperiment),
eq(bucketedVariation), eq(testUserId), attributeCaptor.capture());

Map<String, String> actualValue = attributeCaptor.getValue();
assertThat(actualValue, hasEntry(attribute.getKey(), null));
assertNotSame(actualValue, hasEntry(attribute.getKey(), null));
logbackVerifier.expectMessage(Level.WARN, "Attribute(s) [" + attribute.getKey() + "] not in the datafile or has NULL value.");

// verify that dispatchEvent was called with the correct LogEvent object
verify(mockEventHandler).dispatchEvent(logEventToDispatch);
Expand Down Expand Up @@ -1624,7 +1635,7 @@ public void trackEventWithUnknownAttribute() throws Exception {

logbackVerifier.expectMessage(Level.INFO, "Tracking event \"" + eventType.getKey() +
"\" for user \"" + genericUserId + "\".");
logbackVerifier.expectMessage(Level.WARN, "Attribute(s) [unknownAttribute] not in the datafile.");
logbackVerifier.expectMessage(Level.WARN, "Attribute(s) [unknownAttribute] not in the datafile or has NULL value.");
logbackVerifier.expectMessage(Level.DEBUG, "Dispatching conversion event to URL test_url with params " +
testParams + " and payload \"\"");

Expand Down