@@ -1560,6 +1560,134 @@ public void getVariationCmabExperimentServiceError() {
15601560 verify (mockCmabService , times (1 )).getDecision (any (), any (), any (), any ());
15611561 }
15621562
1563+ /**
1564+ * Verify that getVariation returns the variation from CMAB service
1565+ * when user is bucketed into CMAB traffic and service returns a valid decision.
1566+ */
1567+ @ Test
1568+ public void getVariationCmabExperimentServiceSuccess () {
1569+ // Create a CMAB experiment
1570+ Experiment cmabExperiment = createMockCmabExperiment ();
1571+ Variation expectedVariation = cmabExperiment .getVariations ().get (1 ); // Use second variation
1572+
1573+ // Create mock Cmab object
1574+ Cmab mockCmab = mock (Cmab .class );
1575+ when (mockCmab .getTrafficAllocation ()).thenReturn (4000 );
1576+
1577+ // Create experiment with CMAB config (no whitelisting, no forced variations)
1578+ Experiment experiment = new Experiment (
1579+ cmabExperiment .getId (),
1580+ cmabExperiment .getKey (),
1581+ cmabExperiment .getStatus (),
1582+ cmabExperiment .getLayerId (),
1583+ cmabExperiment .getAudienceIds (),
1584+ cmabExperiment .getAudienceConditions (),
1585+ cmabExperiment .getVariations (),
1586+ Collections .emptyMap (), // No whitelisting
1587+ cmabExperiment .getTrafficAllocation (),
1588+ mockCmab // This makes it a CMAB experiment
1589+ );
1590+
1591+ Bucketer mockBucketer = mock (Bucketer .class );
1592+ when (mockBucketer .bucketForCmab (any (Experiment .class ), anyString (), any (ProjectConfig .class )))
1593+ .thenReturn (DecisionResponse .responseNoReasons ("$" ));
1594+ DecisionService decisionServiceWithMockCmabService = new DecisionService (
1595+ mockBucketer ,
1596+ mockErrorHandler ,
1597+ null ,
1598+ mockCmabService
1599+ );
1600+
1601+ // Mock CmabService.getDecision to return a valid decision
1602+ CmabDecision mockCmabDecision = mock (CmabDecision .class );
1603+ when (mockCmabDecision .getVariationId ()).thenReturn (expectedVariation .getId ());
1604+ when (mockCmabService .getDecision (any (), any (), any (), any ()))
1605+ .thenReturn (mockCmabDecision );
1606+
1607+ // Call getVariation
1608+ DecisionResponse <Variation > result = decisionServiceWithMockCmabService .getVariation (
1609+ experiment ,
1610+ optimizely .createUserContext (genericUserId , Collections .emptyMap ()),
1611+ v4ProjectConfig
1612+ );
1613+
1614+ // Verify that CMAB service decision is returned
1615+ assertEquals (expectedVariation , result .getResult ());
1616+
1617+ // Verify that the result is not an error
1618+ assertFalse (result .isError ());
1619+
1620+ // Assert that CmabService.getDecision was called exactly once
1621+ verify (mockCmabService , times (1 )).getDecision (any (), any (), any (), any ());
1622+
1623+ // Verify that the correct parameters were passed to CMAB service
1624+ verify (mockCmabService ).getDecision (
1625+ eq (v4ProjectConfig ),
1626+ any (OptimizelyUserContext .class ),
1627+ eq (experiment .getId ()),
1628+ any (List .class )
1629+ );
1630+ }
1631+
1632+ /**
1633+ * Verify that getVariation returns null when user is not bucketed into CMAB traffic
1634+ * by mocking the bucketer to return null for CMAB allocation.
1635+ */
1636+ @ Test
1637+ public void getVariationCmabExperimentUserNotInTrafficAllocation () {
1638+ // Create a CMAB experiment
1639+ Experiment cmabExperiment = createMockCmabExperiment ();
1640+
1641+ // Create mock Cmab object
1642+ Cmab mockCmab = mock (Cmab .class );
1643+ when (mockCmab .getTrafficAllocation ()).thenReturn (5000 ); // 50% traffic allocation
1644+
1645+ // Create experiment with CMAB config (no whitelisting, no forced variations)
1646+ Experiment experiment = new Experiment (
1647+ cmabExperiment .getId (),
1648+ cmabExperiment .getKey (),
1649+ cmabExperiment .getStatus (),
1650+ cmabExperiment .getLayerId (),
1651+ cmabExperiment .getAudienceIds (),
1652+ cmabExperiment .getAudienceConditions (),
1653+ cmabExperiment .getVariations (),
1654+ Collections .emptyMap (), // No whitelisting
1655+ cmabExperiment .getTrafficAllocation (),
1656+ mockCmab // This makes it a CMAB experiment
1657+ );
1658+
1659+ // Mock bucketer to return null for CMAB allocation (user not in CMAB traffic)
1660+ Bucketer mockBucketer = mock (Bucketer .class );
1661+ when (mockBucketer .bucketForCmab (any (Experiment .class ), anyString (), any (ProjectConfig .class )))
1662+ .thenReturn (DecisionResponse .nullNoReasons ());
1663+
1664+ DecisionService decisionServiceWithMockCmabService = new DecisionService (
1665+ mockBucketer ,
1666+ mockErrorHandler ,
1667+ null ,
1668+ mockCmabService
1669+ );
1670+
1671+ // Call getVariation
1672+ DecisionResponse <Variation > result = decisionServiceWithMockCmabService .getVariation (
1673+ experiment ,
1674+ optimizely .createUserContext (genericUserId , Collections .emptyMap ()),
1675+ v4ProjectConfig
1676+ );
1677+
1678+ // Verify that no variation is returned (user not in CMAB traffic)
1679+ assertNull (result .getResult ());
1680+
1681+ // Verify that the result is not an error
1682+ assertFalse (result .isError ());
1683+
1684+ // Assert that CmabService.getDecision was never called (user not in CMAB traffic)
1685+ verify (mockCmabService , never ()).getDecision (any (), any (), any (), any ());
1686+
1687+ // Verify that bucketer was called for CMAB allocation
1688+ verify (mockBucketer , times (1 )).bucketForCmab (any (Experiment .class ), anyString (), any (ProjectConfig .class ));
1689+ }
1690+
15631691 private Experiment createMockCmabExperiment () {
15641692 List <Variation > variations = Arrays .asList (
15651693 new Variation ("111151" , "variation_1" ),
@@ -1568,7 +1696,7 @@ private Experiment createMockCmabExperiment() {
15681696
15691697 List <TrafficAllocation > trafficAllocations = Arrays .asList (
15701698 new TrafficAllocation ("111151" , 5000 ),
1571- new TrafficAllocation ("111152" , 10000 )
1699+ new TrafficAllocation ("111152" , 4000 )
15721700 );
15731701
15741702 // Mock CMAB configuration
0 commit comments