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