@@ -295,6 +295,146 @@ func (s *FeatureExperimentServiceTestSuite) TestGetDecisionWithCmabError() {
295295 s .mockExperimentService .AssertExpectations (s .T ())
296296}
297297
298+ func (s * FeatureExperimentServiceTestSuite ) TestGetDecisionWithLocalHoldout () {
299+ testUserContext := entities.UserContext {
300+ ID : "test_user_1" ,
301+ }
302+
303+ // Create holdout variation
304+ holdoutVariation := entities.Variation {
305+ ID : "holdout_var_1" ,
306+ Key : "holdout_off" ,
307+ }
308+
309+ // Create local holdout targeting the first experiment
310+ localHoldout := entities.Holdout {
311+ ID : "local_holdout_1" ,
312+ Key : "local_holdout_key" ,
313+ Status : entities .HoldoutStatusRunning ,
314+ Variations : map [string ]entities.Variation {
315+ "holdout_var_1" : holdoutVariation ,
316+ },
317+ TrafficAllocation : []entities.Range {
318+ {EntityID : "holdout_var_1" , EndOfRange : 10000 },
319+ },
320+ IncludedRules : []string {testExp1113 .ID }, // Target first experiment
321+ }
322+
323+ // Mock GetHoldoutsForRule to return our local holdout
324+ s .mockConfig .On ("GetHoldoutsForRule" , testExp1113 .ID ).Return ([]entities.Holdout {localHoldout })
325+ s .mockConfig .On ("GetAudienceMap" ).Return (map [string ]entities.Audience {})
326+
327+ featureExperimentService := & FeatureExperimentService {
328+ compositeExperimentService : s .mockExperimentService ,
329+ logger : logging .GetLogger ("sdkKey" , "FeatureExperimentService" ),
330+ }
331+
332+ decision , _ , err := featureExperimentService .GetDecision (s .testFeatureDecisionContext , testUserContext , s .options )
333+ s .NoError (err )
334+ s .Equal (Holdout , decision .Source )
335+ s .Equal (& holdoutVariation , decision .Variation )
336+ s .mockConfig .AssertExpectations (s .T ())
337+ }
338+
339+ func (s * FeatureExperimentServiceTestSuite ) TestGetDecisionWithLocalHoldoutNotBucketed () {
340+ testUserContext := entities.UserContext {
341+ ID : "test_user_1" ,
342+ }
343+
344+ // Create holdout variation
345+ holdoutVariation := entities.Variation {
346+ ID : "holdout_var_1" ,
347+ Key : "holdout_off" ,
348+ }
349+
350+ // Create local holdout with 0% traffic
351+ localHoldout := entities.Holdout {
352+ ID : "local_holdout_1" ,
353+ Key : "local_holdout_key" ,
354+ Status : entities .HoldoutStatusRunning ,
355+ Variations : map [string ]entities.Variation {
356+ "holdout_var_1" : holdoutVariation ,
357+ },
358+ TrafficAllocation : []entities.Range {
359+ {EntityID : "holdout_var_1" , EndOfRange : 0 }, // 0% traffic
360+ },
361+ IncludedRules : []string {testExp1113 .ID },
362+ }
363+
364+ s .mockConfig .On ("GetHoldoutsForRule" , testExp1113 .ID ).Return ([]entities.Holdout {localHoldout })
365+ s .mockConfig .On ("GetAudienceMap" ).Return (map [string ]entities.Audience {})
366+
367+ // User should fall through to normal experiment evaluation
368+ expectedVariation := testExp1113 .Variations ["2223" ]
369+ returnExperimentDecision := ExperimentDecision {
370+ Variation : & expectedVariation ,
371+ }
372+ testExperimentDecisionContext := ExperimentDecisionContext {
373+ Experiment : & testExp1113 ,
374+ ProjectConfig : s .mockConfig ,
375+ }
376+ s .mockExperimentService .On ("GetDecision" , testExperimentDecisionContext , testUserContext , s .options ).Return (returnExperimentDecision , s .reasons , nil )
377+
378+ featureExperimentService := & FeatureExperimentService {
379+ compositeExperimentService : s .mockExperimentService ,
380+ logger : logging .GetLogger ("sdkKey" , "FeatureExperimentService" ),
381+ }
382+
383+ decision , _ , err := featureExperimentService .GetDecision (s .testFeatureDecisionContext , testUserContext , s .options )
384+ s .NoError (err )
385+ s .Equal (FeatureTest , decision .Source ) // Should be from feature test, not holdout
386+ s .mockExperimentService .AssertExpectations (s .T ())
387+ s .mockConfig .AssertExpectations (s .T ())
388+ }
389+
390+ func (s * FeatureExperimentServiceTestSuite ) TestGetDecisionWithLocalHoldoutNotRunning () {
391+ testUserContext := entities.UserContext {
392+ ID : "test_user_1" ,
393+ }
394+
395+ // Create holdout variation
396+ holdoutVariation := entities.Variation {
397+ ID : "holdout_var_1" ,
398+ Key : "holdout_off" ,
399+ }
400+
401+ // Create local holdout that's not running
402+ localHoldout := entities.Holdout {
403+ ID : "local_holdout_1" ,
404+ Key : "local_holdout_key" ,
405+ Status : "Draft" , // Not running
406+ Variations : map [string ]entities.Variation {
407+ "holdout_var_1" : holdoutVariation ,
408+ },
409+ TrafficAllocation : []entities.Range {{EntityID : "holdout_var_1" , EndOfRange : 10000 }},
410+ IncludedRules : []string {testExp1113 .ID },
411+ }
412+
413+ s .mockConfig .On ("GetHoldoutsForRule" , testExp1113 .ID ).Return ([]entities.Holdout {localHoldout })
414+
415+ // User should fall through to normal experiment evaluation
416+ expectedVariation := testExp1113 .Variations ["2223" ]
417+ returnExperimentDecision := ExperimentDecision {
418+ Variation : & expectedVariation ,
419+ }
420+ testExperimentDecisionContext := ExperimentDecisionContext {
421+ Experiment : & testExp1113 ,
422+ ProjectConfig : s .mockConfig ,
423+ }
424+ s .mockExperimentService .On ("GetDecision" , testExperimentDecisionContext , testUserContext , s .options ).Return (returnExperimentDecision , s .reasons , nil )
425+
426+ featureExperimentService := & FeatureExperimentService {
427+ compositeExperimentService : s .mockExperimentService ,
428+ logger : logging .GetLogger ("sdkKey" , "FeatureExperimentService" ),
429+ }
430+
431+ decision , _ , err := featureExperimentService .GetDecision (s .testFeatureDecisionContext , testUserContext , s .options )
432+ s .NoError (err )
433+ s .Equal (FeatureTest , decision .Source )
434+ s .mockExperimentService .AssertExpectations (s .T ())
435+ s .mockConfig .AssertExpectations (s .T ())
436+ }
437+
298438func TestFeatureExperimentServiceTestSuite (t * testing.T ) {
299439 suite .Run (t , new (FeatureExperimentServiceTestSuite ))
300440}
0 commit comments