@@ -3,6 +3,8 @@ import { Replane, ReplaneError } from "../src/index";
33import type { ReplaneSnapshot , ConnectOptions } from "../src/index" ;
44import { MockReplaneServerController } from "./utils" ;
55
6+ const REPLANE_CLIENT_ID_KEY = "replaneClientId" ;
7+
68function sync ( ) {
79 return new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
810}
@@ -48,12 +50,14 @@ describe("Replane", () => {
4850 defaults,
4951 context,
5052 } ) ;
51- return client . connect ( {
52- sdkKey : "test-sdk-key" ,
53- baseUrl : "https://replane.my-host.com" ,
54- fetchFn : mockServer . fetchFn ,
55- ...connectOptions ,
56- } ) . then ( ( ) => client ) ;
53+ return client
54+ . connect ( {
55+ sdkKey : "test-sdk-key" ,
56+ baseUrl : "https://replane.my-host.com" ,
57+ fetchFn : mockServer . fetchFn ,
58+ ...connectOptions ,
59+ } )
60+ . then ( ( ) => client ) ;
5761 }
5862
5963 describe ( "basic config fetching" , ( ) => {
@@ -1715,7 +1719,6 @@ describe("Replane", () => {
17151719 unsubscribe3 ( ) ;
17161720 } ) ;
17171721 } ) ;
1718-
17191722 } ) ;
17201723
17211724 describe ( "client close" , ( ) => {
@@ -2125,6 +2128,147 @@ describe("Replane with snapshot", () => {
21252128 } ) ;
21262129 } ) ;
21272130
2131+ describe ( "replaneClientId auto-generation" , ( ) => {
2132+ it ( "should auto-generate replaneClientId in client context" , ( ) => {
2133+ const snapshot : ReplaneSnapshot < Record < string , unknown > > = {
2134+ configs : [
2135+ {
2136+ name : "feature" ,
2137+ value : "default" ,
2138+ overrides : [
2139+ {
2140+ name : "segmented-override" ,
2141+ conditions : [
2142+ {
2143+ operator : "segmentation" ,
2144+ property : REPLANE_CLIENT_ID_KEY ,
2145+ fromPercentage : 0 ,
2146+ toPercentage : 100 ,
2147+ seed : "test-seed" ,
2148+ } ,
2149+ ] ,
2150+ value : "segmented-value" ,
2151+ } ,
2152+ ] ,
2153+ } ,
2154+ ] ,
2155+ } ;
2156+
2157+ // Create client without providing replaneClientId
2158+ const client = new Replane ( { snapshot } ) ;
2159+
2160+ // Should match segmentation because replaneClientId is auto-generated
2161+ expect ( client . get ( "feature" ) ) . toBe ( "segmented-value" ) ;
2162+ } ) ;
2163+
2164+ it ( "should allow user to override replaneClientId" , ( ) => {
2165+ const userProvidedClientId = "user-provided-client-id" ;
2166+ const snapshot : ReplaneSnapshot < Record < string , unknown > > = {
2167+ configs : [
2168+ {
2169+ name : "feature" ,
2170+ value : "default" ,
2171+ overrides : [
2172+ {
2173+ name : "user-override" ,
2174+ conditions : [
2175+ {
2176+ operator : "equals" ,
2177+ property : REPLANE_CLIENT_ID_KEY ,
2178+ value : userProvidedClientId ,
2179+ } ,
2180+ ] ,
2181+ value : "user-override-value" ,
2182+ } ,
2183+ ] ,
2184+ } ,
2185+ ] ,
2186+ } ;
2187+
2188+ // Provide replaneClientId in context - should take precedence
2189+ const client = new Replane ( {
2190+ snapshot,
2191+ context : { [ REPLANE_CLIENT_ID_KEY ] : userProvidedClientId } ,
2192+ } ) ;
2193+
2194+ expect ( client . get ( "feature" ) ) . toBe ( "user-override-value" ) ;
2195+ } ) ;
2196+
2197+ it ( "should allow per-request context to override replaneClientId" , ( ) => {
2198+ const perRequestClientId = "per-request-client-id" ;
2199+ const snapshot : ReplaneSnapshot < Record < string , unknown > > = {
2200+ configs : [
2201+ {
2202+ name : "feature" ,
2203+ value : "default" ,
2204+ overrides : [
2205+ {
2206+ name : "per-request-override" ,
2207+ conditions : [
2208+ {
2209+ operator : "equals" ,
2210+ property : REPLANE_CLIENT_ID_KEY ,
2211+ value : perRequestClientId ,
2212+ } ,
2213+ ] ,
2214+ value : "per-request-value" ,
2215+ } ,
2216+ ] ,
2217+ } ,
2218+ ] ,
2219+ } ;
2220+
2221+ const client = new Replane ( { snapshot } ) ;
2222+
2223+ // Default should be based on auto-generated ID
2224+ expect ( client . get ( "feature" ) ) . toBe ( "default" ) ;
2225+
2226+ // Per-request context should override the auto-generated ID
2227+ expect (
2228+ client . get ( "feature" , { context : { [ REPLANE_CLIENT_ID_KEY ] : perRequestClientId } } )
2229+ ) . toBe ( "per-request-value" ) ;
2230+ } ) ;
2231+
2232+ it ( "should generate unique replaneClientId for each client instance" , ( ) => {
2233+ const snapshot : ReplaneSnapshot < Record < string , unknown > > = {
2234+ configs : [
2235+ {
2236+ name : "feature" ,
2237+ value : "default" ,
2238+ overrides : [
2239+ {
2240+ name : "50-percent-rollout" ,
2241+ conditions : [
2242+ {
2243+ operator : "segmentation" ,
2244+ property : REPLANE_CLIENT_ID_KEY ,
2245+ fromPercentage : 0 ,
2246+ toPercentage : 50 ,
2247+ seed : "test-seed" ,
2248+ } ,
2249+ ] ,
2250+ value : "rollout-value" ,
2251+ } ,
2252+ ] ,
2253+ } ,
2254+ ] ,
2255+ } ;
2256+
2257+ // Create multiple clients
2258+ const clients = Array . from ( { length : 10 } , ( ) => new Replane ( { snapshot } ) ) ;
2259+
2260+ // Each client should get a consistent result (either default or rollout-value)
2261+ // but different clients may get different results due to unique IDs
2262+ const results = clients . map ( ( client ) => client . get ( "feature" ) ) ;
2263+
2264+ // At least verify we get both values (statistically very likely with 10 clients and 50% rollout)
2265+ // This test mainly verifies that the segmentation is actually working
2266+ expect (
2267+ results . some ( ( r ) => r === "rollout-value" ) || results . some ( ( r ) => r === "default" )
2268+ ) . toBe ( true ) ;
2269+ } ) ;
2270+ } ) ;
2271+
21282272 describe ( "overrides evaluation" , ( ) => {
21292273 it ( "should evaluate equals operator" , ( ) => {
21302274 const snapshot : ReplaneSnapshot < Record < string , unknown > > = {
0 commit comments