1414 * limitations under the License.
1515 */
1616import { describe , it , expect , vi , MockInstance , beforeEach } from 'vitest' ;
17- import { CMAB_FETCH_FAILED , DecisionService } from '.' ;
17+ import { CMAB_DUMMY_ENTITY_ID , CMAB_FETCH_FAILED , DecisionService } from '.' ;
1818import { getMockLogger } from '../../tests/mock/mock_logger' ;
1919import OptimizelyUserContext from '../../optimizely_user_context' ;
2020import { bucket } from '../bucketer' ;
2121import { getTestProjectConfig , getTestProjectConfigWithFeatures } from '../../tests/test_data' ;
2222import { createProjectConfig , ProjectConfig } from '../../project_config/project_config' ;
23- import { BucketerParams , Experiment , OptimizelyDecideOption , UserProfile } from '../../shared_types' ;
23+ import { BucketerParams , Experiment , OptimizelyDecideOption , UserAttributes , UserProfile } from '../../shared_types' ;
2424import { CONTROL_ATTRIBUTES , DECISION_SOURCES } from '../../utils/enums' ;
2525import { getDecisionTestDatafile } from '../../tests/decision_test_datafile' ;
2626import { Value } from '../../utils/promise/operation_value' ;
@@ -140,10 +140,18 @@ const verifyBucketCall = (
140140 variationIdMap,
141141 bucketingId,
142142 } = mockBucket . mock . calls [ call ] [ 0 ] ;
143+ let expectedTrafficAllocation = experiment . trafficAllocation ;
144+ if ( experiment . cmab ) {
145+ expectedTrafficAllocation = [ {
146+ endOfRange : experiment . cmab . trafficAllocation ,
147+ entityId : CMAB_DUMMY_ENTITY_ID ,
148+ } ] ;
149+ }
150+
143151 expect ( experimentId ) . toBe ( experiment . id ) ;
144152 expect ( experimentKey ) . toBe ( experiment . key ) ;
145153 expect ( userId ) . toBe ( user . getUserId ( ) ) ;
146- expect ( trafficAllocationConfig ) . toBe ( experiment . trafficAllocation ) ;
154+ expect ( trafficAllocationConfig ) . toEqual ( expectedTrafficAllocation ) ;
147155 expect ( experimentKeyMap ) . toBe ( projectConfig . experimentKeyMap ) ;
148156 expect ( experimentIdMap ) . toBe ( projectConfig . experimentIdMap ) ;
149157 expect ( groupIdMap ) . toBe ( projectConfig . groupIdMap ) ;
@@ -336,7 +344,7 @@ describe('DecisionService', () => {
336344 const config = createProjectConfig ( cloneDeep ( testData ) ) ;
337345 const experiment = config . experimentIdMap [ '111127' ] ;
338346
339- const attributes : any = {
347+ const attributes : UserAttributes = {
340348 $opt_experiment_bucket_map : {
341349 '111127' : {
342350 variation_id : '111129' , // ID of the 'variation' variation
@@ -674,7 +682,7 @@ describe('DecisionService', () => {
674682 const config = createProjectConfig ( cloneDeep ( testData ) ) ;
675683 const experiment = config . experimentIdMap [ '111127' ] ;
676684
677- const attributes : any = {
685+ const attributes : UserAttributes = {
678686 $opt_experiment_bucket_map : {
679687 '111127' : {
680688 variation_id : '111129' , // ID of the 'variation' variation
@@ -707,7 +715,7 @@ describe('DecisionService', () => {
707715 const config = createProjectConfig ( cloneDeep ( testData ) ) ;
708716 const experiment = config . experimentIdMap [ '111127' ] ;
709717
710- const attributes : any = {
718+ const attributes : UserAttributes = {
711719 $opt_experiment_bucket_map : {
712720 '122227' : {
713721 variation_id : '111129' , // ID of the 'variation' variation
@@ -740,7 +748,7 @@ describe('DecisionService', () => {
740748 const config = createProjectConfig ( cloneDeep ( testData ) ) ;
741749 const experiment = config . experimentIdMap [ '111127' ] ;
742750
743- const attributes : any = {
751+ const attributes : UserAttributes = {
744752 $opt_experiment_bucket_map : {
745753 '111127' : {
746754 variation_id : '111129' , // ID of the 'variation' variation
@@ -766,7 +774,7 @@ describe('DecisionService', () => {
766774 const config = createProjectConfig ( cloneDeep ( testData ) ) ;
767775 const experiment = config . experimentIdMap [ '111127' ] ;
768776
769- const attributes : any = {
777+ const attributes : UserAttributes = {
770778 $opt_experiment_bucket_map : {
771779 '111127' : {
772780 variation_id : '111129' , // ID of the 'variation' variation
@@ -1327,7 +1335,8 @@ describe('DecisionService', () => {
13271335 } ) ;
13281336 } ) ;
13291337
1330- it ( 'should get decision from the cmab service if the experiment is a cmab experiment' , async ( ) => {
1338+ it ( 'should not return variation and should not call cmab service \
1339+ for cmab experiment if user is not bucketed into it' , async ( ) => {
13311340 const { decisionService, cmabService } = getDecisionService ( ) ;
13321341 const config = createProjectConfig ( getDecisionTestDatafile ( ) ) ;
13331342
@@ -1340,6 +1349,57 @@ describe('DecisionService', () => {
13401349 } ,
13411350 } ) ;
13421351
1352+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1353+ const ruleKey = param . experimentKey ;
1354+ if ( ruleKey == 'default-rollout-key' ) {
1355+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1356+ }
1357+ return {
1358+ result : null ,
1359+ reasons : [ ] ,
1360+ }
1361+ } ) ;
1362+
1363+ const feature = config . featureKeyMap [ 'flag_1' ] ;
1364+ const value = decisionService . resolveVariationsForFeatureList ( 'async' , config , [ feature ] , user , { } ) . get ( ) ;
1365+ expect ( value ) . toBeInstanceOf ( Promise ) ;
1366+
1367+ const variation = ( await value ) [ 0 ] ;
1368+ expect ( variation . result ) . toEqual ( {
1369+ experiment : config . experimentKeyMap [ 'default-rollout-key' ] ,
1370+ variation : config . variationIdMap [ '5007' ] ,
1371+ decisionSource : DECISION_SOURCES . ROLLOUT ,
1372+ } ) ;
1373+
1374+ verifyBucketCall ( 0 , config , config . experimentKeyMap [ 'exp_3' ] , user ) ;
1375+ expect ( cmabService . getDecision ) . not . toHaveBeenCalled ( ) ;
1376+ } ) ;
1377+
1378+ it ( 'should get decision from the cmab service if the experiment is a cmab experiment \
1379+ and user is bucketed into it' , async ( ) => {
1380+ const { decisionService, cmabService } = getDecisionService ( ) ;
1381+ const config = createProjectConfig ( getDecisionTestDatafile ( ) ) ;
1382+
1383+ const user = new OptimizelyUserContext ( {
1384+ optimizely : { } as any ,
1385+ userId : 'tester' ,
1386+ attributes : {
1387+ country : 'BD' ,
1388+ age : 80 , // should satisfy audience condition for exp_3 which is cmab and not others
1389+ } ,
1390+ } ) ;
1391+
1392+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1393+ const ruleKey = param . experimentKey ;
1394+ if ( ruleKey == 'exp_3' ) {
1395+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1396+ }
1397+ return {
1398+ result : null ,
1399+ reasons : [ ] ,
1400+ }
1401+ } ) ;
1402+
13431403 cmabService . getDecision . mockResolvedValue ( {
13441404 variationId : '5003' ,
13451405 cmabUuid : 'uuid-test' ,
@@ -1357,6 +1417,8 @@ describe('DecisionService', () => {
13571417 decisionSource : DECISION_SOURCES . FEATURE_TEST ,
13581418 } ) ;
13591419
1420+ verifyBucketCall ( 0 , config , config . experimentKeyMap [ 'exp_3' ] , user ) ;
1421+
13601422 expect ( cmabService . getDecision ) . toHaveBeenCalledTimes ( 1 ) ;
13611423 expect ( cmabService . getDecision ) . toHaveBeenCalledWith (
13621424 config ,
@@ -1379,6 +1441,17 @@ describe('DecisionService', () => {
13791441 } ,
13801442 } ) ;
13811443
1444+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1445+ const ruleKey = param . experimentKey ;
1446+ if ( ruleKey == 'exp_3' ) {
1447+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1448+ }
1449+ return {
1450+ result : null ,
1451+ reasons : [ ] ,
1452+ }
1453+ } ) ;
1454+
13821455 cmabService . getDecision . mockResolvedValue ( {
13831456 variationId : '5003' ,
13841457 cmabUuid : 'uuid-test' ,
@@ -1424,6 +1497,17 @@ describe('DecisionService', () => {
14241497 } ,
14251498 } ) ;
14261499
1500+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1501+ const ruleKey = param . experimentKey ;
1502+ if ( ruleKey == 'exp_3' ) {
1503+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1504+ }
1505+ return {
1506+ result : null ,
1507+ reasons : [ ] ,
1508+ }
1509+ } ) ;
1510+
14271511 cmabService . getDecision . mockRejectedValue ( new Error ( 'I am an error' ) ) ;
14281512
14291513 const feature = config . featureKeyMap [ 'flag_1' ] ;
@@ -1474,6 +1558,17 @@ describe('DecisionService', () => {
14741558
14751559 userProfileServiceAsync ?. save . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
14761560
1561+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1562+ const ruleKey = param . experimentKey ;
1563+ if ( ruleKey == 'exp_3' ) {
1564+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1565+ }
1566+ return {
1567+ result : null ,
1568+ reasons : [ ] ,
1569+ }
1570+ } ) ;
1571+
14771572 cmabService . getDecision . mockResolvedValue ( {
14781573 variationId : '5003' ,
14791574 cmabUuid : 'uuid-test' ,
@@ -1552,6 +1647,17 @@ describe('DecisionService', () => {
15521647
15531648 userProfileServiceAsync ?. save . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
15541649
1650+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1651+ const ruleKey = param . experimentKey ;
1652+ if ( ruleKey == 'exp_3' ) {
1653+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1654+ }
1655+ return {
1656+ result : null ,
1657+ reasons : [ ] ,
1658+ }
1659+ } ) ;
1660+
15551661 cmabService . getDecision . mockResolvedValue ( {
15561662 variationId : '5003' ,
15571663 cmabUuid : 'uuid-test' ,
@@ -1605,6 +1711,16 @@ describe('DecisionService', () => {
16051711
16061712 userProfileServiceAsync ?. save . mockRejectedValue ( new Error ( 'I am an error' ) ) ;
16071713
1714+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1715+ const ruleKey = param . experimentKey ;
1716+ if ( ruleKey == 'exp_3' ) {
1717+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1718+ }
1719+ return {
1720+ result : null ,
1721+ reasons : [ ] ,
1722+ }
1723+ } ) ;
16081724
16091725 cmabService . getDecision . mockResolvedValue ( {
16101726 variationId : '5003' ,
@@ -1669,6 +1785,16 @@ describe('DecisionService', () => {
16691785 userProfileServiceAsync ?. lookup . mockResolvedValue ( null ) ;
16701786 userProfileServiceAsync ?. save . mockResolvedValue ( null ) ;
16711787
1788+ mockBucket . mockImplementation ( ( param : BucketerParams ) => {
1789+ const ruleKey = param . experimentKey ;
1790+ if ( ruleKey == 'exp_3' ) {
1791+ return { result : param . trafficAllocationConfig [ 0 ] . entityId , reasons : [ ] }
1792+ }
1793+ return {
1794+ result : null ,
1795+ reasons : [ ] ,
1796+ }
1797+ } ) ;
16721798
16731799 cmabService . getDecision . mockResolvedValue ( {
16741800 variationId : '5003' ,
0 commit comments