|
56 | 56 | import com.optimizely.ab.OptimizelyForcedDecision; |
57 | 57 | import com.optimizely.ab.OptimizelyUserContext; |
58 | 58 | import com.optimizely.ab.cmab.service.CmabService; |
| 59 | +import com.optimizely.ab.cmab.service.CmabDecision; |
| 60 | +import com.optimizely.ab.config.Cmab; |
59 | 61 | import com.optimizely.ab.config.DatafileProjectConfigTestUtils; |
60 | 62 | import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.noAudienceProjectConfigV3; |
61 | 63 | import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV3; |
@@ -1393,4 +1395,136 @@ public void userMeetsHoldoutAudienceConditions() { |
1393 | 1395 |
|
1394 | 1396 | logbackVerifier.expectMessage(Level.INFO, "User (user123) is in variation (ho_off_key) of holdout (typed_audience_holdout)."); |
1395 | 1397 | } |
| 1398 | + |
| 1399 | + /** |
| 1400 | + * Verify that whitelisted variations take precedence over CMAB service decisions |
| 1401 | + * in CMAB experiments. |
| 1402 | + */ |
| 1403 | + @Test |
| 1404 | + public void getVariationCmabExperimentWhitelistedPrecedesCmabService() { |
| 1405 | + // Create a CMAB experiment with whitelisting |
| 1406 | + Experiment cmabExperiment = createMockCmabExperiment(); |
| 1407 | + Variation whitelistedVariation = cmabExperiment.getVariations().get(0); |
| 1408 | + |
| 1409 | + // Setup whitelisting for the test user |
| 1410 | + Map<String, String> userIdToVariationKeyMap = new HashMap<>(); |
| 1411 | + userIdToVariationKeyMap.put(whitelistedUserId, whitelistedVariation.getKey()); |
| 1412 | + |
| 1413 | + // Create mock Cmab object |
| 1414 | + Cmab mockCmab = mock(Cmab.class); |
| 1415 | + |
| 1416 | + // Create experiment with whitelisting and CMAB config |
| 1417 | + Experiment experimentWithWhitelisting = new Experiment( |
| 1418 | + cmabExperiment.getId(), |
| 1419 | + cmabExperiment.getKey(), |
| 1420 | + cmabExperiment.getStatus(), |
| 1421 | + cmabExperiment.getLayerId(), |
| 1422 | + cmabExperiment.getAudienceIds(), |
| 1423 | + cmabExperiment.getAudienceConditions(), |
| 1424 | + cmabExperiment.getVariations(), |
| 1425 | + userIdToVariationKeyMap, |
| 1426 | + cmabExperiment.getTrafficAllocation(), |
| 1427 | + mockCmab // This makes it a CMAB experiment |
| 1428 | + ); |
| 1429 | + |
| 1430 | + // Mock CmabService.getDecision to return a different variation (should be ignored) |
| 1431 | + // Note: We don't need to mock anything since the user is whitelisted |
| 1432 | + |
| 1433 | + // Call getVariation |
| 1434 | + DecisionResponse<Variation> result = decisionService.getVariation( |
| 1435 | + experimentWithWhitelisting, |
| 1436 | + optimizely.createUserContext(whitelistedUserId, Collections.emptyMap()), |
| 1437 | + v4ProjectConfig |
| 1438 | + ); |
| 1439 | + |
| 1440 | + // Verify whitelisted variation is returned |
| 1441 | + assertEquals(whitelistedVariation, result.getResult()); |
| 1442 | + |
| 1443 | + // Verify CmabService was never called since user is whitelisted |
| 1444 | + verify(mockCmabService, never()).getDecision(any(), any(), any(), any()); |
| 1445 | + |
| 1446 | + // Verify appropriate logging |
| 1447 | + logbackVerifier.expectMessage(Level.INFO, |
| 1448 | + "User \"" + whitelistedUserId + "\" is forced in variation \"" + |
| 1449 | + whitelistedVariation.getKey() + "\"."); |
| 1450 | + } |
| 1451 | + |
| 1452 | + /** |
| 1453 | + * Verify that forced variations take precedence over CMAB service decisions |
| 1454 | + * in CMAB experiments. |
| 1455 | + */ |
| 1456 | + @Test |
| 1457 | + public void getVariationCmabExperimentForcedPrecedesCmabService() { |
| 1458 | + // Create a CMAB experiment |
| 1459 | + Experiment cmabExperiment = createMockCmabExperiment(); |
| 1460 | + Variation forcedVariation = cmabExperiment.getVariations().get(0); |
| 1461 | + Variation cmabServiceVariation = cmabExperiment.getVariations().get(1); |
| 1462 | + |
| 1463 | + // Create mock Cmab object |
| 1464 | + Cmab mockCmab = mock(Cmab.class); |
| 1465 | + |
| 1466 | + // Create experiment with CMAB config (no whitelisting) |
| 1467 | + Experiment experiment = new Experiment( |
| 1468 | + cmabExperiment.getId(), |
| 1469 | + cmabExperiment.getKey(), |
| 1470 | + cmabExperiment.getStatus(), |
| 1471 | + cmabExperiment.getLayerId(), |
| 1472 | + cmabExperiment.getAudienceIds(), |
| 1473 | + cmabExperiment.getAudienceConditions(), |
| 1474 | + cmabExperiment.getVariations(), |
| 1475 | + Collections.emptyMap(), // No whitelisting |
| 1476 | + cmabExperiment.getTrafficAllocation(), |
| 1477 | + mockCmab // This makes it a CMAB experiment |
| 1478 | + ); |
| 1479 | + |
| 1480 | + // Set forced variation for the user |
| 1481 | + decisionService.setForcedVariation(experiment, genericUserId, forcedVariation.getKey()); |
| 1482 | + |
| 1483 | + // Mock CmabService.getDecision to return a different variation (should be ignored) |
| 1484 | + CmabDecision mockCmabDecision = mock(CmabDecision.class); |
| 1485 | + when(mockCmabDecision.getVariationId()).thenReturn(cmabServiceVariation.getId()); |
| 1486 | + when(mockCmabService.getDecision(any(), any(), any(), any())) |
| 1487 | + .thenReturn(mockCmabDecision); |
| 1488 | + |
| 1489 | + // Call getVariation |
| 1490 | + DecisionResponse<Variation> result = decisionService.getVariation( |
| 1491 | + experiment, |
| 1492 | + optimizely.createUserContext(genericUserId, Collections.emptyMap()), |
| 1493 | + v4ProjectConfig |
| 1494 | + ); |
| 1495 | + |
| 1496 | + // Verify forced variation is returned (not CMAB service result) |
| 1497 | + assertEquals(forcedVariation, result.getResult()); |
| 1498 | + |
| 1499 | + // Verify CmabService was never called since user has forced variation |
| 1500 | + verify(mockCmabService, never()).getDecision(any(), any(), any(), any()); |
| 1501 | + } |
| 1502 | + |
| 1503 | + private Experiment createMockCmabExperiment() { |
| 1504 | + List<Variation> variations = Arrays.asList( |
| 1505 | + new Variation("111151", "variation_1"), |
| 1506 | + new Variation("111152", "variation_2") |
| 1507 | + ); |
| 1508 | + |
| 1509 | + List<TrafficAllocation> trafficAllocations = Arrays.asList( |
| 1510 | + new TrafficAllocation("111151", 5000), |
| 1511 | + new TrafficAllocation("111152", 10000) |
| 1512 | + ); |
| 1513 | + |
| 1514 | + // Mock CMAB configuration |
| 1515 | + Cmab mockCmab = mock(Cmab.class); |
| 1516 | + |
| 1517 | + return new Experiment( |
| 1518 | + "111150", |
| 1519 | + "cmab_experiment", |
| 1520 | + "Running", |
| 1521 | + "111150", |
| 1522 | + Collections.emptyList(), // No audience IDs |
| 1523 | + null, // No audience conditions |
| 1524 | + variations, |
| 1525 | + Collections.emptyMap(), // No whitelisting initially |
| 1526 | + trafficAllocations, |
| 1527 | + mockCmab // This makes it a CMAB experiment |
| 1528 | + ); |
| 1529 | + } |
1396 | 1530 | } |
0 commit comments