@@ -81,4 +81,109 @@ describe('RedisClient endpoint selection', () => {
8181 const c = new RedisClient ( ) ;
8282 expect ( c . redisIndex ) . toBe ( 1 ) ;
8383 } ) ;
84+
85+ it ( 'distribution approximates weights over many samples' , async ( ) => {
86+ const endpoints = [ 'e0' , 'e1' , 'e2' ] ;
87+ const rule = { balancing : [ { index : 0 , weight : 1 } , { index : 1 , weight : 2 } , { index : 2 , weight : 7 } ] } ;
88+
89+ const mod = await resetEnvAndImport ( endpoints , rule ) ;
90+ const { RedisClient } = mod as any ;
91+
92+ const trials = 2000 ;
93+ const counts : Record < number , number > = { 0 : 0 , 1 : 0 , 2 : 0 } ;
94+ for ( let i = 0 ; i < trials ; i ++ ) {
95+ const c = new RedisClient ( ) ;
96+ counts [ c . redisIndex ] = ( counts [ c . redisIndex ] ?? 0 ) + 1 ;
97+ }
98+
99+ const totalWeight = 1 + 2 + 7 ;
100+ const expected = { 0 : 1 / totalWeight , 1 : 2 / totalWeight , 2 : 7 / totalWeight } ;
101+ for ( const idx of [ 0 , 1 , 2 ] ) {
102+ const obs = counts [ idx ] / trials ;
103+ const relErr = Math . abs ( obs - expected [ idx ] ) / Math . max ( expected [ idx ] , 1e-9 ) ;
104+ expect ( relErr ) . toBeLessThan ( 0.3 ) ;
105+ }
106+ } ) ;
107+
108+ it ( 'registerBlock excludes entries from selection' , async ( ) => {
109+ const endpoints = [ 'e0' , 'e1' , 'e2' ] ;
110+ const future = new Date ( ) ; future . setFullYear ( future . getFullYear ( ) + 10 ) ;
111+ const rule = { balancing : [ { index : 0 , weight : 10 , registerBlock : { dateBefore : future . toISOString ( ) } } , { index : 1 , weight : 1 } , { index : 2 , weight : 1 } ] } ;
112+
113+ const mod = await resetEnvAndImport ( endpoints , rule ) ;
114+ const { RedisClient } = mod as any ;
115+
116+ const trials = 300 ;
117+ for ( let i = 0 ; i < trials ; i ++ ) {
118+ const c = new RedisClient ( ) ;
119+ expect ( c . redisIndex ) . not . toBe ( 0 ) ;
120+ }
121+ } ) ;
122+
123+ it ( 'migration weight changes are applied according to dates' , async ( ) => {
124+ const endpoints = [ 'e0' , 'e1' , 'e2' ] ;
125+ const past = new Date ( ) ; past . setFullYear ( past . getFullYear ( ) - 1 ) ;
126+ const rule = { balancing : [ { index : 0 , weight : 1 , migration : { dateAfter : past . toISOString ( ) , weight : 20 } } , { index : 1 , weight : 1 } , { index : 2 , weight : 1 } ] } ;
127+
128+ const mod = await resetEnvAndImport ( endpoints , rule ) ;
129+ const { RedisClient } = mod as any ;
130+
131+ const trials = 2000 ;
132+ const counts : Record < number , number > = { 0 : 0 , 1 : 0 , 2 : 0 } ;
133+ for ( let i = 0 ; i < trials ; i ++ ) {
134+ const c = new RedisClient ( ) ;
135+ counts [ c . redisIndex ] = ( counts [ c . redisIndex ] ?? 0 ) + 1 ;
136+ }
137+
138+ // index 0 should be selected far more often due to migration weight
139+ expect ( counts [ 0 ] ) . toBeGreaterThan ( counts [ 1 ] * 5 ) ;
140+ } ) ;
141+
142+ it ( 'respects registerBlock.dateAfter (blocks when now >= dateAfter)' , async ( ) => {
143+ const endpoints = [ 'e0' , 'e1' ] ;
144+ const past = new Date ( ) ;
145+ past . setFullYear ( past . getFullYear ( ) - 1 ) ;
146+
147+ const rule = {
148+ balancing : [
149+ { index : 0 , weight : 1 , registerBlock : { dateAfter : past . toISOString ( ) } } ,
150+ { index : 1 , weight : 1 } ,
151+ ] ,
152+ } ;
153+
154+ const mod = await resetEnvAndImport ( endpoints , rule ) ;
155+ const { RedisClient } = mod as any ;
156+
157+ const trials = 200 ;
158+ for ( let i = 0 ; i < trials ; i ++ ) {
159+ const c = new RedisClient ( ) ;
160+ expect ( c . redisIndex ) . not . toBe ( 0 ) ;
161+ }
162+ } ) ;
163+
164+ it ( 'applies migration.dateBefore weight when now < dateBefore' , async ( ) => {
165+ const endpoints = [ 'e0' , 'e1' , 'e2' ] ;
166+ const future = new Date ( ) ;
167+ future . setFullYear ( future . getFullYear ( ) + 1 ) ;
168+
169+ const rule = {
170+ balancing : [
171+ { index : 0 , weight : 1 , migration : { dateBefore : future . toISOString ( ) , weight : 20 } } ,
172+ { index : 1 , weight : 1 } ,
173+ { index : 2 , weight : 1 } ,
174+ ] ,
175+ } ;
176+
177+ const mod = await resetEnvAndImport ( endpoints , rule ) ;
178+ const { RedisClient } = mod as any ;
179+
180+ const trials = 2000 ;
181+ const counts : Record < number , number > = { 0 : 0 , 1 : 0 , 2 : 0 } ;
182+ for ( let i = 0 ; i < trials ; i ++ ) {
183+ const c = new RedisClient ( ) ;
184+ counts [ c . redisIndex ] = ( counts [ c . redisIndex ] ?? 0 ) + 1 ;
185+ }
186+
187+ expect ( counts [ 0 ] ) . toBeGreaterThan ( counts [ 1 ] * 5 ) ;
188+ } ) ;
84189} ) ;
0 commit comments