Skip to main content

launchdarkly_server_sdk/
test_data.rs

1//! Test data source for use in tests.
2//!
3//! The [`TestData`] type provides a way to inject feature flag data into the SDK for testing,
4//! without needing a LaunchDarkly connection. Pass it to [`crate::ConfigBuilder::data_source`]
5//! to use it with the FDv1 data source, or to [`crate::DataSystemBuilder::synchronizer`] to use
6//! it with the FDv2 data system.
7//!
8//! # Examples
9//!
10//! ```no_run
11//! use launchdarkly_server_sdk::{TestData, FlagBuilder};
12//!
13//! let td = TestData::new();
14//! td.update(FlagBuilder::new("flag-key-1").variation_for_all(true));
15//! ```
16
17use std::collections::HashMap;
18use std::sync::Arc;
19
20use launchdarkly_server_sdk_evaluation::{Flag, FlagBuilder, Segment};
21use parking_lot::{Mutex, RwLock};
22use tokio::sync::{broadcast, mpsc};
23
24use crate::data_source::DataSource;
25use crate::data_source_builders::{BuildError, DataSourceFactory};
26use crate::data_system_builders::{
27    BuildError as DataSystemBuildError, DataSourceBuildContext, FDv2SynchronizerConfig,
28};
29use crate::fdv2::data_system::SynchronizerFactory;
30use crate::fdv2::model::{ChangeSetKind, Selector};
31use crate::fdv2::source::{FDv2SourceEvent, FDv2SourceEventFuture, FDv2SourceResult, Synchronizer};
32use crate::service_endpoints;
33use crate::stores::change_set::{ChangeSet, ItemChange};
34use crate::stores::store::DataStore;
35use crate::stores::store_types::{AllData, PatchTarget, StorageItem};
36
37/// Tracks whether a flag was created via a builder or set as a preconfigured flag.
38enum FlagOrigin {
39    Builder(FlagBuilder),
40    Preconfigured,
41}
42
43struct TestDataInner {
44    flag_origins: HashMap<String, FlagOrigin>,
45    current_flags: HashMap<String, Flag>,
46    flag_versions: HashMap<String, u64>,
47    current_segments: HashMap<String, Segment>,
48    segment_versions: HashMap<String, u64>,
49    instances: Vec<Arc<RwLock<dyn DataStore>>>,
50    fdv2_senders: Vec<mpsc::UnboundedSender<ChangeSet>>,
51}
52
53impl TestDataInner {
54    /// Sends a partial change set for one flag to every registered FDv2 synchronizer.
55    fn broadcast_flag(&mut self, flag: &Flag) {
56        self.broadcast(ItemChange::Flag {
57            key: flag.key.clone(),
58            item: StorageItem::Item(flag.clone()),
59        });
60    }
61
62    /// Sends a partial change set for one segment to every registered FDv2 synchronizer.
63    fn broadcast_segment(&mut self, segment: &Segment) {
64        self.broadcast(ItemChange::Segment {
65            key: segment.key.clone(),
66            item: StorageItem::Item(segment.clone()),
67        });
68    }
69
70    /// Sends `change` to every registered FDv2 synchronizer, dropping any whose
71    /// receiver has closed.
72    fn broadcast(&mut self, change: ItemChange) {
73        self.fdv2_senders.retain(|sender| {
74            let change_set = ChangeSet {
75                kind: ChangeSetKind::Partial,
76                changes: vec![change.clone()],
77                selector: None,
78            };
79            sender.send(change_set).is_ok()
80        });
81    }
82}
83
84/// A mechanism for providing dynamically updatable feature flag state in a simplified form to an
85/// SDK client.
86///
87/// `TestData` can be passed to [`crate::ConfigBuilder::data_source`] to drive the FDv1 data
88/// source, or to [`crate::DataSystemBuilder::synchronizer`] to drive the FDv2 data system. When
89/// the SDK client is started, it receives the current flag state from `TestData` and is notified
90/// of subsequent changes.
91///
92/// With the FDv2 data system, updates are delivered asynchronously. A change made with
93/// [`TestData::update`] may take a moment to be reflected in flag evaluations.
94///
95/// Flag data can be provided using [`FlagBuilder`](crate::FlagBuilder) (via [`TestData::update`])
96/// or by passing fully constructed [`Flag`] objects (via [`TestData::use_preconfigured_flag`]).
97///
98/// Cloning a `TestData` creates a new handle that shares the same underlying state.
99#[derive(Clone)]
100pub struct TestData {
101    inner: Arc<Mutex<TestDataInner>>,
102}
103
104impl TestData {
105    /// Creates a new `TestData` instance with no initial flag data.
106    pub fn new() -> Self {
107        Self {
108            inner: Arc::new(Mutex::new(TestDataInner {
109                flag_origins: HashMap::new(),
110                current_flags: HashMap::new(),
111                flag_versions: HashMap::new(),
112                current_segments: HashMap::new(),
113                segment_versions: HashMap::new(),
114                instances: Vec::new(),
115                fdv2_senders: Vec::new(),
116            })),
117        }
118    }
119
120    /// Returns a copy of the current [`FlagBuilder`](crate::FlagBuilder) for the specified flag key, or a new default
121    /// builder if the flag has not been configured yet.
122    ///
123    /// If the flag was previously set via [`TestData::use_preconfigured_flag`], this returns a new
124    /// default builder since [`FlagBuilder`](crate::FlagBuilder) cannot represent all flag fields
125    /// (e.g. prerequisites, migration settings, client visibility) and hydrating from a [`Flag`]
126    /// would silently lose that data.
127    pub fn flag(&self, key: &str) -> FlagBuilder {
128        let inner = self.inner.lock();
129        match inner.flag_origins.get(key) {
130            Some(FlagOrigin::Builder(builder)) => builder.clone(),
131            _ => FlagBuilder::new(key),
132        }
133    }
134
135    /// Updates the test data with the given flag builder.
136    ///
137    /// If there are any connected SDK clients, they will be notified of the flag change. The flag
138    /// version is automatically incremented.
139    pub fn update(&self, builder: FlagBuilder) {
140        let mut inner = self.inner.lock();
141
142        let key = builder.key().to_owned();
143        let stored_builder = builder.clone();
144        let mut flag = builder.build();
145
146        let version = inner.flag_versions.entry(key.clone()).or_insert(0);
147        *version += 1;
148        flag.version = *version;
149
150        inner
151            .flag_origins
152            .insert(key.clone(), FlagOrigin::Builder(stored_builder));
153        inner.current_flags.insert(key.clone(), flag.clone());
154
155        for store in &inner.instances {
156            let mut store = store.write();
157            let _ = store.upsert(&key, PatchTarget::Flag(StorageItem::Item(flag.clone())));
158        }
159
160        inner.broadcast_flag(&flag);
161    }
162
163    /// Sets a preconfigured [`Flag`] directly.
164    ///
165    /// Use this when you need to set a flag with a configuration that cannot be expressed through
166    /// [`FlagBuilder`](crate::FlagBuilder). The flag version is automatically managed.
167    ///
168    /// Note: calling [`TestData::flag`] after this will return a new default builder, not a
169    /// builder derived from the preconfigured flag.
170    pub fn use_preconfigured_flag(&self, mut flag: Flag) {
171        let mut inner = self.inner.lock();
172
173        let key = flag.key.clone();
174        let version = inner.flag_versions.entry(key.clone()).or_insert(0);
175        *version += 1;
176        flag.version = *version;
177
178        inner
179            .flag_origins
180            .insert(key.clone(), FlagOrigin::Preconfigured);
181        inner.current_flags.insert(key.clone(), flag.clone());
182
183        for store in &inner.instances {
184            let mut store = store.write();
185            let _ = store.upsert(&key, PatchTarget::Flag(StorageItem::Item(flag.clone())));
186        }
187
188        inner.broadcast_flag(&flag);
189    }
190
191    /// Sets a preconfigured [`Segment`] directly.
192    ///
193    /// The segment version is automatically managed.
194    pub fn use_preconfigured_segment(&self, mut segment: Segment) {
195        let mut inner = self.inner.lock();
196
197        let key = segment.key.clone();
198        let version = inner.segment_versions.entry(key.clone()).or_insert(0);
199        *version += 1;
200        segment.version = *version;
201
202        inner.current_segments.insert(key.clone(), segment.clone());
203
204        for store in &inner.instances {
205            let mut store = store.write();
206            let _ = store.upsert(
207                &key,
208                PatchTarget::Segment(StorageItem::Item(segment.clone())),
209            );
210        }
211
212        inner.broadcast_segment(&segment);
213    }
214}
215
216impl Default for TestData {
217    fn default() -> Self {
218        Self::new()
219    }
220}
221
222impl DataSourceFactory for TestData {
223    fn build(
224        &self,
225        _endpoints: &service_endpoints::ServiceEndpoints,
226        _sdk_key: &str,
227        _tags: Option<String>,
228    ) -> Result<Arc<dyn DataSource>, BuildError> {
229        Ok(Arc::new(TestDataSource {
230            inner: self.inner.clone(),
231        }))
232    }
233
234    fn to_owned(&self) -> Box<dyn DataSourceFactory> {
235        Box::new(self.clone())
236    }
237}
238
239struct TestDataSource {
240    inner: Arc<Mutex<TestDataInner>>,
241}
242
243impl DataSource for TestDataSource {
244    fn subscribe(
245        &self,
246        data_store: Arc<RwLock<dyn DataStore>>,
247        init_complete: Arc<dyn Fn(bool) + Send + Sync>,
248        shutdown_receiver: broadcast::Receiver<()>,
249    ) {
250        let mut inner = self.inner.lock();
251
252        // Initialize the store with all current data
253        let all_data = AllData {
254            flags: inner.current_flags.clone(),
255            segments: inner.current_segments.clone(),
256        };
257
258        {
259            let mut store = data_store.write();
260            store.init(all_data);
261        }
262
263        // Register this store for future updates
264        inner.instances.push(data_store.clone());
265
266        (init_complete)(true);
267
268        // Spawn a task to unregister the store on shutdown
269        let inner_ref = self.inner.clone();
270        let store_ref = data_store.clone();
271        tokio::spawn(async move {
272            let mut shutdown = shutdown_receiver;
273            let _ = shutdown.recv().await;
274            let mut inner = inner_ref.lock();
275            inner.instances.retain(|s| !Arc::ptr_eq(s, &store_ref));
276        });
277    }
278}
279
280impl FDv2SynchronizerConfig for TestData {
281    fn build_synchronizer(
282        &self,
283        _context: &DataSourceBuildContext,
284    ) -> Result<Box<dyn SynchronizerFactory>, DataSystemBuildError> {
285        Ok(Box::new(TestDataSynchronizerFactory {
286            inner: self.inner.clone(),
287        }))
288    }
289
290    fn to_owned(&self) -> Box<dyn FDv2SynchronizerConfig> {
291        Box::new(self.clone())
292    }
293}
294
295struct TestDataSynchronizerFactory {
296    inner: Arc<Mutex<TestDataInner>>,
297}
298
299impl SynchronizerFactory for TestDataSynchronizerFactory {
300    fn create(&self) -> Box<dyn Synchronizer> {
301        let mut inner = self.inner.lock();
302
303        // Snapshot the current flags and segments as the initial full payload.
304        let mut changes = Vec::new();
305        for flag in inner.current_flags.values() {
306            changes.push(ItemChange::Flag {
307                key: flag.key.clone(),
308                item: StorageItem::Item(flag.clone()),
309            });
310        }
311        for segment in inner.current_segments.values() {
312            changes.push(ItemChange::Segment {
313                key: segment.key.clone(),
314                item: StorageItem::Item(segment.clone()),
315            });
316        }
317        let initial = ChangeSet {
318            kind: ChangeSetKind::Full,
319            changes,
320            selector: None,
321        };
322
323        // Register a channel so later updates reach this synchronizer.
324        let (sender, receiver) = mpsc::unbounded_channel();
325        inner.fdv2_senders.push(sender);
326
327        Box::new(TestDataSynchronizer {
328            initial: Some(initial),
329            receiver,
330        })
331    }
332}
333
334struct TestDataSynchronizer {
335    initial: Option<ChangeSet>,
336    receiver: mpsc::UnboundedReceiver<ChangeSet>,
337}
338
339impl Synchronizer for TestDataSynchronizer {
340    fn next(&mut self, _selector: Selector) -> FDv2SourceEventFuture<'_> {
341        if let Some(change_set) = self.initial.take() {
342            return Box::pin(async move {
343                FDv2SourceEvent {
344                    result: FDv2SourceResult::ChangeSet(change_set),
345                    fdv1_fallback: None,
346                }
347            });
348        }
349        Box::pin(async move {
350            match self.receiver.recv().await {
351                Some(change_set) => FDv2SourceEvent {
352                    result: FDv2SourceResult::ChangeSet(change_set),
353                    fdv1_fallback: None,
354                },
355                // The test data was dropped, so idle instead of terminating.
356                None => std::future::pending().await,
357            }
358        })
359    }
360
361    fn name(&self) -> &str {
362        "test-data"
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use crate::stores::store::InMemoryDataStore;
370    use launchdarkly_server_sdk_evaluation::FlagBuilder;
371    use std::sync::atomic::{AtomicBool, Ordering};
372
373    fn make_store() -> Arc<RwLock<dyn DataStore>> {
374        Arc::new(RwLock::new(InMemoryDataStore::new()))
375    }
376
377    fn subscribe_store(td: &TestData, store: &Arc<RwLock<dyn DataStore>>) -> broadcast::Sender<()> {
378        let factory: &dyn DataSourceFactory = td;
379        let endpoints = crate::ServiceEndpointsBuilder::new().build().unwrap();
380        let ds = factory.build(&endpoints, "fake-key", None).unwrap();
381
382        let (shutdown_tx, shutdown_rx) = broadcast::channel(1);
383        ds.subscribe(store.clone(), Arc::new(|_| {}), shutdown_rx);
384        shutdown_tx
385    }
386
387    #[test]
388    fn flag_returns_default_builder_for_unknown_key() {
389        let td = TestData::new();
390        let builder = td.flag("unknown");
391        assert_eq!(builder.key(), "unknown");
392        let flag = builder.build();
393        assert_eq!(flag.key, "unknown");
394    }
395
396    #[test]
397    fn flag_returns_cloned_builder_after_update() {
398        let td = TestData::new();
399        td.update(FlagBuilder::new("my-flag").variation_for_all(false));
400        let builder = td.flag("my-flag");
401        assert_eq!(builder.key(), "my-flag");
402    }
403
404    #[test]
405    fn flag_returns_default_builder_for_preconfigured_flag() {
406        let td = TestData::new();
407        let flag = FlagBuilder::new("preconf").build();
408        td.use_preconfigured_flag(flag);
409        let builder = td.flag("preconf");
410        assert_eq!(builder.key(), "preconf");
411    }
412
413    #[test]
414    fn update_increments_version_each_call() {
415        let td = TestData::new();
416        td.update(FlagBuilder::new("my-flag"));
417        td.update(FlagBuilder::new("my-flag"));
418        td.update(FlagBuilder::new("my-flag"));
419
420        let inner = td.inner.lock();
421        let flag = inner.current_flags.get("my-flag").unwrap();
422        assert_eq!(flag.version, 3);
423    }
424
425    #[test]
426    fn use_preconfigured_flag_increments_version_each_call() {
427        let td = TestData::new();
428        td.use_preconfigured_flag(FlagBuilder::new("my-flag").build());
429        td.use_preconfigured_flag(FlagBuilder::new("my-flag").build());
430        td.use_preconfigured_flag(FlagBuilder::new("my-flag").build());
431
432        let inner = td.inner.lock();
433        let flag = inner.current_flags.get("my-flag").unwrap();
434        assert_eq!(flag.version, 3);
435    }
436
437    #[test]
438    fn flag_builder_increments_version_each_call() {
439        let td = TestData::new();
440        td.update(td.flag("my-flag").variation_for_all(true));
441        td.update(td.flag("my-flag").variation_for_all(false));
442        td.update(td.flag("my-flag").variation_for_all(true));
443
444        let inner = td.inner.lock();
445        let flag = inner.current_flags.get("my-flag").unwrap();
446        assert_eq!(flag.version, 3);
447    }
448
449    #[tokio::test]
450    async fn update_propagates_to_connected_store() {
451        let td = TestData::new();
452        let store = make_store();
453        let _shutdown = subscribe_store(&td, &store);
454
455        td.update(FlagBuilder::new("my-flag").variation_for_all(true));
456
457        let s = store.read();
458        let flag = s.flag("my-flag").unwrap();
459        assert_eq!(flag.key, "my-flag");
460    }
461
462    #[tokio::test]
463    async fn subscribe_initializes_store_with_all_current_data() {
464        let td = TestData::new();
465        td.update(FlagBuilder::new("flag-1").variation_for_all(true));
466        td.update(FlagBuilder::new("flag-2").variation_for_all(false));
467
468        let store = make_store();
469        let _shutdown = subscribe_store(&td, &store);
470
471        let s = store.read();
472        assert!(s.flag("flag-1").is_some());
473        assert!(s.flag("flag-2").is_some());
474    }
475
476    #[tokio::test]
477    async fn subscribe_calls_init_complete_true() {
478        let td = TestData::new();
479        let store = make_store();
480
481        let factory: &dyn DataSourceFactory = &td;
482        let endpoints = crate::ServiceEndpointsBuilder::new().build().unwrap();
483        let ds = factory.build(&endpoints, "fake-key", None).unwrap();
484
485        let initialized = Arc::new(AtomicBool::new(false));
486        let init_clone = initialized.clone();
487        let (_shutdown_tx, shutdown_rx) = broadcast::channel(1);
488
489        ds.subscribe(
490            store,
491            Arc::new(move |success| init_clone.store(success, Ordering::SeqCst)),
492            shutdown_rx,
493        );
494
495        assert!(initialized.load(Ordering::SeqCst));
496    }
497
498    #[tokio::test]
499    async fn multiple_stores_receive_updates() {
500        let td = TestData::new();
501        let store1 = make_store();
502        let store2 = make_store();
503        let _shutdown1 = subscribe_store(&td, &store1);
504        let _shutdown2 = subscribe_store(&td, &store2);
505
506        td.update(FlagBuilder::new("shared-flag"));
507
508        assert!(store1.read().flag("shared-flag").is_some());
509        assert!(store2.read().flag("shared-flag").is_some());
510    }
511
512    #[tokio::test]
513    async fn use_preconfigured_flag_propagates() {
514        let td = TestData::new();
515        let store = make_store();
516        let _shutdown = subscribe_store(&td, &store);
517
518        let flag = FlagBuilder::new("preconf").variation_for_all(true).build();
519        td.use_preconfigured_flag(flag);
520
521        let s = store.read();
522        let stored_flag = s.flag("preconf").unwrap();
523        assert_eq!(stored_flag.version, 1);
524    }
525
526    #[tokio::test]
527    async fn use_preconfigured_segment_propagates() {
528        let td = TestData::new();
529        let store = make_store();
530        let _shutdown = subscribe_store(&td, &store);
531
532        let segment: Segment = serde_json::from_str(
533            r#"{
534                "key": "seg-1",
535                "included": ["alice"],
536                "excluded": [],
537                "rules": [],
538                "salt": "salty",
539                "version": 999
540            }"#,
541        )
542        .unwrap();
543
544        td.use_preconfigured_segment(segment);
545
546        let s = store.read();
547        let stored = s.segment("seg-1").unwrap();
548        assert_eq!(stored.version, 1); // version auto-managed, not 999
549    }
550
551    #[test]
552    fn version_counters_are_independent_per_flag() {
553        let td = TestData::new();
554        td.update(FlagBuilder::new("a"));
555        td.update(FlagBuilder::new("a"));
556        td.update(FlagBuilder::new("b"));
557
558        let inner = td.inner.lock();
559        assert_eq!(inner.current_flags.get("a").unwrap().version, 2);
560        assert_eq!(inner.current_flags.get("b").unwrap().version, 1);
561    }
562
563    #[tokio::test]
564    async fn data_source_factory_build_returns_working_data_source() {
565        let td = TestData::new();
566        td.update(FlagBuilder::new("factory-flag"));
567
568        let factory: &dyn DataSourceFactory = &td;
569        let endpoints = crate::ServiceEndpointsBuilder::new().build().unwrap();
570        let ds = factory.build(&endpoints, "key", None).unwrap();
571
572        let store = make_store();
573        let (_tx, rx) = broadcast::channel(1);
574        ds.subscribe(store.clone(), Arc::new(|_| {}), rx);
575
576        assert!(store.read().flag("factory-flag").is_some());
577    }
578
579    #[tokio::test]
580    async fn data_source_factory_to_owned_shares_state() {
581        let td = TestData::new();
582        let owned = DataSourceFactory::to_owned(&td);
583
584        td.update(FlagBuilder::new("shared-state"));
585
586        let endpoints = crate::ServiceEndpointsBuilder::new().build().unwrap();
587        let ds = owned.build(&endpoints, "key", None).unwrap();
588
589        let store = make_store();
590        let (_tx, rx) = broadcast::channel(1);
591        ds.subscribe(store.clone(), Arc::new(|_| {}), rx);
592
593        assert!(store.read().flag("shared-state").is_some());
594    }
595
596    #[tokio::test]
597    async fn shutdown_unregisters_store() {
598        let td = TestData::new();
599        let store = make_store();
600        let shutdown_tx = subscribe_store(&td, &store);
601
602        assert_eq!(td.inner.lock().instances.len(), 1);
603
604        let _ = shutdown_tx.send(());
605        // Give the spawned task time to run
606        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
607
608        assert_eq!(td.inner.lock().instances.len(), 0);
609    }
610
611    fn build_fdv2_synchronizer(td: &TestData) -> Box<dyn crate::fdv2::source::Synchronizer> {
612        use crate::fdv2::request_headers::RequestHeaders;
613
614        let endpoints = crate::ServiceEndpointsBuilder::new().build().unwrap();
615        let headers = RequestHeaders::new("fake-key", None, "test-instance");
616        let context = DataSourceBuildContext {
617            endpoints: &endpoints,
618            headers: &headers,
619        };
620        FDv2SynchronizerConfig::build_synchronizer(td, &context)
621            .unwrap()
622            .create()
623    }
624
625    fn flag_keys(cs: &crate::stores::change_set::ChangeSet) -> Vec<&str> {
626        cs.changes
627            .iter()
628            .filter_map(|c| match c {
629                crate::stores::change_set::ItemChange::Flag { key, .. } => Some(key.as_str()),
630                _ => None,
631            })
632            .collect()
633    }
634
635    #[tokio::test]
636    async fn fdv2_synchronizer_emits_full_basis_then_partials() {
637        use crate::fdv2::model::ChangeSetKind;
638        use crate::fdv2::source::FDv2SourceResult;
639
640        let td = TestData::new();
641        td.update(FlagBuilder::new("flag-1").variation_for_all(true));
642
643        let mut sync = build_fdv2_synchronizer(&td);
644
645        // First next() delivers a full payload with the current flags.
646        let event = sync.next(None).await;
647        match event.result {
648            FDv2SourceResult::ChangeSet(cs) => {
649                assert_eq!(cs.kind, ChangeSetKind::Full);
650                assert_eq!(flag_keys(&cs), vec!["flag-1"]);
651            }
652            other => panic!("expected a change set, got {other:?}"),
653        }
654
655        // A later update delivers a partial for just that flag.
656        td.update(FlagBuilder::new("flag-2").variation_for_all(false));
657        let event = sync.next(None).await;
658        match event.result {
659            FDv2SourceResult::ChangeSet(cs) => {
660                assert_eq!(cs.kind, ChangeSetKind::Partial);
661                assert_eq!(flag_keys(&cs), vec!["flag-2"]);
662            }
663            other => panic!("expected a change set, got {other:?}"),
664        }
665    }
666}