@@ -14,55 +14,99 @@ def initialize(log, config)
1414
1515 opts [ :logger ] = log
1616
17- if config [ :streaming ]
17+ data_system_config = config [ :dataSystem ]
18+ if data_system_config
19+ data_system = LaunchDarkly ::DataSystem . custom
20+
21+ # For FDv2, persistent store config is nested inside dataSystem.store
22+ persistent_store_config = data_system_config . dig ( :store , :persistentDataStore )
23+ if persistent_store_config
24+ store , store_mode = build_persistent_store ( persistent_store_config )
25+ data_system . data_store ( store , store_mode )
26+ end
27+
28+ init_configs = data_system_config [ :initializers ]
29+ if init_configs
30+ initializers = [ ]
31+ init_configs . each do |init_config |
32+ polling = init_config [ :polling ]
33+ next unless polling
34+
35+ opts [ :base_uri ] = polling [ :baseUri ] if polling [ :baseUri ]
36+ set_optional_time_prop ( polling , :pollIntervalMs , opts , :poll_interval )
37+ initializers << LaunchDarkly ::DataSystem . polling_ds_builder
38+ end
39+ data_system . initializers ( initializers )
40+ end
41+
42+ sync_config = data_system_config [ :synchronizers ]
43+ if sync_config
44+ primary = sync_config [ :primary ]
45+ secondary = sync_config [ :secondary ]
46+
47+ primary_builder = nil
48+ secondary_builder = nil
49+
50+ if primary
51+ streaming = primary [ :streaming ]
52+ if streaming
53+ opts [ :stream_uri ] = streaming [ :baseUri ] if streaming [ :baseUri ]
54+ set_optional_time_prop ( streaming , :initialRetryDelayMs , opts , :initial_reconnect_delay )
55+ primary_builder = LaunchDarkly ::DataSystem . streaming_ds_builder
56+ elsif primary [ :polling ]
57+ polling = primary [ :polling ]
58+ opts [ :base_uri ] = polling [ :baseUri ] if polling [ :baseUri ]
59+ set_optional_time_prop ( polling , :pollIntervalMs , opts , :poll_interval )
60+ primary_builder = LaunchDarkly ::DataSystem . polling_ds_builder
61+ end
62+ end
63+
64+ if secondary
65+ streaming = secondary [ :streaming ]
66+ if streaming
67+ opts [ :stream_uri ] = streaming [ :baseUri ] if streaming [ :baseUri ]
68+ set_optional_time_prop ( streaming , :initialRetryDelayMs , opts , :initial_reconnect_delay )
69+ secondary_builder = LaunchDarkly ::DataSystem . streaming_ds_builder
70+ elsif secondary [ :polling ]
71+ polling = secondary [ :polling ]
72+ opts [ :base_uri ] = polling [ :baseUri ] if polling [ :baseUri ]
73+ set_optional_time_prop ( polling , :pollIntervalMs , opts , :poll_interval )
74+ secondary_builder = LaunchDarkly ::DataSystem . polling_ds_builder
75+ end
76+ end
77+
78+ data_system . synchronizers ( primary_builder , secondary_builder ) if primary_builder
79+
80+ if primary_builder || secondary_builder
81+ fallback_builder = LaunchDarkly ::DataSystem . fdv1_fallback_ds_builder
82+ data_system . fdv1_compatible_synchronizer ( fallback_builder )
83+ end
84+ end
85+
86+ if data_system_config [ :payloadFilter ]
87+ opts [ :payload_filter_key ] = data_system_config [ :payloadFilter ]
88+ end
89+
90+ opts [ :data_system_config ] = data_system . build
91+ elsif config [ :streaming ]
1892 streaming = config [ :streaming ]
1993 opts [ :stream_uri ] = streaming [ :baseUri ] unless streaming [ :baseUri ] . nil?
2094 opts [ :payload_filter_key ] = streaming [ :filter ] unless streaming [ :filter ] . nil?
21- opts [ :initial_reconnect_delay ] = streaming [ :initialRetryDelayMs ] / 1_000.0 unless streaming [ :initialRetryDelayMs ] . nil?
95+ set_optional_time_prop ( streaming , :initialRetryDelayMs , opts , :initial_reconnect_delay )
2296 elsif config [ :polling ]
2397 polling = config [ :polling ]
2498 opts [ :stream ] = false
2599 opts [ :base_uri ] = polling [ :baseUri ] unless polling [ :baseUri ] . nil?
26100 opts [ :payload_filter_key ] = polling [ :filter ] unless polling [ :filter ] . nil?
27- opts [ :poll_interval ] = polling [ :pollIntervalMs ] / 1_000.0 unless polling [ :pollIntervalMs ] . nil?
101+ set_optional_time_prop ( polling , :pollIntervalMs , opts , :poll_interval )
28102 else
29103 opts [ :use_ldd ] = true
30104 end
31105
32- if config [ :persistentDataStore ]
33- store_config = { }
34- store_config [ :prefix ] = config [ :persistentDataStore ] [ :store ] [ :prefix ] if config [ :persistentDataStore ] [ :store ] [ :prefix ]
35-
36- case config [ :persistentDataStore ] [ :cache ] [ :mode ]
37- when 'off'
38- store_config [ :expiration ] = 0
39- when 'infinite'
40- # NOTE: We don't actually support infinite cache mode, so we'll just set it to nil for now. This uses a default
41- # 15 second expiration time in the SDK, which is long enough to pass any test.
42- store_config [ :expiration ] = nil
43- when 'ttl'
44- store_config [ :expiration ] = config [ :persistentDataStore ] [ :cache ] [ :ttl ]
45- end
46-
47- case config [ :persistentDataStore ] [ :store ] [ :type ]
48- when 'redis'
49- store_config [ :redis_url ] = config [ :persistentDataStore ] [ :store ] [ :dsn ]
50- store = LaunchDarkly ::Integrations ::Redis . new_feature_store ( store_config )
51- opts [ :feature_store ] = store
52- when 'consul'
53- store_config [ :url ] = config [ :persistentDataStore ] [ :store ] [ :url ]
54- store = LaunchDarkly ::Integrations ::Consul . new_feature_store ( store_config )
55- opts [ :feature_store ] = store
56- when 'dynamodb'
57- client = Aws ::DynamoDB ::Client . new (
58- region : 'us-east-1' ,
59- credentials : Aws ::Credentials . new ( 'dummy' , 'dummy' , 'dummy' ) ,
60- endpoint : config [ :persistentDataStore ] [ :store ] [ :dsn ]
61- )
62- store_config [ :existing_client ] = client
63- store = LaunchDarkly ::Integrations ::DynamoDB . new_feature_store ( 'sdk-contract-tests' , store_config )
64- opts [ :feature_store ] = store
65- end
106+ # Configure persistent data store for legacy (non-dataSystem) configurations
107+ if !data_system_config && config [ :persistentDataStore ]
108+ store , _store_mode = build_persistent_store ( config [ :persistentDataStore ] )
109+ opts [ :feature_store ] = store
66110 end
67111
68112 if config [ :events ]
@@ -72,7 +116,7 @@ def initialize(log, config)
72116 opts [ :diagnostic_opt_out ] = !events [ :enableDiagnostics ]
73117 opts [ :all_attributes_private ] = !!events [ :allAttributesPrivate ]
74118 opts [ :private_attributes ] = events [ :globalPrivateAttributes ]
75- opts [ :flush_interval ] = ( events [ :flushIntervalMs ] / 1_000 ) unless events [ :flushIntervalMs ] . nil?
119+ set_optional_time_prop ( events , :flushIntervalMs , opts , :flush_interval )
76120 opts [ :omit_anonymous_contexts ] = !!events [ :omitAnonymousContexts ]
77121 opts [ :compress_events ] = !!events [ :enableGzip ]
78122 else
@@ -81,19 +125,14 @@ def initialize(log, config)
81125
82126 if config [ :bigSegments ]
83127 big_segments = config [ :bigSegments ]
128+ big_config = { store : BigSegmentStoreFixture . new ( big_segments [ :callbackUri ] ) }
84129
85- store = BigSegmentStoreFixture . new ( config [ :bigSegments ] [ :callbackUri ] )
86- context_cache_time = big_segments [ :userCacheTimeMs ] . nil? ? nil : big_segments [ :userCacheTimeMs ] / 1_000
87- status_poll_interval_ms = big_segments [ :statusPollIntervalMs ] . nil? ? nil : big_segments [ :statusPollIntervalMs ] / 1_000
88- stale_after_ms = big_segments [ :staleAfterMs ] . nil? ? nil : big_segments [ :staleAfterMs ] / 1_000
89-
90- opts [ :big_segments ] = LaunchDarkly ::BigSegmentsConfig . new (
91- store : store ,
92- context_cache_size : big_segments [ :userCacheSize ] ,
93- context_cache_time : context_cache_time ,
94- status_poll_interval : status_poll_interval_ms ,
95- stale_after : stale_after_ms
96- )
130+ big_config [ :context_cache_size ] = big_segments [ :userCacheSize ] if big_segments [ :userCacheSize ]
131+ set_optional_time_prop ( big_segments , :userCacheTimeMs , big_config , :context_cache_time )
132+ set_optional_time_prop ( big_segments , :statusPollIntervalMs , big_config , :status_poll_interval )
133+ set_optional_time_prop ( big_segments , :staleAfterMs , big_config , :stale_after )
134+
135+ opts [ :big_segments ] = LaunchDarkly ::BigSegmentsConfig . new ( **big_config )
97136 end
98137
99138 if config [ :tags ]
@@ -198,6 +237,50 @@ def context_comparison(params)
198237 context1 == context2
199238 end
200239
240+ def secure_mode_hash ( params )
241+ @client . secure_mode_hash ( params [ :context ] )
242+ end
243+
244+ def track ( params )
245+ @client . track ( params [ :eventKey ] , params [ :context ] , params [ :data ] , params [ :metricValue ] )
246+ end
247+
248+ def identify ( params )
249+ @client . identify ( params [ :context ] )
250+ end
251+
252+ def flush_events
253+ @client . flush
254+ end
255+
256+ def get_big_segment_store_status
257+ status = @client . big_segment_store_status_provider . status
258+ { available : status . available , stale : status . stale }
259+ end
260+
261+ def log
262+ @log
263+ end
264+
265+ def close
266+ @client . close
267+ @log . info ( "Test ended" )
268+ end
269+
270+ #
271+ # Helper to convert millisecond time properties to seconds.
272+ # Only sets the output if the input value is present.
273+ #
274+ # @param params_in [Hash] Input parameters hash
275+ # @param name_in [Symbol] Key name in input hash (e.g., :pollIntervalMs)
276+ # @param params_out [Hash] Output parameters hash
277+ # @param name_out [Symbol] Key name in output hash (e.g., :poll_interval)
278+ #
279+ private def set_optional_time_prop ( params_in , name_in , params_out , name_out )
280+ value = params_in [ name_in ]
281+ params_out [ name_out ] = value / 1_000.0 if value
282+ end
283+
201284 private def build_context_from_params ( params )
202285 return build_single_context_from_attribute_definitions ( params [ :single ] ) unless params [ :single ] . nil?
203286
@@ -230,33 +313,48 @@ def context_comparison(params)
230313 LaunchDarkly ::LDContext . create ( context )
231314 end
232315
233- def secure_mode_hash ( params )
234- @client . secure_mode_hash ( params [ :context ] )
235- end
236-
237- def track ( params )
238- @client . track ( params [ :eventKey ] , params [ :context ] , params [ :data ] , params [ :metricValue ] )
239- end
240-
241- def identify ( params )
242- @client . identify ( params [ :context ] )
243- end
244-
245- def flush_events
246- @client . flush
247- end
248-
249- def get_big_segment_store_status
250- status = @client . big_segment_store_status_provider . status
251- { available : status . available , stale : status . stale }
252- end
253-
254- def log
255- @log
256- end
316+ #
317+ # Builds a persistent data store from the contract test configuration.
318+ #
319+ # @param persistent_store_config [Hash] The persistentDataStore configuration
320+ # @return [Array<Object, Symbol>] Returns [store, store_mode]
321+ #
322+ private def build_persistent_store ( persistent_store_config )
323+ store_config = { }
324+ store_config [ :prefix ] = persistent_store_config [ :store ] [ :prefix ] if persistent_store_config [ :store ] [ :prefix ]
325+
326+ case persistent_store_config [ :cache ] [ :mode ]
327+ when 'off'
328+ store_config [ :expiration ] = 0
329+ when 'infinite'
330+ # NOTE: We don't actually support infinite cache mode, so we'll just set it to nil for now. This uses a default
331+ # 15 second expiration time in the SDK, which is long enough to pass any test.
332+ store_config [ :expiration ] = nil
333+ when 'ttl'
334+ store_config [ :expiration ] = persistent_store_config [ :cache ] [ :ttl ]
335+ end
257336
258- def close
259- @client . close
260- @log . info ( "Test ended" )
337+ store = case persistent_store_config [ :store ] [ :type ]
338+ when 'redis'
339+ store_config [ :redis_url ] = persistent_store_config [ :store ] [ :dsn ]
340+ LaunchDarkly ::Integrations ::Redis . new_feature_store ( store_config )
341+ when 'consul'
342+ store_config [ :url ] = persistent_store_config [ :store ] [ :url ]
343+ LaunchDarkly ::Integrations ::Consul . new_feature_store ( store_config )
344+ when 'dynamodb'
345+ client = Aws ::DynamoDB ::Client . new (
346+ region : 'us-east-1' ,
347+ credentials : Aws ::Credentials . new ( 'dummy' , 'dummy' , 'dummy' ) ,
348+ endpoint : persistent_store_config [ :store ] [ :dsn ]
349+ )
350+ store_config [ :existing_client ] = client
351+ LaunchDarkly ::Integrations ::DynamoDB . new_feature_store ( 'sdk-contract-tests' , store_config )
352+ end
353+
354+ store_mode = persistent_store_config [ :mode ] == 'read' ?
355+ LaunchDarkly ::Interfaces ::DataSystem ::DataStoreMode ::READ_ONLY :
356+ LaunchDarkly ::Interfaces ::DataSystem ::DataStoreMode ::READ_WRITE
357+
358+ [ store , store_mode ]
261359 end
262360end
0 commit comments