|
19 | 19 | import ch.qos.logback.classic.Level;
|
20 | 20 | import com.google.common.collect.ImmutableMap;
|
21 | 21 | import com.optimizely.ab.bucketing.Bucketer;
|
| 22 | +import com.optimizely.ab.bucketing.UserProfile; |
22 | 23 | import com.optimizely.ab.config.Attribute;
|
23 | 24 | import com.optimizely.ab.config.EventType;
|
24 | 25 | import com.optimizely.ab.config.Experiment;
|
25 | 26 | import com.optimizely.ab.config.ProjectConfig;
|
26 | 27 | import com.optimizely.ab.config.TrafficAllocation;
|
27 | 28 | import com.optimizely.ab.config.Variation;
|
| 29 | +import com.optimizely.ab.config.parser.ConfigParseException; |
28 | 30 | import com.optimizely.ab.error.ErrorHandler;
|
29 | 31 | import com.optimizely.ab.error.NoOpErrorHandler;
|
30 | 32 | import com.optimizely.ab.error.RaiseExceptionErrorHandler;
|
|
62 | 64 | import static org.hamcrest.MatcherAssert.assertThat;
|
63 | 65 | import static org.hamcrest.Matchers.hasEntry;
|
64 | 66 | import static org.hamcrest.Matchers.hasKey;
|
| 67 | +import static org.junit.Assert.assertEquals; |
65 | 68 | import static org.junit.Assert.assertNotNull;
|
66 | 69 | import static org.junit.Assert.assertNull;
|
67 | 70 | import static org.mockito.Matchers.any;
|
|
71 | 74 | import static org.mockito.Mockito.doThrow;
|
72 | 75 | import static org.mockito.Mockito.mock;
|
73 | 76 | import static org.mockito.Mockito.never;
|
| 77 | +import static org.mockito.Mockito.spy; |
74 | 78 | import static org.mockito.Mockito.verify;
|
75 | 79 | import static org.mockito.Mockito.when;
|
76 | 80 |
|
@@ -1450,6 +1454,30 @@ public void trackDispatchesWhenEventHasLaunchedAndRunningExperiments() throws Ex
|
1450 | 1454 | verify(client.eventHandler).dispatchEvent(eq(conversionEvent));
|
1451 | 1455 | }
|
1452 | 1456 |
|
| 1457 | + |
| 1458 | + |
| 1459 | + /** |
| 1460 | + * Verify that an event is not dispatched if a user doesn't satisfy audience conditions for an experiment. |
| 1461 | + */ |
| 1462 | + @Test |
| 1463 | + public void trackDoesNotSendEventWhenUserDoesNotSatisfyAudiences() throws Exception { |
| 1464 | + Attribute attribute = validProjectConfig.getAttributes().get(0); |
| 1465 | + EventType eventType = validProjectConfig.getEventTypes().get(2); |
| 1466 | + |
| 1467 | + // the audience for the experiments is "NOT firefox" so this user shouldn't satisfy audience conditions |
| 1468 | + Map<String, String> attributeMap = Collections.singletonMap(attribute.getKey(), "firefox"); |
| 1469 | + |
| 1470 | + Optimizely client = Optimizely.builder(validDatafile, mockEventHandler) |
| 1471 | + .withConfig(validProjectConfig) |
| 1472 | + .build(); |
| 1473 | + |
| 1474 | + logbackVerifier.expectMessage(Level.INFO, "There are no valid experiments for event \"" + eventType.getKey() |
| 1475 | + + "\" to track."); |
| 1476 | + |
| 1477 | + client.track(eventType.getKey(), genericUserId, attributeMap); |
| 1478 | + verify(mockEventHandler, never()).dispatchEvent(any(LogEvent.class)); |
| 1479 | + } |
| 1480 | + |
1453 | 1481 | //======== getVariation tests ========//
|
1454 | 1482 |
|
1455 | 1483 | /**
|
@@ -1604,6 +1632,37 @@ public void getVariationNoAudiences() throws Exception {
|
1604 | 1632 | assertThat(actualVariation, is(bucketedVariation));
|
1605 | 1633 | }
|
1606 | 1634 |
|
| 1635 | + /** |
| 1636 | + * Verify that {@link Optimizely#getVariation(Experiment, String, Map)} |
| 1637 | + * gives precedence to user profile over audience evaluation. |
| 1638 | + */ |
| 1639 | + @Test |
| 1640 | + public void getVariationEvaluatesUserProfileBeforeAudienceTargeting() throws ConfigParseException { |
| 1641 | + Experiment experiment = validProjectConfig.getExperiments().get(0); |
| 1642 | + Variation storedVariation = experiment.getVariations().get(0); |
| 1643 | + String userProfileUserId = "userProfileId"; |
| 1644 | + |
| 1645 | + UserProfile mockedUserProfile = mock(UserProfile.class); |
| 1646 | + when(mockedUserProfile.lookup(userProfileUserId, experiment.getId())).thenReturn(storedVariation.getId()); |
| 1647 | + |
| 1648 | + Optimizely client = Optimizely.builder(validDatafile, mockEventHandler) |
| 1649 | + .withConfig(validProjectConfig) |
| 1650 | + .withUserProfile(mockedUserProfile) |
| 1651 | + .build(); |
| 1652 | + assertNotNull(client); |
| 1653 | + |
| 1654 | + // ensure that normal users still get excluded from the experiment when they fail audience evaluation |
| 1655 | + assertNull(client.getVariation(experiment, genericUserId, Collections.<String, String>emptyMap())); |
| 1656 | + |
| 1657 | + logbackVerifier.expectMessage(Level.INFO, |
| 1658 | + "User \"" + genericUserId + "\" does not meet conditions to be in experiment \"" |
| 1659 | + + experiment.getKey() + "\"."); |
| 1660 | + |
| 1661 | + // ensure that a user with a saved user profile, sees the same variation regardless of audience evaluation |
| 1662 | + assertEquals(storedVariation, |
| 1663 | + client.getVariation(experiment, userProfileUserId, Collections.<String, String>emptyMap())); |
| 1664 | + } |
| 1665 | + |
1607 | 1666 | /**
|
1608 | 1667 | * Verify that {@link Optimizely#getVariation(String, String)} handles the case where an unknown experiment
|
1609 | 1668 | * (i.e., not in the config) is passed through and a {@link RaiseExceptionErrorHandler} is provided.
|
@@ -1685,24 +1744,31 @@ public void getVariationForGroupExperimentWithNonMatchingAttributes() throws Exc
|
1685 | 1744 |
|
1686 | 1745 | /**
|
1687 | 1746 | * Verify that {@link Optimizely#getVariation(String, String, Map)} gives precedence to forced variation bucketing
|
1688 |
| - * over audience evaluation. |
| 1747 | + * over user profile and audience evaluation for murmurhash3 bucketing. |
1689 | 1748 | */
|
1690 | 1749 | @Test
|
1691 |
| - public void getVariationForcedVariationPrecedesAudienceEval() throws Exception { |
| 1750 | + public void getVariationForcedVariationPrecedesUserProfileAndAudienceEval() throws Exception { |
| 1751 | + Bucketer bucketer = spy(new Bucketer(validProjectConfig)); |
1692 | 1752 | Experiment experiment = validProjectConfig.getExperiments().get(0);
|
1693 | 1753 | Variation expectedVariation = experiment.getVariations().get(0);
|
| 1754 | + String whitelistedUserId = "testUser1"; |
1694 | 1755 |
|
1695 | 1756 | Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler)
|
1696 |
| - .withConfig(validProjectConfig) |
1697 |
| - .build(); |
| 1757 | + .withBucketing(bucketer) |
| 1758 | + .withConfig(validProjectConfig) |
| 1759 | + .build(); |
1698 | 1760 |
|
1699 | 1761 | // user excluded without audiences and whitelisting
|
1700 | 1762 | assertNull(optimizely.getVariation(experiment.getKey(), genericUserId));
|
1701 | 1763 |
|
1702 |
| - logbackVerifier.expectMessage(Level.INFO, "User \"testUser1\" is forced in variation \"vtag1\"."); |
| 1764 | + logbackVerifier.expectMessage(Level.INFO, "User \"" + whitelistedUserId + "\" is forced in variation \"vtag1\"."); |
1703 | 1765 |
|
1704 | 1766 | // no attributes provided for a experiment that has an audience
|
1705 |
| - assertThat(optimizely.getVariation(experiment.getKey(), "testUser1"), is(expectedVariation)); |
| 1767 | + assertThat(optimizely.getVariation(experiment.getKey(), whitelistedUserId), is(expectedVariation)); |
| 1768 | + |
| 1769 | + verify(bucketer).getForcedVariation(experiment, whitelistedUserId); |
| 1770 | + verify(bucketer, never()).getStoredVariation(experiment, whitelistedUserId); |
| 1771 | + verify(bucketer, never()).bucket(experiment, whitelistedUserId); |
1706 | 1772 | }
|
1707 | 1773 |
|
1708 | 1774 | /**
|
|
0 commit comments