@@ -28,7 +28,7 @@ import {
28
28
} from '../../../src/remote-config/remote-config-api' ;
29
29
import { v4 as uuidv4 } from 'uuid' ;
30
30
import { clone } from 'lodash' ;
31
- import * as farmhash from 'farmhash-modern ' ;
31
+ import * as crypto from 'crypto ' ;
32
32
33
33
const expect = chai . expect ;
34
34
@@ -251,9 +251,8 @@ describe('ConditionEvaluator', () => {
251
251
252
252
it ( 'should use zero for undefined microPercent' , ( ) => {
253
253
// Stubs ID hasher to return a number larger than zero.
254
- const stub = sinon
255
- . stub ( farmhash , 'fingerprint64' )
256
- . returns ( 1n ) ;
254
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
255
+ stub . withArgs ( 'hex' ) . returns ( '1' ) ;
257
256
stubs . push ( stub ) ;
258
257
259
258
const condition = {
@@ -284,9 +283,8 @@ describe('ConditionEvaluator', () => {
284
283
285
284
it ( 'should use zeros for undefined microPercentRange' , ( ) => {
286
285
// Stubs ID hasher to return a number in range.
287
- const stub = sinon
288
- . stub ( farmhash , 'fingerprint64' )
289
- . returns ( 1n ) ;
286
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
287
+ stub . withArgs ( 'hex' ) . returns ( '1' ) ;
290
288
stubs . push ( stub ) ;
291
289
292
290
const condition = {
@@ -317,9 +315,8 @@ describe('ConditionEvaluator', () => {
317
315
318
316
it ( 'should use zero for undefined microPercentUpperBound' , ( ) => {
319
317
// Stubs ID hasher to return a number outside range.
320
- const stub = sinon
321
- . stub ( farmhash , 'fingerprint64' )
322
- . returns ( 1n ) ;
318
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
319
+ stub . withArgs ( 'hex' ) . returns ( '1' ) ;
323
320
stubs . push ( stub ) ;
324
321
325
322
const condition = {
@@ -353,9 +350,8 @@ describe('ConditionEvaluator', () => {
353
350
354
351
it ( 'should use zero for undefined microPercentLowerBound' , ( ) => {
355
352
// Stubs ID hasher to return a number in range.
356
- const stub = sinon
357
- . stub ( farmhash , 'fingerprint64' )
358
- . returns ( 1n ) ;
353
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
354
+ stub . withArgs ( 'hex' ) . returns ( '1' ) ;
359
355
stubs . push ( stub ) ;
360
356
361
357
const condition = {
@@ -388,9 +384,9 @@ describe('ConditionEvaluator', () => {
388
384
} ) ;
389
385
390
386
it ( 'should evaluate 9 as less or equal to 10' , ( ) => {
391
- const stub = sinon
392
- . stub ( farmhash , 'fingerprint64' )
393
- . returns ( 9n ) ;
387
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
388
+ stub . withArgs ( 'hex' ) . returns ( '9' ) ;
389
+ stubs . push ( stub ) ;
394
390
395
391
stubs . push ( stub ) ;
396
392
const condition = {
@@ -419,9 +415,9 @@ describe('ConditionEvaluator', () => {
419
415
} ) ;
420
416
421
417
it ( 'should evaluate 10 as less or equal to 10' , ( ) => {
422
- const stub = sinon
423
- . stub ( farmhash , 'fingerprint64' )
424
- . returns ( 10n ) ;
418
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
419
+ stub . withArgs ( 'hex' ) . returns ( ( 10 ) . toString ( 16 ) ) ;
420
+ stubs . push ( stub ) ;
425
421
426
422
stubs . push ( stub ) ;
427
423
const condition = {
@@ -450,9 +446,9 @@ describe('ConditionEvaluator', () => {
450
446
} ) ;
451
447
452
448
it ( 'should evaluate 11 as not less or equal to 10' , ( ) => {
453
- const stub = sinon
454
- . stub ( farmhash , 'fingerprint64' )
455
- . returns ( 11n ) ;
449
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
450
+ stub . withArgs ( 'hex' ) . returns ( ( 11 ) . toString ( 16 ) ) ;
451
+ stubs . push ( stub ) ;
456
452
457
453
stubs . push ( stub ) ;
458
454
const condition = {
@@ -481,9 +477,9 @@ describe('ConditionEvaluator', () => {
481
477
} ) ;
482
478
483
479
it ( 'should negate -11 to 11 and evaluate as not less or equal to 10' , ( ) => {
484
- const stub = sinon
485
- . stub ( farmhash , 'fingerprint64' )
486
- . returns ( - 11n ) ;
480
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
481
+ stub . withArgs ( 'hex' ) . returns ( ( 11 ) . toString ( 16 ) ) ;
482
+ stubs . push ( stub ) ;
487
483
488
484
stubs . push ( stub ) ;
489
485
const condition = {
@@ -540,9 +536,9 @@ describe('ConditionEvaluator', () => {
540
536
} ) ;
541
537
542
538
it ( 'should evaluate 11M as greater than 10M' , ( ) => {
543
- const stub = sinon
544
- . stub ( farmhash , 'fingerprint64' )
545
- . returns ( 11n ) ;
539
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
540
+ stub . withArgs ( 'hex' ) . returns ( ( 11 ) . toString ( 16 ) ) ;
541
+ stubs . push ( stub ) ;
546
542
547
543
stubs . push ( stub ) ;
548
544
const condition = {
@@ -571,9 +567,8 @@ describe('ConditionEvaluator', () => {
571
567
} ) ;
572
568
573
569
it ( 'should evaluate 9 as not greater than 10' , ( ) => {
574
- const stub = sinon
575
- . stub ( farmhash , 'fingerprint64' )
576
- . returns ( 9n ) ;
570
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
571
+ stub . withArgs ( 'hex' ) . returns ( '9' ) ;
577
572
stubs . push ( stub ) ;
578
573
579
574
const condition = {
@@ -661,9 +656,8 @@ describe('ConditionEvaluator', () => {
661
656
} ) ;
662
657
663
658
it ( 'should evaluate 10 as between 9 and 11' , ( ) => {
664
- const stub = sinon
665
- . stub ( farmhash , 'fingerprint64' )
666
- . returns ( 10n ) ;
659
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
660
+ stub . withArgs ( 'hex' ) . returns ( ( 10 ) . toString ( 16 ) ) ;
667
661
stubs . push ( stub ) ;
668
662
669
663
const condition = {
@@ -726,9 +720,8 @@ describe('ConditionEvaluator', () => {
726
720
} ) ;
727
721
728
722
it ( 'should evaluate 12 as not between 9 and 11' , ( ) => {
729
- const stub = sinon
730
- . stub ( farmhash , 'fingerprint64' )
731
- . returns ( 12n ) ;
723
+ const stub = sinon . stub ( crypto . Hash . prototype , 'digest' ) ;
724
+ stub . withArgs ( 'hex' ) . returns ( ( 12 ) . toString ( 16 ) ) ;
732
725
stubs . push ( stub ) ;
733
726
734
727
const condition = {
@@ -759,6 +752,51 @@ describe('ConditionEvaluator', () => {
759
752
expect ( actual ) . to . be . false ;
760
753
} ) ;
761
754
755
+ describe ( 'known percent condition values' , ( ) => {
756
+ // This test is useful for ensuring consistency across all places we
757
+ // evaluate percent conditions. It creates a set of 10 conditions targeting 50%
758
+ // with randomizationIds 0-9 and a constant `seed` value.
759
+ const conditionEvaluator = new ConditionEvaluator ( ) ;
760
+
761
+ const percentCondition = {
762
+ percentOperator : PercentConditionOperator . BETWEEN ,
763
+ microPercentRange : {
764
+ microPercentLowerBound : 0 ,
765
+ microPercentUpperBound : 50_000_000 // 50%
766
+ } ,
767
+ } ;
768
+
769
+ const testCases = [
770
+ { seed : '1' , randomizationId : 'one' , result : false } ,
771
+ { seed : '2' , randomizationId : 'two' , result : false } ,
772
+ { seed : '3' , randomizationId : 'three' , result : true } ,
773
+ { seed : '4' , randomizationId : 'four' , result : false } ,
774
+ { seed : '5' , randomizationId : 'five' , result : true } ,
775
+ { seed : '' , randomizationId : '😊' , result : true } ,
776
+ { seed : '' , randomizationId : '😀' , result : false } ,
777
+ { seed : 'hêl£o' , randomizationId : 'wørlÐ' , result : false } ,
778
+ { seed : 'řemøťe' , randomizationId : 'çōnfįġ' , result : true } ,
779
+ { seed : 'long' , randomizationId : '.' . repeat ( 100 ) , result : true } ,
780
+ { seed : 'very-long' , randomizationId : '.' . repeat ( 1000 ) , result : false } ,
781
+ ] ;
782
+
783
+ testCases . map ( ( { randomizationId, seed, result } ) => {
784
+
785
+ const idSummary = randomizationId . length > 25
786
+ ? `a ${ randomizationId . length } character randomizationID`
787
+ : `"${ randomizationId } "` ;
788
+
789
+ it ( `should evaluate ${ idSummary } with seed "${ seed } " to ${ result } ` , ( ) => {
790
+ const context = { randomizationId } ;
791
+ const evalResult = conditionEvaluator . evaluateConditions ( [ {
792
+ name : 'is_enabled' ,
793
+ condition : { percent : { ...percentCondition , seed } }
794
+ } ] , context ) ;
795
+ expect ( evalResult . get ( 'is_enabled' ) ) . to . equal ( result ) ;
796
+ } ) ;
797
+ } ) ;
798
+ } ) ;
799
+
762
800
// The following tests are probabilistic. They use tolerances based on
763
801
// standard deviations to balance accuracy and flakiness. Random IDs will
764
802
// hash to the target range + 3 standard deviations 99.7% of the time,
@@ -958,15 +996,15 @@ describe('ConditionEvaluator', () => {
958
996
describe ( 'STRING_CONTAINS' , ( ) => {
959
997
const testCases : CustomSignalTestCase [ ] = [
960
998
{ targets : [ 'foo' , 'biz' ] , actual : 'foobar' , outcome : true } ,
961
- { targets : [ 'foo' , 'biz' ] , actual : 'bar' , outcome : false } ,
999
+ { targets : [ 'foo' , 'biz' ] , actual : 'bar' , outcome : false } ,
962
1000
] ;
963
1001
964
1002
testCases . forEach ( runCustomSignalTestCase ( CustomSignalOperator . STRING_CONTAINS ) ) ;
965
1003
} ) ;
966
1004
967
1005
describe ( 'STRING_DOES_NOT_CONTAIN' , ( ) => {
968
1006
const testCases : CustomSignalTestCase [ ] = [
969
- { targets : [ 'foo' , 'biz' ] , actual : 'bar' , outcome : true } ,
1007
+ { targets : [ 'foo' , 'biz' ] , actual : 'bar' , outcome : true } ,
970
1008
{ targets : [ 'foo' , 'biz' ] , actual : 'foobar' , outcome : false } ,
971
1009
] ;
972
1010
@@ -1135,77 +1173,4 @@ describe('ConditionEvaluator', () => {
1135
1173
} ) ;
1136
1174
} ) ;
1137
1175
} ) ;
1138
-
1139
- describe ( 'hashSeededRandomizationId' , ( ) => {
1140
- // The Farmhash algorithm produces a 64 bit unsigned integer,
1141
- // which we convert to a signed integer for legacy compatibility.
1142
- // This has caused confusion in the past, so we explicitly
1143
- // test here.
1144
- it ( 'should leave numbers <= 2^63-1 (max signed long) as is' , function ( ) {
1145
- if ( nodeVersion . startsWith ( '14' ) ) {
1146
- this . skip ( ) ;
1147
- }
1148
-
1149
- const stub = sinon
1150
- . stub ( farmhash , 'fingerprint64' )
1151
- // 2^63-1 = 9223372036854775807.
1152
- . returns ( BigInt ( '9223372036854775807' ) ) ;
1153
- stubs . push ( stub ) ;
1154
-
1155
- const actual = ConditionEvaluator . hashSeededRandomizationId ( 'anything' ) ;
1156
-
1157
- expect ( actual ) . to . equal ( BigInt ( '9223372036854775807' ) )
1158
- } ) ;
1159
-
1160
- it ( 'should convert 2^63 to negative (min signed long) and then find the absolute value' , function ( ) {
1161
- if ( nodeVersion . startsWith ( '14' ) ) {
1162
- this . skip ( ) ;
1163
- }
1164
-
1165
- const stub = sinon
1166
- . stub ( farmhash , 'fingerprint64' )
1167
- // 2^63 = 9223372036854775808.
1168
- . returns ( BigInt ( '9223372036854775808' ) ) ;
1169
- stubs . push ( stub ) ;
1170
-
1171
- const actual = ConditionEvaluator . hashSeededRandomizationId ( 'anything' ) ;
1172
-
1173
- // 2^63 is the negation of 2^63-1
1174
- expect ( actual ) . to . equal ( BigInt ( '9223372036854775808' ) )
1175
- } ) ;
1176
-
1177
- it ( 'should convert 2^63+1 to negative and then find the absolute value' , function ( ) {
1178
- if ( nodeVersion . startsWith ( '14' ) ) {
1179
- this . skip ( ) ;
1180
- }
1181
-
1182
- const stub = sinon
1183
- . stub ( farmhash , 'fingerprint64' )
1184
- // 2^63+1 9223372036854775809.
1185
- . returns ( BigInt ( '9223372036854775809' ) ) ;
1186
- stubs . push ( stub ) ;
1187
-
1188
- const actual = ConditionEvaluator . hashSeededRandomizationId ( 'anything' ) ;
1189
-
1190
- // 2^63+1 is larger than 2^63, so the absolute value is smaller
1191
- expect ( actual ) . to . equal ( BigInt ( '9223372036854775807' ) )
1192
- } ) ;
1193
-
1194
- it ( 'should handle the value that initially caused confusion' , function ( ) {
1195
- if ( nodeVersion . startsWith ( '14' ) ) {
1196
- this . skip ( ) ;
1197
- }
1198
-
1199
- const stub = sinon
1200
- . stub ( farmhash , 'fingerprint64' )
1201
- // We were initially confused about the nature of this value ...
1202
- . returns ( BigInt ( '16081085603393958147' ) ) ;
1203
- stubs . push ( stub ) ;
1204
-
1205
- const actual = ConditionEvaluator . hashSeededRandomizationId ( 'anything' ) ;
1206
-
1207
- // ... Now we know it's the unsigned equivalent of this absolute value.
1208
- expect ( actual ) . to . equal ( BigInt ( '2365658470315593469' ) )
1209
- } ) ;
1210
- } ) ;
1211
1176
} ) ;
0 commit comments