@@ -1490,5 +1490,252 @@ public void TestCmabFieldPopulation()
14901490
14911491 Assert . IsNull ( experimentWithoutCmab . Cmab ) ;
14921492 }
1493+
1494+ #region Feature Rollout Tests
1495+
1496+ private string BuildFeatureRolloutDatafile (
1497+ string experimentType = "fr" ,
1498+ string rolloutId = "rollout_1" ,
1499+ bool includeRollout = true ,
1500+ bool includeRolloutVariations = true )
1501+ {
1502+ var rolloutExperiments = new List < object > ( ) ;
1503+ if ( includeRollout && includeRolloutVariations )
1504+ {
1505+ rolloutExperiments . Add ( new
1506+ {
1507+ id = "rollout_rule_1" ,
1508+ key = "rollout_rule_1_key" ,
1509+ layerId = "rollout_layer_1" ,
1510+ status = "Running" ,
1511+ variations = new [ ]
1512+ {
1513+ new { id = "var_rr1" , key = "variation_rr1" , featureEnabled = true } ,
1514+ } ,
1515+ trafficAllocation = new [ ]
1516+ {
1517+ new { entityId = "var_rr1" , endOfRange = 10000 } ,
1518+ } ,
1519+ audienceIds = new string [ 0 ] ,
1520+ forcedVariations = new Dictionary < string , string > ( ) ,
1521+ } ) ;
1522+ rolloutExperiments . Add ( new
1523+ {
1524+ id = "rollout_everyone_else" ,
1525+ key = "rollout_everyone_else_key" ,
1526+ layerId = "rollout_layer_ee" ,
1527+ status = "Running" ,
1528+ variations = new [ ]
1529+ {
1530+ new
1531+ {
1532+ id = "var_ee" , key = "variation_everyone_else" ,
1533+ featureEnabled = false ,
1534+ } ,
1535+ } ,
1536+ trafficAllocation = new [ ]
1537+ {
1538+ new { entityId = "var_ee" , endOfRange = 10000 } ,
1539+ } ,
1540+ audienceIds = new string [ 0 ] ,
1541+ forcedVariations = new Dictionary < string , string > ( ) ,
1542+ } ) ;
1543+ }
1544+
1545+ var rollouts = new List < object > ( ) ;
1546+ if ( includeRollout )
1547+ {
1548+ rollouts . Add ( new
1549+ {
1550+ id = "rollout_1" ,
1551+ experiments = rolloutExperiments ,
1552+ } ) ;
1553+ }
1554+
1555+ var experiments = new List < object >
1556+ {
1557+ new
1558+ {
1559+ id = "exp_ab" ,
1560+ key = "ab_experiment" ,
1561+ layerId = "layer_ab" ,
1562+ status = "Running" ,
1563+ variations = new [ ]
1564+ {
1565+ new { id = "var_ab_1" , key = "variation_ab_1" , featureEnabled = true } ,
1566+ } ,
1567+ trafficAllocation = new [ ]
1568+ {
1569+ new { entityId = "var_ab_1" , endOfRange = 10000 } ,
1570+ } ,
1571+ audienceIds = new string [ 0 ] ,
1572+ forcedVariations = new Dictionary < string , string > ( ) ,
1573+ } ,
1574+ } ;
1575+
1576+ if ( experimentType != null )
1577+ {
1578+ experiments . Add ( new
1579+ {
1580+ id = "exp_rollout" ,
1581+ key = "rollout_experiment" ,
1582+ layerId = "layer_rollout" ,
1583+ status = "Running" ,
1584+ type = experimentType ,
1585+ variations = new [ ]
1586+ {
1587+ new
1588+ {
1589+ id = "var_rollout_1" , key = "variation_rollout_1" ,
1590+ featureEnabled = true ,
1591+ } ,
1592+ } ,
1593+ trafficAllocation = new [ ]
1594+ {
1595+ new { entityId = "var_rollout_1" , endOfRange = 5000 } ,
1596+ } ,
1597+ audienceIds = new string [ 0 ] ,
1598+ forcedVariations = new Dictionary < string , string > ( ) ,
1599+ } ) ;
1600+ }
1601+
1602+ var datafile = new
1603+ {
1604+ version = "4" ,
1605+ revision = "1" ,
1606+ projectId = "rollout_test" ,
1607+ accountId = "12345" ,
1608+ sdkKey = "test-key" ,
1609+ environmentKey = "production" ,
1610+ events = new object [ 0 ] ,
1611+ audiences = new object [ 0 ] ,
1612+ typedAudiences = new object [ 0 ] ,
1613+ attributes = new object [ 0 ] ,
1614+ groups = new object [ 0 ] ,
1615+ integrations = new object [ 0 ] ,
1616+ holdouts = new object [ 0 ] ,
1617+ experiments = experiments ,
1618+ rollouts = rollouts ,
1619+ featureFlags = new [ ]
1620+ {
1621+ new
1622+ {
1623+ id = "feature_1" ,
1624+ key = "feature_rollout_flag" ,
1625+ rolloutId = rolloutId ,
1626+ experimentIds = experimentType != null
1627+ ? new [ ] { "exp_ab" , "exp_rollout" }
1628+ : new [ ] { "exp_ab" } ,
1629+ variables = new object [ 0 ] ,
1630+ } ,
1631+ } ,
1632+ } ;
1633+
1634+ return JsonConvert . SerializeObject ( datafile ) ;
1635+ }
1636+
1637+ [ Test ]
1638+ public void TestBackwardCompatibilityExperimentsWithoutTypeField ( )
1639+ {
1640+ var datafile = BuildFeatureRolloutDatafile ( ) ;
1641+ var config = DatafileProjectConfig . Create ( datafile , LoggerMock . Object ,
1642+ ErrorHandlerMock . Object ) ;
1643+
1644+ var abExperiment = config . GetExperimentFromKey ( "ab_experiment" ) ;
1645+ Assert . IsNull ( abExperiment . Type ) ;
1646+ }
1647+
1648+ [ Test ]
1649+ public void TestFeatureRolloutInjectionAddsEveryoneElseVariation ( )
1650+ {
1651+ var datafile = BuildFeatureRolloutDatafile ( ) ;
1652+ var config = DatafileProjectConfig . Create ( datafile , LoggerMock . Object ,
1653+ ErrorHandlerMock . Object ) ;
1654+
1655+ var rolloutExperiment = config . GetExperimentFromKey ( "rollout_experiment" ) ;
1656+
1657+ // Should have 2 variations: original + injected everyone else
1658+ Assert . AreEqual ( 2 , rolloutExperiment . Variations . Length ) ;
1659+ Assert . AreEqual ( "var_ee" , rolloutExperiment . Variations [ 1 ] . Id ) ;
1660+ Assert . AreEqual ( "variation_everyone_else" , rolloutExperiment . Variations [ 1 ] . Key ) ;
1661+
1662+ // Should have injected traffic allocation entry
1663+ var lastAllocation =
1664+ rolloutExperiment . TrafficAllocation [ rolloutExperiment . TrafficAllocation . Length - 1 ] ;
1665+ Assert . AreEqual ( "var_ee" , lastAllocation . EntityId ) ;
1666+ Assert . AreEqual ( 10000 , lastAllocation . EndOfRange ) ;
1667+ }
1668+
1669+ [ Test ]
1670+ public void TestVariationMapsUpdatedWithInjectedVariation ( )
1671+ {
1672+ var datafile = BuildFeatureRolloutDatafile ( ) ;
1673+ var config = DatafileProjectConfig . Create ( datafile , LoggerMock . Object ,
1674+ ErrorHandlerMock . Object ) ;
1675+
1676+ var rolloutExperiment = config . GetExperimentFromKey ( "rollout_experiment" ) ;
1677+
1678+ // VariationKeyToVariationMap should contain the injected variation
1679+ Assert . IsTrue (
1680+ rolloutExperiment . VariationKeyToVariationMap . ContainsKey (
1681+ "variation_everyone_else" ) ) ;
1682+ Assert . AreEqual ( "var_ee" ,
1683+ rolloutExperiment . VariationKeyToVariationMap [ "variation_everyone_else" ] . Id ) ;
1684+
1685+ // VariationIdToVariationMap should contain the injected variation
1686+ Assert . IsTrue (
1687+ rolloutExperiment . VariationIdToVariationMap . ContainsKey ( "var_ee" ) ) ;
1688+
1689+ // Global variation maps should also contain the injected variation
1690+ var variationByKey = config . GetVariationFromKey ( "rollout_experiment" ,
1691+ "variation_everyone_else" ) ;
1692+ Assert . AreEqual ( "var_ee" , variationByKey . Id ) ;
1693+
1694+ var variationById =
1695+ config . GetVariationFromId ( "rollout_experiment" , "var_ee" ) ;
1696+ Assert . AreEqual ( "variation_everyone_else" , variationById . Key ) ;
1697+ }
1698+
1699+ [ Test ]
1700+ public void TestNonRolloutExperimentsNotModified ( )
1701+ {
1702+ var datafile = BuildFeatureRolloutDatafile ( ) ;
1703+ var config = DatafileProjectConfig . Create ( datafile , LoggerMock . Object ,
1704+ ErrorHandlerMock . Object ) ;
1705+
1706+ var abExperiment = config . GetExperimentFromKey ( "ab_experiment" ) ;
1707+
1708+ // A/B experiment should still have only 1 variation
1709+ Assert . AreEqual ( 1 , abExperiment . Variations . Length ) ;
1710+ Assert . AreEqual ( "var_ab_1" , abExperiment . Variations [ 0 ] . Id ) ;
1711+ Assert . AreEqual ( 1 , abExperiment . TrafficAllocation . Length ) ;
1712+ }
1713+
1714+ [ Test ]
1715+ public void TestNoRolloutEdgeCaseSilentSkip ( )
1716+ {
1717+ var datafile = BuildFeatureRolloutDatafile ( rolloutId : "" ) ;
1718+ var config = DatafileProjectConfig . Create ( datafile , LoggerMock . Object ,
1719+ ErrorHandlerMock . Object ) ;
1720+
1721+ var rolloutExperiment = config . GetExperimentFromKey ( "rollout_experiment" ) ;
1722+
1723+ // Should still have only 1 variation (no injection)
1724+ Assert . AreEqual ( 1 , rolloutExperiment . Variations . Length ) ;
1725+ Assert . AreEqual ( "var_rollout_1" , rolloutExperiment . Variations [ 0 ] . Id ) ;
1726+ }
1727+
1728+ [ Test ]
1729+ public void TestTypeFieldParsedCorrectly ( )
1730+ {
1731+ var datafile = BuildFeatureRolloutDatafile ( ) ;
1732+ var config = DatafileProjectConfig . Create ( datafile , LoggerMock . Object ,
1733+ ErrorHandlerMock . Object ) ;
1734+
1735+ var rolloutExperiment = config . GetExperimentFromKey ( "rollout_experiment" ) ;
1736+ Assert . AreEqual ( "fr" , rolloutExperiment . Type ) ;
1737+ }
1738+
1739+ #endregion
14931740 }
14941741}
0 commit comments