1+ import { v4 as uuidV4 } from 'uuid' ;
2+
13import {
24 generateDeterministicRandomNumber ,
35 isFeatureFlagWithScopeValue ,
46} from './user-segmentation-utils' ;
57
6- const MOCK_METRICS_IDS = [
7- '123e4567-e89b-4456-a456-426614174000' ,
8- '987fcdeb-51a2-4c4b-9876-543210fedcba' ,
9- 'a1b2c3d4-e5f6-4890-abcd-ef1234567890' ,
10- 'f9e8d7c6-b5a4-4210-9876-543210fedcba' ,
11- ] ;
8+ const MOCK_METRICS_IDS = {
9+ MOBILE_VALID : '123e4567-e89b-4456-a456-426614174000' ,
10+ EXTENSION_VALID :
11+ '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420' ,
12+ MOBILE_MIN : '00000000-0000-4000-8000-000000000000' ,
13+ MOBILE_MAX : 'ffffffff-ffff-4fff-bfff-ffffffffffff' ,
14+ EXTENSION_MIN : `0x${ '0' . repeat ( 64 ) } ` ,
15+ EXTENSION_MAX : `0x${ 'f' . repeat ( 64 ) } ` ,
16+ } ;
1217
1318const MOCK_FEATURE_FLAGS = {
1419 VALID : {
@@ -28,26 +33,104 @@ const MOCK_FEATURE_FLAGS = {
2833
2934describe ( 'user-segmentation-utils' , ( ) => {
3035 describe ( 'generateDeterministicRandomNumber' , ( ) => {
31- it ( 'generates consistent numbers for the same input' , ( ) => {
32- const result1 = generateDeterministicRandomNumber ( MOCK_METRICS_IDS [ 0 ] ) ;
33- const result2 = generateDeterministicRandomNumber ( MOCK_METRICS_IDS [ 0 ] ) ;
36+ describe ( 'Mobile client new implementation (uuidv4)' , ( ) => {
37+ it ( 'generates consistent results for same uuidv4' , ( ) => {
38+ const result1 = generateDeterministicRandomNumber (
39+ MOCK_METRICS_IDS . MOBILE_VALID ,
40+ ) ;
41+ const result2 = generateDeterministicRandomNumber (
42+ MOCK_METRICS_IDS . MOBILE_VALID ,
43+ ) ;
44+ expect ( result1 ) . toBe ( result2 ) ;
45+ } ) ;
3446
35- expect ( result1 ) . toBe ( result2 ) ;
36- } ) ;
47+ it ( 'handles minimum uuidv4 value' , ( ) => {
48+ const result = generateDeterministicRandomNumber (
49+ MOCK_METRICS_IDS . MOBILE_MIN ,
50+ ) ;
51+ expect ( result ) . toBe ( 0 ) ;
52+ } ) ;
3753
38- it ( 'generates numbers between 0 and 1' , ( ) => {
39- MOCK_METRICS_IDS . forEach ( ( id ) => {
40- const result = generateDeterministicRandomNumber ( id ) ;
54+ it ( 'handles maximum uuidv4 value' , ( ) => {
55+ const result = generateDeterministicRandomNumber (
56+ MOCK_METRICS_IDS . MOBILE_MAX ,
57+ ) ;
58+ // For practical purposes, 0.999999 is functionally equivalent to 1 in this context
59+ // the small deviation from exactly 1.0 is a limitation of floating-point arithmetic, not a bug in the logic.
60+ expect ( result ) . toBeCloseTo ( 1 , 5 ) ;
61+ } ) ;
62+
63+ it ( 'results a random number between 0 and 1' , ( ) => {
64+ const result = generateDeterministicRandomNumber (
65+ MOCK_METRICS_IDS . MOBILE_VALID ,
66+ ) ;
4167 expect ( result ) . toBeGreaterThanOrEqual ( 0 ) ;
4268 expect ( result ) . toBeLessThanOrEqual ( 1 ) ;
4369 } ) ;
4470 } ) ;
4571
46- it ( 'generates different numbers for different inputs' , ( ) => {
47- const result1 = generateDeterministicRandomNumber ( MOCK_METRICS_IDS [ 0 ] ) ;
48- const result2 = generateDeterministicRandomNumber ( MOCK_METRICS_IDS [ 1 ] ) ;
72+ describe ( 'Mobile client old implementation and Extension client (hex string)' , ( ) => {
73+ it ( 'generates consistent results for same hex' , ( ) => {
74+ const result1 = generateDeterministicRandomNumber (
75+ MOCK_METRICS_IDS . EXTENSION_VALID ,
76+ ) ;
77+ const result2 = generateDeterministicRandomNumber (
78+ MOCK_METRICS_IDS . EXTENSION_VALID ,
79+ ) ;
80+ expect ( result1 ) . toBe ( result2 ) ;
81+ } ) ;
82+
83+ it ( 'handles minimum hex value' , ( ) => {
84+ const result = generateDeterministicRandomNumber (
85+ MOCK_METRICS_IDS . EXTENSION_MIN ,
86+ ) ;
87+ expect ( result ) . toBe ( 0 ) ;
88+ } ) ;
89+
90+ it ( 'handles maximum hex value' , ( ) => {
91+ const result = generateDeterministicRandomNumber (
92+ MOCK_METRICS_IDS . EXTENSION_MAX ,
93+ ) ;
94+ expect ( result ) . toBe ( 1 ) ;
95+ } ) ;
96+ } ) ;
97+
98+ describe ( 'Distribution validation' , ( ) => {
99+ it ( 'produces uniform distribution across 1000 samples' , ( ) => {
100+ const samples = 1000 ;
101+ const buckets = 10 ;
102+ const tolerance = 0.3 ;
103+ const distribution = new Array ( buckets ) . fill ( 0 ) ;
49104
50- expect ( result1 ) . not . toBe ( result2 ) ;
105+ // Generate samples using valid UUIDs
106+ Array . from ( { length : samples } ) . forEach ( ( ) => {
107+ const uuid = uuidV4 ( ) ;
108+ const value = generateDeterministicRandomNumber ( uuid ) ;
109+ const bucketIndex = Math . floor ( value * buckets ) ;
110+ // Handle edge case where value === 1
111+ distribution [
112+ bucketIndex === buckets ? buckets - 1 : bucketIndex
113+ ] += 1 ;
114+ } ) ;
115+
116+ // Check distribution
117+ const expectedPerBucket = samples / buckets ;
118+ const allowedDeviation = expectedPerBucket * tolerance ;
119+
120+ distribution . forEach ( ( count , index ) => {
121+ const minExpected = Math . floor ( expectedPerBucket - allowedDeviation ) ;
122+ const maxExpected = Math . ceil ( expectedPerBucket + allowedDeviation ) ;
123+
124+ expect ( count ) . toBeGreaterThanOrEqual (
125+ minExpected ,
126+ `Bucket ${ index } has too few items (${ count } < ${ minExpected } ) with ${ samples } samples` ,
127+ ) ;
128+ expect ( count ) . toBeLessThanOrEqual (
129+ maxExpected ,
130+ `Bucket ${ index } has too many items (${ count } > ${ maxExpected } ) with ${ samples } samples` ,
131+ ) ;
132+ } ) ;
133+ } ) ;
51134 } ) ;
52135 } ) ;
53136
0 commit comments