14
14
* limitations under the License.
15
15
*/
16
16
import { 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 '.' ;
18
18
import { getMockLogger } from '../../tests/mock/mock_logger' ;
19
19
import OptimizelyUserContext from '../../optimizely_user_context' ;
20
20
import { bucket } from '../bucketer' ;
@@ -140,10 +140,18 @@ const verifyBucketCall = (
140
140
variationIdMap,
141
141
bucketingId,
142
142
} = 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
+
143
151
expect ( experimentId ) . toBe ( experiment . id ) ;
144
152
expect ( experimentKey ) . toBe ( experiment . key ) ;
145
153
expect ( userId ) . toBe ( user . getUserId ( ) ) ;
146
- expect ( trafficAllocationConfig ) . toBe ( experiment . trafficAllocation ) ;
154
+ expect ( trafficAllocationConfig ) . toEqual ( expectedTrafficAllocation ) ;
147
155
expect ( experimentKeyMap ) . toBe ( projectConfig . experimentKeyMap ) ;
148
156
expect ( experimentIdMap ) . toBe ( projectConfig . experimentIdMap ) ;
149
157
expect ( groupIdMap ) . toBe ( projectConfig . groupIdMap ) ;
@@ -1327,7 +1335,8 @@ describe('DecisionService', () => {
1327
1335
} ) ;
1328
1336
} ) ;
1329
1337
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 ( ) => {
1331
1340
const { decisionService, cmabService } = getDecisionService ( ) ;
1332
1341
const config = createProjectConfig ( getDecisionTestDatafile ( ) ) ;
1333
1342
@@ -1340,6 +1349,57 @@ describe('DecisionService', () => {
1340
1349
} ,
1341
1350
} ) ;
1342
1351
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
+
1343
1403
cmabService . getDecision . mockResolvedValue ( {
1344
1404
variationId : '5003' ,
1345
1405
cmabUuid : 'uuid-test' ,
@@ -1357,6 +1417,8 @@ describe('DecisionService', () => {
1357
1417
decisionSource : DECISION_SOURCES . FEATURE_TEST ,
1358
1418
} ) ;
1359
1419
1420
+ verifyBucketCall ( 0 , config , config . experimentKeyMap [ 'exp_3' ] , user ) ;
1421
+
1360
1422
expect ( cmabService . getDecision ) . toHaveBeenCalledTimes ( 1 ) ;
1361
1423
expect ( cmabService . getDecision ) . toHaveBeenCalledWith (
1362
1424
config ,
@@ -1379,6 +1441,17 @@ describe('DecisionService', () => {
1379
1441
} ,
1380
1442
} ) ;
1381
1443
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
+
1382
1455
cmabService . getDecision . mockResolvedValue ( {
1383
1456
variationId : '5003' ,
1384
1457
cmabUuid : 'uuid-test' ,
@@ -1424,6 +1497,17 @@ describe('DecisionService', () => {
1424
1497
} ,
1425
1498
} ) ;
1426
1499
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
+
1427
1511
cmabService . getDecision . mockRejectedValue ( new Error ( 'I am an error' ) ) ;
1428
1512
1429
1513
const feature = config . featureKeyMap [ 'flag_1' ] ;
@@ -1474,6 +1558,17 @@ describe('DecisionService', () => {
1474
1558
1475
1559
userProfileServiceAsync ?. save . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
1476
1560
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
+
1477
1572
cmabService . getDecision . mockResolvedValue ( {
1478
1573
variationId : '5003' ,
1479
1574
cmabUuid : 'uuid-test' ,
@@ -1552,6 +1647,17 @@ describe('DecisionService', () => {
1552
1647
1553
1648
userProfileServiceAsync ?. save . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
1554
1649
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
+
1555
1661
cmabService . getDecision . mockResolvedValue ( {
1556
1662
variationId : '5003' ,
1557
1663
cmabUuid : 'uuid-test' ,
@@ -1605,6 +1711,16 @@ describe('DecisionService', () => {
1605
1711
1606
1712
userProfileServiceAsync ?. save . mockRejectedValue ( new Error ( 'I am an error' ) ) ;
1607
1713
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
+ } ) ;
1608
1724
1609
1725
cmabService . getDecision . mockResolvedValue ( {
1610
1726
variationId : '5003' ,
@@ -1669,6 +1785,16 @@ describe('DecisionService', () => {
1669
1785
userProfileServiceAsync ?. lookup . mockResolvedValue ( null ) ;
1670
1786
userProfileServiceAsync ?. save . mockResolvedValue ( null ) ;
1671
1787
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
+ } ) ;
1672
1798
1673
1799
cmabService . getDecision . mockResolvedValue ( {
1674
1800
variationId : '5003' ,
0 commit comments