1use eval::Context;
2use parking_lot::RwLock;
3use std::io;
4use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
5use std::sync::{Arc, Mutex};
6use std::time::Duration;
7use tokio::runtime::Runtime;
8
9use launchdarkly_server_sdk_evaluation::{self as eval, Detail, FlagValue, PrerequisiteEvent};
10use serde::Serialize;
11use thiserror::Error;
12use tokio::sync::{broadcast, Semaphore};
13
14use super::config::Config;
15use super::data_source_builders::BuildError as DataSourceError;
16use super::data_system::{DataSystem, FDv1DataSystem};
17use super::data_system_builders::BuildError as DataSystemError;
18use super::evaluation::{FlagDetail, FlagDetailConfig};
19use super::stores::store::DataStore;
20use super::stores::store_builders::BuildError as DataStoreError;
21use crate::config::BuildError as ConfigBuildError;
22use crate::events::event::EventFactory;
23use crate::events::event::InputEvent;
24use crate::events::processor::EventProcessor;
25use crate::events::processor_builders::BuildError as EventProcessorError;
26use crate::{MigrationOpTracker, Stage};
27
28struct EventsScope {
29 disabled: bool,
30 event_factory: EventFactory,
31 prerequisite_event_recorder: Box<dyn eval::PrerequisiteEventRecorder + Send + Sync>,
32}
33
34struct PrerequisiteEventRecorder {
35 event_factory: EventFactory,
36 event_processor: Arc<dyn EventProcessor>,
37}
38
39impl eval::PrerequisiteEventRecorder for PrerequisiteEventRecorder {
40 fn record(&self, event: PrerequisiteEvent) {
41 let evt = self.event_factory.new_eval_event(
42 &event.prerequisite_flag.key,
43 event.context.clone(),
44 &event.prerequisite_flag,
45 event.prerequisite_result,
46 FlagValue::Json(serde_json::Value::Null),
47 Some(event.target_flag_key),
48 );
49
50 self.event_processor.send(evt);
51 }
52}
53
54#[non_exhaustive]
56#[derive(Debug, Error)]
57pub enum BuildError {
58 #[error("invalid client config: {0}")]
60 InvalidConfig(String),
61}
62
63impl From<DataSourceError> for BuildError {
64 fn from(error: DataSourceError) -> Self {
65 Self::InvalidConfig(error.to_string())
66 }
67}
68
69impl From<DataSystemError> for BuildError {
70 fn from(error: DataSystemError) -> Self {
71 Self::InvalidConfig(error.to_string())
72 }
73}
74
75impl From<DataStoreError> for BuildError {
76 fn from(error: DataStoreError) -> Self {
77 Self::InvalidConfig(error.to_string())
78 }
79}
80
81impl From<EventProcessorError> for BuildError {
82 fn from(error: EventProcessorError) -> Self {
83 Self::InvalidConfig(error.to_string())
84 }
85}
86
87impl From<ConfigBuildError> for BuildError {
88 fn from(error: ConfigBuildError) -> Self {
89 Self::InvalidConfig(error.to_string())
90 }
91}
92
93#[non_exhaustive]
95#[derive(Debug, Error)]
96pub enum StartError {
97 #[error("couldn't spawn background thread for client: {0}")]
99 SpawnFailed(io::Error),
100}
101
102#[derive(PartialEq, Copy, Clone, Debug)]
103enum ClientInitState {
104 Initializing = 0,
105 Initialized = 1,
106 InitializationFailed = 2,
107}
108
109impl PartialEq<usize> for ClientInitState {
110 fn eq(&self, other: &usize) -> bool {
111 *self as usize == *other
112 }
113}
114
115impl From<usize> for ClientInitState {
116 fn from(val: usize) -> Self {
117 match val {
118 0 => ClientInitState::Initializing,
119 1 => ClientInitState::Initialized,
120 2 => ClientInitState::InitializationFailed,
121 _ => unreachable!(),
122 }
123 }
124}
125
126pub struct Client {
156 event_processor: Arc<dyn EventProcessor>,
157 data_system: Arc<dyn DataSystem>,
158 data_store: Arc<RwLock<dyn DataStore>>,
159 events_default: EventsScope,
160 events_with_reasons: EventsScope,
161 init_notify: Arc<Semaphore>,
162 init_state: Arc<AtomicUsize>,
163 started: AtomicBool,
164 offline: bool,
165 daemon_mode: bool,
166 #[cfg_attr(
167 not(any(feature = "crypto-openssl", feature = "crypto-aws-lc-rs")),
168 allow(dead_code)
169 )]
170 sdk_key: String,
171 shutdown_broadcast: broadcast::Sender<()>,
172 runtime: RwLock<Option<Runtime>>,
173}
174
175impl Client {
176 pub fn build(config: Config) -> Result<Self, BuildError> {
178 if config.offline() {
179 info!("Started LaunchDarkly Client in offline mode");
180 } else if config.daemon_mode() {
181 info!("Started LaunchDarkly Client in daemon mode");
182 }
183
184 let tags = config.application_tag();
185 let instance_id = config.instance_id().to_string();
186
187 let endpoints = config.service_endpoints_builder().build()?;
188
189 let mut event_processor_builder = config.event_processor_builder().to_owned();
190 event_processor_builder.set_instance_id(instance_id.clone());
191 let event_processor =
192 event_processor_builder.build(&endpoints, config.sdk_key(), tags.clone())?;
193
194 let data_system: Arc<dyn DataSystem> = match config.data_system_builder() {
195 Some(data_system_builder) => data_system_builder.build(
196 &endpoints,
197 config.sdk_key(),
198 tags.as_deref(),
199 &instance_id,
200 )?,
201 None => {
202 let mut data_source_builder = config.data_source_builder().to_owned();
203 data_source_builder.set_instance_id(instance_id);
204 let data_source =
205 data_source_builder.build(&endpoints, config.sdk_key(), tags.clone())?;
206 Arc::new(FDv1DataSystem::new(
207 data_source,
208 config.data_store_builder(),
209 )?)
210 }
211 };
212 let data_store = data_system.store();
213
214 let events_default = EventsScope {
215 disabled: config.offline(),
216 event_factory: EventFactory::new(false),
217 prerequisite_event_recorder: Box::new(PrerequisiteEventRecorder {
218 event_factory: EventFactory::new(false),
219 event_processor: event_processor.clone(),
220 }),
221 };
222
223 let events_with_reasons = EventsScope {
224 disabled: config.offline(),
225 event_factory: EventFactory::new(true),
226 prerequisite_event_recorder: Box::new(PrerequisiteEventRecorder {
227 event_factory: EventFactory::new(true),
228 event_processor: event_processor.clone(),
229 }),
230 };
231
232 let (shutdown_tx, _) = broadcast::channel(1);
233
234 Ok(Client {
235 event_processor,
236 data_system,
237 data_store,
238 events_default,
239 events_with_reasons,
240 init_notify: Arc::new(Semaphore::new(0)),
241 init_state: Arc::new(AtomicUsize::new(ClientInitState::Initializing as usize)),
242 started: AtomicBool::new(false),
243 offline: config.offline(),
244 daemon_mode: config.daemon_mode(),
245 sdk_key: config.sdk_key().into(),
246 shutdown_broadcast: shutdown_tx,
247 runtime: RwLock::new(None),
248 })
249 }
250
251 pub fn start_with_default_executor(&self) {
253 if self.started.load(Ordering::SeqCst) {
254 return;
255 }
256 self.started.store(true, Ordering::SeqCst);
257 self.start_with_default_executor_internal();
258 }
259
260 fn start_with_default_executor_internal(&self) {
261 let notify = self.init_notify.clone();
265 let init_state = self.init_state.clone();
266
267 self.data_system.start(
268 Arc::new(move |success| {
269 init_state.store(
270 (if success {
271 ClientInitState::Initialized
272 } else {
273 ClientInitState::InitializationFailed
274 }) as usize,
275 Ordering::SeqCst,
276 );
277 notify.add_permits(1);
278 }),
279 self.shutdown_broadcast.subscribe(),
280 );
281 }
282
283 pub fn start_with_runtime(&self) -> Result<bool, StartError> {
289 if self.started.load(Ordering::SeqCst) {
290 return Ok(true);
291 }
292 self.started.store(true, Ordering::SeqCst);
293
294 let runtime = Runtime::new().map_err(StartError::SpawnFailed)?;
295 let _guard = runtime.enter();
296 self.runtime.write().replace(runtime);
297
298 self.start_with_default_executor_internal();
299
300 Ok(true)
301 }
302
303 pub async fn wait_for_initialization(&self, timeout: Duration) -> Option<bool> {
309 if timeout > Duration::from_secs(60) {
310 warn!("wait_for_initialization was configured to block for up to {} seconds. We recommend blocking no longer than 60 seconds.", timeout.as_secs());
311 }
312
313 let initialized = tokio::time::timeout(timeout, self.initialized_async_internal()).await;
314 initialized.ok()
315 }
316
317 async fn initialized_async_internal(&self) -> bool {
318 if self.offline || self.daemon_mode {
319 return true;
320 }
321
322 if ClientInitState::Initialized != self.init_state.load(Ordering::SeqCst) {
328 let _permit = self.init_notify.acquire().await;
329 }
330 ClientInitState::Initialized == self.init_state.load(Ordering::SeqCst)
331 }
332
333 pub fn initialized(&self) -> bool {
337 self.offline
338 || self.daemon_mode
339 || ClientInitState::Initialized == self.init_state.load(Ordering::SeqCst)
340 }
341
342 pub fn close(&self) {
346 self.event_processor.close();
347
348 if !self.offline && !self.daemon_mode {
351 if let Err(e) = self.shutdown_broadcast.send(()) {
352 error!("Failed to shutdown client appropriately: {e}");
353 }
354 }
355
356 self.runtime.write().take();
359 }
360
361 pub fn flush(&self) {
369 self.event_processor.flush();
370 }
371
372 pub async fn flush_blocking(&self, timeout: Duration) -> bool {
408 let event_processor = self.event_processor.clone();
409
410 let flush_future =
411 tokio::task::spawn_blocking(move || event_processor.flush_blocking(timeout));
412
413 if timeout == Duration::ZERO {
414 flush_future.await.unwrap_or(false)
416 } else {
417 match tokio::time::timeout(timeout, flush_future).await {
419 Ok(Ok(result)) => result,
420 Ok(Err(_)) => false, Err(_) => false, }
423 }
424 }
425
426 pub fn identify(&self, context: Context) {
431 if self.events_default.disabled {
432 return;
433 }
434
435 self.send_internal(self.events_default.event_factory.new_identify(context));
436 }
437
438 pub fn bool_variation(&self, context: &Context, flag_key: &str, default: bool) -> bool {
446 let val = self.variation(context, flag_key, default);
447 if let Some(b) = val.as_bool() {
448 b
449 } else {
450 warn!("bool_variation called for a non-bool flag {flag_key:?} (got {val:?})");
451 default
452 }
453 }
454
455 pub fn str_variation(&self, context: &Context, flag_key: &str, default: String) -> String {
463 let val = self.variation(context, flag_key, default.clone());
464 if let Some(s) = val.as_string() {
465 s
466 } else {
467 warn!("str_variation called for a non-string flag {flag_key:?} (got {val:?})");
468 default
469 }
470 }
471
472 pub fn float_variation(&self, context: &Context, flag_key: &str, default: f64) -> f64 {
480 let val = self.variation(context, flag_key, default);
481 if let Some(f) = val.as_float() {
482 f
483 } else {
484 warn!("float_variation called for a non-float flag {flag_key:?} (got {val:?})");
485 default
486 }
487 }
488
489 pub fn int_variation(&self, context: &Context, flag_key: &str, default: i64) -> i64 {
497 let val = self.variation(context, flag_key, default);
498 if let Some(f) = val.as_int() {
499 f
500 } else {
501 warn!("int_variation called for a non-int flag {flag_key:?} (got {val:?})");
502 default
503 }
504 }
505
506 pub fn json_variation(
516 &self,
517 context: &Context,
518 flag_key: &str,
519 default: serde_json::Value,
520 ) -> serde_json::Value {
521 self.variation(context, flag_key, default.clone())
522 .as_json()
523 .unwrap_or(default)
524 }
525
526 pub fn bool_variation_detail(
533 &self,
534 context: &Context,
535 flag_key: &str,
536 default: bool,
537 ) -> Detail<bool> {
538 self.variation_detail(context, flag_key, default).try_map(
539 |val| val.as_bool(),
540 default,
541 eval::Error::WrongType,
542 )
543 }
544
545 pub fn str_variation_detail(
552 &self,
553 context: &Context,
554 flag_key: &str,
555 default: String,
556 ) -> Detail<String> {
557 self.variation_detail(context, flag_key, default.clone())
558 .try_map(|val| val.as_string(), default, eval::Error::WrongType)
559 }
560
561 pub fn float_variation_detail(
568 &self,
569 context: &Context,
570 flag_key: &str,
571 default: f64,
572 ) -> Detail<f64> {
573 self.variation_detail(context, flag_key, default).try_map(
574 |val| val.as_float(),
575 default,
576 eval::Error::WrongType,
577 )
578 }
579
580 pub fn int_variation_detail(
587 &self,
588 context: &Context,
589 flag_key: &str,
590 default: i64,
591 ) -> Detail<i64> {
592 self.variation_detail(context, flag_key, default).try_map(
593 |val| val.as_int(),
594 default,
595 eval::Error::WrongType,
596 )
597 }
598
599 pub fn json_variation_detail(
606 &self,
607 context: &Context,
608 flag_key: &str,
609 default: serde_json::Value,
610 ) -> Detail<serde_json::Value> {
611 self.variation_detail(context, flag_key, default.clone())
612 .try_map(|val| val.as_json(), default, eval::Error::WrongType)
613 }
614
615 #[cfg(any(feature = "crypto-aws-lc-rs", feature = "crypto-openssl"))]
616 pub fn secure_mode_hash(&self, context: &Context) -> Result<String, String> {
621 #[cfg(feature = "crypto-aws-lc-rs")]
622 {
623 let key =
624 aws_lc_rs::hmac::Key::new(aws_lc_rs::hmac::HMAC_SHA256, self.sdk_key.as_bytes());
625 let tag = aws_lc_rs::hmac::sign(&key, context.canonical_key().as_bytes());
626
627 Ok(data_encoding::HEXLOWER.encode(tag.as_ref()))
628 }
629 #[cfg(feature = "crypto-openssl")]
630 {
631 use openssl::hash::MessageDigest;
632 use openssl::pkey::PKey;
633 use openssl::sign::Signer;
634
635 let key = PKey::hmac(self.sdk_key.as_bytes())
636 .map_err(|e| format!("Failed to create HMAC key: {e}"))?;
637 let mut signer = Signer::new(MessageDigest::sha256(), &key)
638 .map_err(|e| format!("Failed to create signer: {e}"))?;
639 signer
640 .update(context.canonical_key().as_bytes())
641 .map_err(|e| format!("Failed to update signer: {e}"))?;
642 let hmac = signer
643 .sign_to_vec()
644 .map_err(|e| format!("Failed to sign: {e}"))?;
645
646 Ok(data_encoding::HEXLOWER.encode(&hmac))
647 }
648 }
649
650 pub fn all_flags_detail(
661 &self,
662 context: &Context,
663 flag_state_config: FlagDetailConfig,
664 ) -> FlagDetail {
665 if self.offline {
666 warn!(
667 "all_flags_detail() called, but client is in offline mode. Returning empty state"
668 );
669 return FlagDetail::new(false);
670 }
671
672 if !self.initialized() {
673 warn!("all_flags_detail() called before client has finished initializing! Feature store unavailable - returning empty state");
674 return FlagDetail::new(false);
675 }
676
677 let data_store = self.data_store.read();
678
679 let mut flag_detail = FlagDetail::new(true);
680 flag_detail.populate(&*data_store, context, flag_state_config);
681
682 flag_detail
683 }
684
685 pub fn variation_detail<T: Into<FlagValue> + Clone>(
691 &self,
692 context: &Context,
693 flag_key: &str,
694 default: T,
695 ) -> Detail<FlagValue> {
696 let (detail, _) =
697 self.variation_internal(context, flag_key, default, &self.events_with_reasons);
698 detail
699 }
700
701 pub fn variation<T: Into<FlagValue> + Clone>(
712 &self,
713 context: &Context,
714 flag_key: &str,
715 default: T,
716 ) -> FlagValue {
717 let (detail, _) = self.variation_internal(context, flag_key, default, &self.events_default);
718 detail.value.unwrap()
719 }
720
721 pub fn migration_variation(
726 &self,
727 context: &Context,
728 flag_key: &str,
729 default_stage: Stage,
730 ) -> (Stage, Arc<Mutex<MigrationOpTracker>>) {
731 let (detail, flag) =
732 self.variation_internal(context, flag_key, default_stage, &self.events_default);
733
734 let migration_detail =
735 detail.try_map(|v| v.try_into().ok(), default_stage, eval::Error::WrongType);
736 let tracker = MigrationOpTracker::new(
737 flag_key.into(),
738 flag,
739 context.clone(),
740 migration_detail.clone(),
741 default_stage,
742 );
743
744 (
745 migration_detail.value.unwrap_or(default_stage),
746 Arc::new(Mutex::new(tracker)),
747 )
748 }
749
750 pub fn track_event(&self, context: Context, key: impl Into<String>) {
760 let _ = self.track(context, key, None, serde_json::Value::Null);
761 }
762
763 pub fn track_data(
776 &self,
777 context: Context,
778 key: impl Into<String>,
779 data: impl Serialize,
780 ) -> serde_json::Result<()> {
781 self.track(context, key, None, data)
782 }
783
784 pub fn track_metric(
795 &self,
796 context: Context,
797 key: impl Into<String>,
798 value: f64,
799 data: impl Serialize,
800 ) {
801 let _ = self.track(context, key, Some(value), data);
802 }
803
804 fn track(
805 &self,
806 context: Context,
807 key: impl Into<String>,
808 metric_value: Option<f64>,
809 data: impl Serialize,
810 ) -> serde_json::Result<()> {
811 if !self.events_default.disabled {
812 let event =
813 self.events_default
814 .event_factory
815 .new_custom(context, key, metric_value, data)?;
816
817 self.send_internal(event);
818 }
819
820 Ok(())
821 }
822
823 pub fn track_migration_op(&self, tracker: Arc<Mutex<MigrationOpTracker>>) {
830 if self.events_default.disabled {
831 return;
832 }
833
834 match tracker.lock() {
835 Ok(tracker) => {
836 let event = tracker.build();
837 match event {
838 Ok(event) => {
839 self.send_internal(
840 self.events_default.event_factory.new_migration_op(event),
841 );
842 }
843 Err(e) => error!("Failed to build migration event, no event will be sent: {e}"),
844 }
845 }
846 Err(e) => error!("Failed to lock migration tracker, no event will be sent: {e}"),
847 }
848 }
849
850 fn variation_internal<T: Into<FlagValue> + Clone>(
851 &self,
852 context: &Context,
853 flag_key: &str,
854 default: T,
855 events_scope: &EventsScope,
856 ) -> (Detail<FlagValue>, Option<eval::Flag>) {
857 if self.offline {
858 return (
859 Detail::err_default(eval::Error::ClientNotReady, default.into()),
860 None,
861 );
862 }
863
864 let (flag, result) = match self.initialized() {
865 false => (
866 None,
867 Detail::err_default(eval::Error::ClientNotReady, default.clone().into()),
868 ),
869 true => {
870 let data_store = self.data_store.read();
871 match data_store.flag(flag_key) {
872 Some(flag) => {
873 let result = eval::evaluate(
874 data_store.to_store(),
875 &flag,
876 context,
877 Some(&*events_scope.prerequisite_event_recorder),
878 )
879 .map(|v| v.clone())
880 .or(default.clone().into());
881
882 (Some(flag), result)
883 }
884 None => (
885 None,
886 Detail::err_default(eval::Error::FlagNotFound, default.clone().into()),
887 ),
888 }
889 }
890 };
891
892 if !events_scope.disabled {
893 let event = match &flag {
894 Some(f) => events_scope.event_factory.new_eval_event(
895 flag_key,
896 context.clone(),
897 f,
898 result.clone(),
899 default.into(),
900 None,
901 ),
902 None => events_scope.event_factory.new_unknown_flag_event(
903 flag_key,
904 context.clone(),
905 result.clone(),
906 default.into(),
907 ),
908 };
909 self.send_internal(event);
910 }
911
912 (result, flag)
913 }
914
915 fn send_internal(&self, event: InputEvent) {
916 self.event_processor.send(event);
917 }
918}
919
920#[cfg(test)]
921mod tests {
922 use assert_json_diff::assert_json_eq;
923 use crossbeam_channel::Receiver;
924 use eval::{ContextBuilder, MultiContextBuilder};
925 use futures::FutureExt;
926 use launchdarkly_server_sdk_evaluation::{Flag, Reason, Segment};
927 use maplit::hashmap;
928 use std::collections::HashMap;
929 use tokio::time::Instant;
930
931 use crate::data_source::MockDataSource;
932 use crate::data_source_builders::MockDataSourceBuilder;
933 use crate::evaluation::FlagFilter;
934 use crate::events::create_event_sender;
935 use crate::events::event::{OutputEvent, VariationKey};
936 use crate::events::processor_builders::EventProcessorBuilder;
937 use crate::stores::persistent_store::tests::InMemoryPersistentDataStore;
938 use crate::stores::store_types::{PatchTarget, StorageItem};
939 use crate::test_common::{
940 self, basic_flag, basic_flag_with_prereq, basic_flag_with_prereqs_and_visibility,
941 basic_flag_with_visibility, basic_int_flag, basic_migration_flag, basic_off_flag,
942 };
943 use crate::test_data::TestData;
944 use crate::{
945 AllData, ConfigBuilder, DataSystemBuilder, FlagBuilder, MigratorBuilder,
946 NullEventProcessorBuilder, Operation, Origin, PersistentDataStore,
947 PersistentDataStoreBuilder, PersistentDataStoreFactory, SerializedItem,
948 };
949 use test_case::test_case;
950
951 use super::*;
952
953 fn is_send_and_sync<T: Send + Sync>() {}
954
955 #[test]
956 fn ensure_client_is_send_and_sync() {
957 is_send_and_sync::<Client>()
958 }
959
960 #[tokio::test]
961 async fn client_asynchronously_initializes_within_timeout() {
962 let (client, _event_rx) = make_mocked_client_with_delay(1000, false, false);
963 client.start_with_default_executor();
964
965 let now = Instant::now();
966 let initialized = client
967 .wait_for_initialization(Duration::from_millis(1500))
968 .await;
969 let elapsed_time = now.elapsed();
970 assert!(elapsed_time.as_millis() > 500);
972 assert_eq!(initialized, Some(true));
973 }
974
975 #[tokio::test]
976 async fn client_asynchronously_initializes_slower_than_timeout() {
977 let (client, _event_rx) = make_mocked_client_with_delay(2000, false, false);
978 client.start_with_default_executor();
979
980 let now = Instant::now();
981 let initialized = client
982 .wait_for_initialization(Duration::from_millis(500))
983 .await;
984 let elapsed_time = now.elapsed();
985 assert!(elapsed_time.as_millis() < 750);
987 assert!(initialized.is_none());
988 }
989
990 #[tokio::test]
991 async fn client_initializes_immediately_in_offline_mode() {
992 let (client, _event_rx) = make_mocked_client_with_delay(1000, true, false);
993 client.start_with_default_executor();
994
995 assert!(client.initialized());
996
997 let now = Instant::now();
998 let initialized = client
999 .wait_for_initialization(Duration::from_millis(2000))
1000 .await;
1001 let elapsed_time = now.elapsed();
1002 assert_eq!(initialized, Some(true));
1003 assert!(elapsed_time.as_millis() < 500)
1004 }
1005
1006 #[tokio::test]
1007 async fn client_initializes_immediately_in_daemon_mode() {
1008 let (client, _event_rx) = make_mocked_client_with_delay(1000, false, true);
1009 client.start_with_default_executor();
1010
1011 assert!(client.initialized());
1012
1013 let now = Instant::now();
1014 let initialized = client
1015 .wait_for_initialization(Duration::from_millis(2000))
1016 .await;
1017 let elapsed_time = now.elapsed();
1018 assert_eq!(initialized, Some(true));
1019 assert!(elapsed_time.as_millis() < 500)
1020 }
1021
1022 #[test_case(basic_flag("myFlag"), false.into(), true.into())]
1023 #[test_case(basic_int_flag("myFlag"), 0.into(), test_common::FLOAT_TO_INT_MAX.into())]
1024 fn client_updates_changes_evaluation_results(
1025 flag: eval::Flag,
1026 default: FlagValue,
1027 expected: FlagValue,
1028 ) {
1029 let context = ContextBuilder::new("foo")
1030 .build()
1031 .expect("Failed to create context");
1032
1033 let (client, _event_rx) = make_mocked_client();
1034
1035 let result = client.variation_detail(&context, "myFlag", default.clone());
1036 assert_eq!(result.value.unwrap(), default);
1037
1038 client.start_with_default_executor();
1039 client
1040 .data_store
1041 .write()
1042 .upsert(
1043 &flag.key,
1044 PatchTarget::Flag(StorageItem::Item(flag.clone())),
1045 )
1046 .expect("patch should apply");
1047
1048 let result = client.variation_detail(&context, "myFlag", default);
1049 assert_eq!(result.value.unwrap(), expected);
1050 assert!(matches!(
1051 result.reason,
1052 Reason::Fallthrough {
1053 in_experiment: false
1054 }
1055 ));
1056 }
1057
1058 #[test]
1059 fn all_flags_detail_is_invalid_when_offline() {
1060 let (client, _event_rx) = make_mocked_offline_client();
1061 client.start_with_default_executor();
1062
1063 let context = ContextBuilder::new("bob")
1064 .build()
1065 .expect("Failed to create context");
1066
1067 let all_flags = client.all_flags_detail(&context, FlagDetailConfig::new());
1068 assert_json_eq!(all_flags, json!({"$valid": false, "$flagsState" : {}}));
1069 }
1070
1071 #[test]
1072 fn all_flags_detail_is_invalid_when_not_initialized() {
1073 let (client, _event_rx) = make_mocked_client();
1074
1075 let context = ContextBuilder::new("bob")
1076 .build()
1077 .expect("Failed to create context");
1078
1079 let all_flags = client.all_flags_detail(&context, FlagDetailConfig::new());
1080 assert_json_eq!(all_flags, json!({"$valid": false, "$flagsState" : {}}));
1081 }
1082
1083 #[tokio::test]
1084 async fn all_flags_detail_returns_flag_states() {
1085 let td = TestData::new();
1086 td.use_preconfigured_flag(basic_flag("myFlag1"));
1087 td.use_preconfigured_flag(basic_flag("myFlag2"));
1088 let (client, _event_rx) = make_client_with_test_data(&td);
1089 client.start_with_default_executor();
1090
1091 let context = ContextBuilder::new("bob")
1092 .build()
1093 .expect("Failed to create context");
1094
1095 let all_flags = client.all_flags_detail(&context, FlagDetailConfig::new());
1096
1097 client.close();
1098
1099 assert_json_eq!(
1100 all_flags,
1101 json!({
1102 "myFlag1": true,
1103 "myFlag2": true,
1104 "$flagsState": {
1105 "myFlag1": {
1106 "version": 1,
1107 "variation": 1
1108 },
1109 "myFlag2": {
1110 "version": 1,
1111 "variation": 1
1112 },
1113 },
1114 "$valid": true
1115 })
1116 );
1117 }
1118
1119 #[tokio::test]
1120 async fn all_flags_detail_returns_prerequisite_relations() {
1121 let td = TestData::new();
1122 td.use_preconfigured_flag(basic_flag("prereq1"));
1123 td.use_preconfigured_flag(basic_flag("prereq2"));
1124 td.use_preconfigured_flag(basic_flag_with_prereqs_and_visibility(
1125 "toplevel",
1126 &["prereq1", "prereq2"],
1127 false,
1128 false,
1129 ));
1130 let (client, _event_rx) = make_client_with_test_data(&td);
1131 client.start_with_default_executor();
1132
1133 let context = ContextBuilder::new("bob")
1134 .build()
1135 .expect("Failed to create context");
1136
1137 let all_flags = client.all_flags_detail(&context, FlagDetailConfig::new());
1138
1139 client.close();
1140
1141 assert_json_eq!(
1142 all_flags,
1143 json!({
1144 "prereq1": true,
1145 "prereq2": true,
1146 "toplevel": true,
1147 "$flagsState": {
1148 "toplevel": {
1149 "version": 1,
1150 "variation": 1,
1151 "prerequisites": ["prereq1", "prereq2"]
1152 },
1153 "prereq1": {
1154 "version": 1,
1155 "variation": 1
1156 },
1157 "prereq2": {
1158 "version": 1,
1159 "variation": 1
1160 },
1161 },
1162 "$valid": true
1163 })
1164 );
1165 }
1166
1167 #[tokio::test]
1168 async fn all_flags_detail_returns_prerequisite_relations_when_not_visible_to_clients() {
1169 let td = TestData::new();
1170 td.use_preconfigured_flag(basic_flag_with_visibility("prereq1", false, false));
1171 td.use_preconfigured_flag(basic_flag_with_visibility("prereq2", false, false));
1172 td.use_preconfigured_flag(basic_flag_with_prereqs_and_visibility(
1173 "toplevel",
1174 &["prereq1", "prereq2"],
1175 true,
1176 false,
1177 ));
1178 let (client, _event_rx) = make_client_with_test_data(&td);
1179 client.start_with_default_executor();
1180
1181 let context = ContextBuilder::new("bob")
1182 .build()
1183 .expect("Failed to create context");
1184
1185 let mut config = FlagDetailConfig::new();
1186 config.flag_filter(FlagFilter::CLIENT);
1187
1188 let all_flags = client.all_flags_detail(&context, config);
1189
1190 client.close();
1191
1192 assert_json_eq!(
1193 all_flags,
1194 json!({
1195 "toplevel": true,
1196 "$flagsState": {
1197 "toplevel": {
1198 "version": 1,
1199 "variation": 1,
1200 "prerequisites": ["prereq1", "prereq2"]
1201 },
1202 },
1203 "$valid": true
1204 })
1205 );
1206 }
1207
1208 #[tokio::test]
1209 async fn variation_tracks_events_correctly() {
1210 let td = TestData::new();
1211 td.use_preconfigured_flag(basic_flag("myFlag"));
1212 let (client, event_rx) = make_client_with_test_data(&td);
1213 client.start_with_default_executor();
1214
1215 let context = ContextBuilder::new("bob")
1216 .build()
1217 .expect("Failed to create context");
1218
1219 let flag_value = client.variation(&context, "myFlag", FlagValue::Bool(false));
1220
1221 assert!(flag_value.as_bool().unwrap());
1222 client.flush();
1223 client.close();
1224
1225 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1226 assert_eq!(events.len(), 2);
1227 assert_eq!(events[0].kind(), "index");
1228 assert_eq!(events[1].kind(), "summary");
1229
1230 if let OutputEvent::Summary(event_summary) = events[1].clone() {
1231 let variation_key = VariationKey {
1232 version: Some(1),
1233 variation: Some(1),
1234 };
1235 let feature = event_summary.features.get("myFlag");
1236 assert!(feature.is_some());
1237
1238 let feature = feature.unwrap();
1239 assert!(feature.counters.contains_key(&variation_key));
1240 } else {
1241 panic!("Event should be a summary type");
1242 }
1243 }
1244
1245 #[test]
1246 fn variation_handles_offline_mode() {
1247 let (client, event_rx) = make_mocked_offline_client();
1248 client.start_with_default_executor();
1249
1250 let context = ContextBuilder::new("bob")
1251 .build()
1252 .expect("Failed to create context");
1253 let flag_value = client.variation(&context, "myFlag", FlagValue::Bool(false));
1254
1255 assert!(!flag_value.as_bool().unwrap());
1256 client.flush();
1257 client.close();
1258
1259 assert_eq!(event_rx.iter().count(), 0);
1260 }
1261
1262 #[test]
1263 fn variation_handles_unknown_flags() {
1264 let (client, event_rx) = make_mocked_client();
1265 client.start_with_default_executor();
1266 let context = ContextBuilder::new("bob")
1267 .build()
1268 .expect("Failed to create context");
1269
1270 let flag_value = client.variation(&context, "non-existent-flag", FlagValue::Bool(false));
1271
1272 assert!(!flag_value.as_bool().unwrap());
1273 client.flush();
1274 client.close();
1275
1276 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1277 assert_eq!(events.len(), 2);
1278 assert_eq!(events[0].kind(), "index");
1279 assert_eq!(events[1].kind(), "summary");
1280
1281 if let OutputEvent::Summary(event_summary) = events[1].clone() {
1282 let variation_key = VariationKey {
1283 version: None,
1284 variation: None,
1285 };
1286
1287 let feature = event_summary.features.get("non-existent-flag");
1288 assert!(feature.is_some());
1289
1290 let feature = feature.unwrap();
1291 assert!(feature.counters.contains_key(&variation_key));
1292 } else {
1293 panic!("Event should be a summary type");
1294 }
1295 }
1296
1297 #[tokio::test]
1298 async fn variation_detail_handles_debug_events_correctly() {
1299 let td = TestData::new();
1300 let mut flag = basic_flag("myFlag");
1301 flag.debug_events_until_date = Some(64_060_606_800_000); td.use_preconfigured_flag(flag);
1303 let (client, event_rx) = make_client_with_test_data(&td);
1304 client.start_with_default_executor();
1305
1306 let context = ContextBuilder::new("bob")
1307 .build()
1308 .expect("Failed to create context");
1309
1310 let detail = client.variation_detail(&context, "myFlag", FlagValue::Bool(false));
1311
1312 assert!(detail.value.unwrap().as_bool().unwrap());
1313 assert!(matches!(
1314 detail.reason,
1315 Reason::Fallthrough {
1316 in_experiment: false
1317 }
1318 ));
1319 client.flush();
1320 client.close();
1321
1322 let events = event_rx.try_iter().collect::<Vec<OutputEvent>>();
1323 assert_eq!(events.len(), 3);
1324 assert_eq!(events[0].kind(), "index");
1325 assert_eq!(events[1].kind(), "debug");
1326 assert_eq!(events[2].kind(), "summary");
1327
1328 if let OutputEvent::Summary(event_summary) = events[2].clone() {
1329 let variation_key = VariationKey {
1330 version: Some(1),
1331 variation: Some(1),
1332 };
1333
1334 let feature = event_summary.features.get("myFlag");
1335 assert!(feature.is_some());
1336
1337 let feature = feature.unwrap();
1338 assert!(feature.counters.contains_key(&variation_key));
1339 } else {
1340 panic!("Event should be a summary type");
1341 }
1342 }
1343
1344 #[tokio::test]
1345 async fn variation_detail_tracks_events_correctly() {
1346 let td = TestData::new();
1347 td.use_preconfigured_flag(basic_flag("myFlag"));
1348 let (client, event_rx) = make_client_with_test_data(&td);
1349 client.start_with_default_executor();
1350
1351 let context = ContextBuilder::new("bob")
1352 .build()
1353 .expect("Failed to create context");
1354
1355 let detail = client.variation_detail(&context, "myFlag", FlagValue::Bool(false));
1356
1357 assert!(detail.value.unwrap().as_bool().unwrap());
1358 assert!(matches!(
1359 detail.reason,
1360 Reason::Fallthrough {
1361 in_experiment: false
1362 }
1363 ));
1364 client.flush();
1365 client.close();
1366
1367 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1368 assert_eq!(events.len(), 2);
1369 assert_eq!(events[0].kind(), "index");
1370 assert_eq!(events[1].kind(), "summary");
1371
1372 if let OutputEvent::Summary(event_summary) = events[1].clone() {
1373 let variation_key = VariationKey {
1374 version: Some(1),
1375 variation: Some(1),
1376 };
1377
1378 let feature = event_summary.features.get("myFlag");
1379 assert!(feature.is_some());
1380
1381 let feature = feature.unwrap();
1382 assert!(feature.counters.contains_key(&variation_key));
1383 } else {
1384 panic!("Event should be a summary type");
1385 }
1386 }
1387
1388 #[test]
1389 fn variation_detail_handles_offline_mode() {
1390 let (client, event_rx) = make_mocked_offline_client();
1391 client.start_with_default_executor();
1392
1393 let context = ContextBuilder::new("bob")
1394 .build()
1395 .expect("Failed to create context");
1396
1397 let detail = client.variation_detail(&context, "myFlag", FlagValue::Bool(false));
1398
1399 assert!(!detail.value.unwrap().as_bool().unwrap());
1400 assert!(matches!(
1401 detail.reason,
1402 Reason::Error {
1403 error: eval::Error::ClientNotReady
1404 }
1405 ));
1406 client.flush();
1407 client.close();
1408
1409 assert_eq!(event_rx.iter().count(), 0);
1410 }
1411
1412 struct InMemoryPersistentDataStoreFactory {
1413 data: AllData<Flag, Segment>,
1414 initialized: bool,
1415 }
1416
1417 impl PersistentDataStoreFactory for InMemoryPersistentDataStoreFactory {
1418 fn create_persistent_data_store(
1419 &self,
1420 ) -> Result<Box<dyn PersistentDataStore + 'static>, std::io::Error> {
1421 let serialized_data =
1422 AllData::<SerializedItem, SerializedItem>::try_from(self.data.clone())?;
1423 Ok(Box::new(InMemoryPersistentDataStore {
1424 data: serialized_data,
1425 initialized: self.initialized,
1426 }))
1427 }
1428 }
1429
1430 #[test]
1431 fn variation_detail_handles_daemon_mode() {
1432 testing_logger::setup();
1433 let factory = InMemoryPersistentDataStoreFactory {
1434 data: AllData {
1435 flags: hashmap!["flag".into() => basic_flag("flag")],
1436 segments: HashMap::new(),
1437 },
1438 initialized: true,
1439 };
1440 let builder = PersistentDataStoreBuilder::new(Arc::new(factory));
1441
1442 let config = ConfigBuilder::new("sdk-key")
1443 .daemon_mode(true)
1444 .data_store(&builder)
1445 .event_processor(&NullEventProcessorBuilder::new())
1446 .build()
1447 .expect("config should build");
1448
1449 let client = Client::build(config).expect("Should be built.");
1450
1451 client.start_with_default_executor();
1452
1453 let context = ContextBuilder::new("bob")
1454 .build()
1455 .expect("Failed to create context");
1456
1457 let detail = client.variation_detail(&context, "flag", FlagValue::Bool(false));
1458
1459 assert!(detail.value.unwrap().as_bool().unwrap());
1460 assert!(matches!(
1461 detail.reason,
1462 Reason::Fallthrough {
1463 in_experiment: false
1464 }
1465 ));
1466 client.flush();
1467 client.close();
1468
1469 testing_logger::validate(|captured_logs| {
1470 assert_eq!(captured_logs.len(), 1);
1471 assert_eq!(
1472 captured_logs[0].body,
1473 "Started LaunchDarkly Client in daemon mode"
1474 );
1475 });
1476 }
1477
1478 #[test]
1479 fn daemon_mode_is_quiet_if_store_is_not_initialized() {
1480 testing_logger::setup();
1481
1482 let factory = InMemoryPersistentDataStoreFactory {
1483 data: AllData {
1484 flags: HashMap::new(),
1485 segments: HashMap::new(),
1486 },
1487 initialized: false,
1488 };
1489 let builder = PersistentDataStoreBuilder::new(Arc::new(factory));
1490
1491 let config = ConfigBuilder::new("sdk-key")
1492 .daemon_mode(true)
1493 .data_store(&builder)
1494 .event_processor(&NullEventProcessorBuilder::new())
1495 .build()
1496 .expect("config should build");
1497
1498 let client = Client::build(config).expect("Should be built.");
1499
1500 client.start_with_default_executor();
1501
1502 let context = ContextBuilder::new("bob")
1503 .build()
1504 .expect("Failed to create context");
1505
1506 client.variation_detail(&context, "flag", FlagValue::Bool(false));
1507
1508 testing_logger::validate(|captured_logs| {
1509 assert_eq!(captured_logs.len(), 1);
1510 assert_eq!(
1511 captured_logs[0].body,
1512 "Started LaunchDarkly Client in daemon mode"
1513 );
1514 });
1515 }
1516
1517 #[tokio::test]
1518 async fn variation_handles_off_flag_without_variation() {
1519 let td = TestData::new();
1520 td.use_preconfigured_flag(basic_off_flag("myFlag"));
1521 let (client, event_rx) = make_client_with_test_data(&td);
1522 client.start_with_default_executor();
1523
1524 let context = ContextBuilder::new("bob")
1525 .build()
1526 .expect("Failed to create context");
1527
1528 let result = client.variation(&context, "myFlag", FlagValue::Bool(false));
1529
1530 assert!(!result.as_bool().unwrap());
1531 client.flush();
1532 client.close();
1533
1534 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1535 assert_eq!(events.len(), 2);
1536 assert_eq!(events[0].kind(), "index");
1537 assert_eq!(events[1].kind(), "summary");
1538
1539 if let OutputEvent::Summary(event_summary) = events[1].clone() {
1540 let variation_key = VariationKey {
1541 version: Some(1),
1542 variation: None,
1543 };
1544 let feature = event_summary.features.get("myFlag");
1545 assert!(feature.is_some());
1546
1547 let feature = feature.unwrap();
1548 assert!(feature.counters.contains_key(&variation_key));
1549 } else {
1550 panic!("Event should be a summary type");
1551 }
1552 }
1553
1554 #[tokio::test]
1555 async fn variation_detail_tracks_prereq_events_correctly() {
1556 let td = TestData::new();
1557 let mut prereq_flag = basic_flag("prereqFlag");
1558 prereq_flag.track_events = true;
1559 td.use_preconfigured_flag(prereq_flag);
1560
1561 let mut main_flag = basic_flag_with_prereq("myFlag", "prereqFlag");
1562 main_flag.track_events = true;
1563 td.use_preconfigured_flag(main_flag);
1564
1565 let (client, event_rx) = make_client_with_test_data(&td);
1566 client.start_with_default_executor();
1567
1568 let context = ContextBuilder::new("bob")
1569 .build()
1570 .expect("Failed to create context");
1571
1572 let detail = client.variation_detail(&context, "myFlag", FlagValue::Bool(false));
1573
1574 assert!(detail.value.unwrap().as_bool().unwrap());
1575 assert!(matches!(
1576 detail.reason,
1577 Reason::Fallthrough {
1578 in_experiment: false
1579 }
1580 ));
1581 client.flush();
1582 client.close();
1583
1584 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1585 assert_eq!(events.len(), 4);
1586 assert_eq!(events[0].kind(), "index");
1587 assert_eq!(events[1].kind(), "feature");
1588 assert_eq!(events[2].kind(), "feature");
1589 assert_eq!(events[3].kind(), "summary");
1590
1591 if let OutputEvent::Summary(event_summary) = events[3].clone() {
1592 let variation_key = VariationKey {
1593 version: Some(1),
1594 variation: Some(1),
1595 };
1596 let feature = event_summary.features.get("myFlag");
1597 assert!(feature.is_some());
1598
1599 let feature = feature.unwrap();
1600 assert!(feature.counters.contains_key(&variation_key));
1601
1602 let variation_key = VariationKey {
1603 version: Some(1),
1604 variation: Some(1),
1605 };
1606 let feature = event_summary.features.get("prereqFlag");
1607 assert!(feature.is_some());
1608
1609 let feature = feature.unwrap();
1610 assert!(feature.counters.contains_key(&variation_key));
1611 }
1612 }
1613
1614 #[tokio::test]
1615 async fn variation_handles_failed_prereqs_correctly() {
1616 let td = TestData::new();
1617 let mut prereq_flag = basic_off_flag("prereqFlag");
1618 prereq_flag.track_events = true;
1619 td.use_preconfigured_flag(prereq_flag);
1620
1621 let mut main_flag = basic_flag_with_prereq("myFlag", "prereqFlag");
1622 main_flag.track_events = true;
1623 td.use_preconfigured_flag(main_flag);
1624
1625 let (client, event_rx) = make_client_with_test_data(&td);
1626 client.start_with_default_executor();
1627
1628 let context = ContextBuilder::new("bob")
1629 .build()
1630 .expect("Failed to create context");
1631
1632 let detail = client.variation(&context, "myFlag", FlagValue::Bool(false));
1633
1634 assert!(!detail.as_bool().unwrap());
1635 client.flush();
1636 client.close();
1637
1638 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1639 assert_eq!(events.len(), 4);
1640 assert_eq!(events[0].kind(), "index");
1641 assert_eq!(events[1].kind(), "feature");
1642 assert_eq!(events[2].kind(), "feature");
1643 assert_eq!(events[3].kind(), "summary");
1644
1645 if let OutputEvent::Summary(event_summary) = events[3].clone() {
1646 let variation_key = VariationKey {
1647 version: Some(1),
1648 variation: Some(0),
1649 };
1650 let feature = event_summary.features.get("myFlag");
1651 assert!(feature.is_some());
1652
1653 let feature = feature.unwrap();
1654 assert!(feature.counters.contains_key(&variation_key));
1655
1656 let variation_key = VariationKey {
1657 version: Some(1),
1658 variation: None,
1659 };
1660 let feature = event_summary.features.get("prereqFlag");
1661 assert!(feature.is_some());
1662
1663 let feature = feature.unwrap();
1664 assert!(feature.counters.contains_key(&variation_key));
1665 }
1666 }
1667
1668 #[test]
1669 fn variation_detail_handles_flag_not_found() {
1670 let (client, event_rx) = make_mocked_client();
1671 client.start_with_default_executor();
1672
1673 let context = ContextBuilder::new("bob")
1674 .build()
1675 .expect("Failed to create context");
1676 let detail = client.variation_detail(&context, "non-existent-flag", FlagValue::Bool(false));
1677
1678 assert!(!detail.value.unwrap().as_bool().unwrap());
1679 assert!(matches!(
1680 detail.reason,
1681 Reason::Error {
1682 error: eval::Error::FlagNotFound
1683 }
1684 ));
1685 client.flush();
1686 client.close();
1687
1688 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1689 assert_eq!(events.len(), 2);
1690 assert_eq!(events[0].kind(), "index");
1691 assert_eq!(events[1].kind(), "summary");
1692
1693 if let OutputEvent::Summary(event_summary) = events[1].clone() {
1694 let variation_key = VariationKey {
1695 version: None,
1696 variation: None,
1697 };
1698 let feature = event_summary.features.get("non-existent-flag");
1699 assert!(feature.is_some());
1700
1701 let feature = feature.unwrap();
1702 assert!(feature.counters.contains_key(&variation_key));
1703 } else {
1704 panic!("Event should be a summary type");
1705 }
1706 }
1707
1708 #[tokio::test]
1709 async fn variation_detail_handles_client_not_ready() {
1710 let (client, event_rx) = make_mocked_client_with_delay(u64::MAX, false, false);
1711 client.start_with_default_executor();
1712 let context = ContextBuilder::new("bob")
1713 .build()
1714 .expect("Failed to create context");
1715
1716 let detail = client.variation_detail(&context, "non-existent-flag", FlagValue::Bool(false));
1717
1718 assert!(!detail.value.unwrap().as_bool().unwrap());
1719 assert!(matches!(
1720 detail.reason,
1721 Reason::Error {
1722 error: eval::Error::ClientNotReady
1723 }
1724 ));
1725 client.flush();
1726 client.close();
1727
1728 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1729 assert_eq!(events.len(), 2);
1730 assert_eq!(events[0].kind(), "index");
1731 assert_eq!(events[1].kind(), "summary");
1732
1733 if let OutputEvent::Summary(event_summary) = events[1].clone() {
1734 let variation_key = VariationKey {
1735 version: None,
1736 variation: None,
1737 };
1738 let feature = event_summary.features.get("non-existent-flag");
1739 assert!(feature.is_some());
1740
1741 let feature = feature.unwrap();
1742 assert!(feature.counters.contains_key(&variation_key));
1743 } else {
1744 panic!("Event should be a summary type");
1745 }
1746 }
1747
1748 #[test]
1749 fn identify_sends_identify_event() {
1750 let (client, event_rx) = make_mocked_client();
1751 client.start_with_default_executor();
1752
1753 let context = ContextBuilder::new("bob")
1754 .build()
1755 .expect("Failed to create context");
1756
1757 client.identify(context);
1758 client.flush();
1759 client.close();
1760
1761 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1762 assert_eq!(events.len(), 1);
1763 assert_eq!(events[0].kind(), "identify");
1764 }
1765
1766 #[test]
1767 fn identify_sends_sends_nothing_in_offline_mode() {
1768 let (client, event_rx) = make_mocked_offline_client();
1769 client.start_with_default_executor();
1770
1771 let context = ContextBuilder::new("bob")
1772 .build()
1773 .expect("Failed to create context");
1774
1775 client.identify(context);
1776 client.flush();
1777 client.close();
1778
1779 assert_eq!(event_rx.iter().count(), 0);
1780 }
1781
1782 #[test]
1783 #[cfg(any(feature = "crypto-aws-lc-rs", feature = "crypto-openssl"))]
1784 fn secure_mode_hash() {
1785 let config = ConfigBuilder::new("secret")
1786 .offline(true)
1787 .build()
1788 .expect("config should build");
1789 let client = Client::build(config).expect("Should be built.");
1790 let context = ContextBuilder::new("Message")
1791 .build()
1792 .expect("Failed to create context");
1793
1794 assert_eq!(
1795 client
1796 .secure_mode_hash(&context)
1797 .expect("Hash should be computed"),
1798 "aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597"
1799 );
1800 }
1801
1802 #[test]
1803 #[cfg(any(feature = "crypto-aws-lc-rs", feature = "crypto-openssl"))]
1804 fn secure_mode_hash_with_multi_kind() {
1805 let config = ConfigBuilder::new("secret")
1806 .offline(true)
1807 .build()
1808 .expect("config should build");
1809 let client = Client::build(config).expect("Should be built.");
1810
1811 let org = ContextBuilder::new("org-key|1")
1812 .kind("org")
1813 .build()
1814 .expect("Failed to create context");
1815 let user = ContextBuilder::new("user-key:2")
1816 .build()
1817 .expect("Failed to create context");
1818
1819 let context = MultiContextBuilder::new()
1820 .add_context(org)
1821 .add_context(user)
1822 .build()
1823 .expect("failed to build multi-context");
1824
1825 assert_eq!(
1826 client
1827 .secure_mode_hash(&context)
1828 .expect("Hash should be computed"),
1829 "5687e6383b920582ed50c2a96c98a115f1b6aad85a60579d761d9b8797415163"
1830 );
1831 }
1832
1833 #[derive(Serialize)]
1834 struct MyCustomData {
1835 pub answer: u32,
1836 }
1837
1838 #[test]
1839 fn track_sends_track_and_index_events() -> serde_json::Result<()> {
1840 let (client, event_rx) = make_mocked_client();
1841 client.start_with_default_executor();
1842
1843 let context = ContextBuilder::new("bob")
1844 .build()
1845 .expect("Failed to create context");
1846
1847 client.track_event(context.clone(), "event-with-null");
1848 client.track_data(context.clone(), "event-with-string", "string-data")?;
1849 client.track_data(context.clone(), "event-with-json", json!({"answer": 42}))?;
1850 client.track_data(
1851 context.clone(),
1852 "event-with-struct",
1853 MyCustomData { answer: 42 },
1854 )?;
1855 client.track_metric(context, "event-with-metric", 42.0, serde_json::Value::Null);
1856
1857 client.flush();
1858 client.close();
1859
1860 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
1861 assert_eq!(events.len(), 6);
1862
1863 let mut events_by_type: HashMap<&str, usize> = HashMap::new();
1864 for event in events {
1865 if let Some(count) = events_by_type.get_mut(event.kind()) {
1866 *count += 1;
1867 } else {
1868 events_by_type.insert(event.kind(), 1);
1869 }
1870 }
1871 assert!(matches!(events_by_type.get("index"), Some(1)));
1872 assert!(matches!(events_by_type.get("custom"), Some(5)));
1873
1874 Ok(())
1875 }
1876
1877 #[test]
1878 fn track_sends_nothing_in_offline_mode() -> serde_json::Result<()> {
1879 let (client, event_rx) = make_mocked_offline_client();
1880 client.start_with_default_executor();
1881
1882 let context = ContextBuilder::new("bob")
1883 .build()
1884 .expect("Failed to create context");
1885
1886 client.track_event(context.clone(), "event-with-null");
1887 client.track_data(context.clone(), "event-with-string", "string-data")?;
1888 client.track_data(context.clone(), "event-with-json", json!({"answer": 42}))?;
1889 client.track_data(
1890 context.clone(),
1891 "event-with-struct",
1892 MyCustomData { answer: 42 },
1893 )?;
1894 client.track_metric(context, "event-with-metric", 42.0, serde_json::Value::Null);
1895
1896 client.flush();
1897 client.close();
1898
1899 assert_eq!(event_rx.iter().count(), 0);
1900
1901 Ok(())
1902 }
1903
1904 #[test]
1905 fn migration_handles_flag_not_found() {
1906 let (client, _event_rx) = make_mocked_client();
1907 client.start_with_default_executor();
1908
1909 let context = ContextBuilder::new("bob")
1910 .build()
1911 .expect("Failed to create context");
1912
1913 let (stage, _tracker) =
1914 client.migration_variation(&context, "non-existent-flag-key", Stage::Off);
1915
1916 assert_eq!(stage, Stage::Off);
1917 }
1918
1919 #[tokio::test]
1920 async fn migration_uses_non_migration_flag() {
1921 let td = TestData::new();
1922 td.use_preconfigured_flag(basic_flag("boolean-flag"));
1923 let (client, _event_rx) = make_client_with_test_data(&td);
1924 client.start_with_default_executor();
1925
1926 let context = ContextBuilder::new("bob")
1927 .build()
1928 .expect("Failed to create context");
1929
1930 let (stage, _tracker) = client.migration_variation(&context, "boolean-flag", Stage::Off);
1931
1932 assert_eq!(stage, Stage::Off);
1933 }
1934
1935 #[test_case(Stage::Off)]
1936 #[test_case(Stage::DualWrite)]
1937 #[test_case(Stage::Shadow)]
1938 #[test_case(Stage::Live)]
1939 #[test_case(Stage::Rampdown)]
1940 #[test_case(Stage::Complete)]
1941 #[tokio::test]
1942 async fn migration_can_determine_correct_stage_from_flag(stage: Stage) {
1943 let td = TestData::new();
1944 td.use_preconfigured_flag(basic_migration_flag("stage-flag", stage));
1945 let (client, _event_rx) = make_client_with_test_data(&td);
1946 client.start_with_default_executor();
1947
1948 let context = ContextBuilder::new("bob")
1949 .build()
1950 .expect("Failed to create context");
1951
1952 let (evaluated_stage, _tracker) =
1953 client.migration_variation(&context, "stage-flag", Stage::Off);
1954
1955 assert_eq!(evaluated_stage, stage);
1956 }
1957
1958 #[tokio::test]
1959 async fn migration_tracks_invoked_correctly() {
1960 migration_tracks_invoked_correctly_driver(Stage::Off, Operation::Read, vec![Origin::Old])
1961 .await;
1962 migration_tracks_invoked_correctly_driver(
1963 Stage::DualWrite,
1964 Operation::Read,
1965 vec![Origin::Old],
1966 )
1967 .await;
1968 migration_tracks_invoked_correctly_driver(
1969 Stage::Shadow,
1970 Operation::Read,
1971 vec![Origin::Old, Origin::New],
1972 )
1973 .await;
1974 migration_tracks_invoked_correctly_driver(
1975 Stage::Live,
1976 Operation::Read,
1977 vec![Origin::Old, Origin::New],
1978 )
1979 .await;
1980 migration_tracks_invoked_correctly_driver(
1981 Stage::Rampdown,
1982 Operation::Read,
1983 vec![Origin::New],
1984 )
1985 .await;
1986 migration_tracks_invoked_correctly_driver(
1987 Stage::Complete,
1988 Operation::Read,
1989 vec![Origin::New],
1990 )
1991 .await;
1992 migration_tracks_invoked_correctly_driver(Stage::Off, Operation::Write, vec![Origin::Old])
1993 .await;
1994 migration_tracks_invoked_correctly_driver(
1995 Stage::DualWrite,
1996 Operation::Write,
1997 vec![Origin::Old, Origin::New],
1998 )
1999 .await;
2000 migration_tracks_invoked_correctly_driver(
2001 Stage::Shadow,
2002 Operation::Write,
2003 vec![Origin::Old, Origin::New],
2004 )
2005 .await;
2006 migration_tracks_invoked_correctly_driver(
2007 Stage::Live,
2008 Operation::Write,
2009 vec![Origin::Old, Origin::New],
2010 )
2011 .await;
2012 migration_tracks_invoked_correctly_driver(
2013 Stage::Rampdown,
2014 Operation::Write,
2015 vec![Origin::Old, Origin::New],
2016 )
2017 .await;
2018 migration_tracks_invoked_correctly_driver(
2019 Stage::Complete,
2020 Operation::Write,
2021 vec![Origin::New],
2022 )
2023 .await;
2024 }
2025
2026 async fn migration_tracks_invoked_correctly_driver(
2027 stage: Stage,
2028 operation: Operation,
2029 origins: Vec<Origin>,
2030 ) {
2031 let td = TestData::new();
2032 td.use_preconfigured_flag(basic_migration_flag("stage-flag", stage));
2033 let (client, event_rx) = make_client_with_test_data(&td);
2034 let client = Arc::new(client);
2035 client.start_with_default_executor();
2036
2037 let mut migrator = MigratorBuilder::new(client.clone())
2038 .read(
2039 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2040 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2041 Some(|_, _| true),
2042 )
2043 .write(
2044 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2045 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2046 )
2047 .build()
2048 .expect("migrator should build");
2049
2050 let context = ContextBuilder::new("bob")
2051 .build()
2052 .expect("Failed to create context");
2053
2054 if let Operation::Read = operation {
2055 migrator
2056 .read(
2057 &context,
2058 "stage-flag".into(),
2059 Stage::Off,
2060 serde_json::Value::Null,
2061 )
2062 .await;
2063 } else {
2064 migrator
2065 .write(
2066 &context,
2067 "stage-flag".into(),
2068 Stage::Off,
2069 serde_json::Value::Null,
2070 )
2071 .await;
2072 }
2073
2074 client.flush();
2075 client.close();
2076
2077 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2078 assert_eq!(events.len(), 3);
2079 match &events[1] {
2080 OutputEvent::MigrationOp(event) => {
2081 assert!(event.invoked.len() == origins.len());
2082 assert!(event.invoked.iter().all(|i| origins.contains(i)));
2083 }
2084 _ => panic!("Expected migration event"),
2085 }
2086 }
2087
2088 #[tokio::test]
2089 async fn migration_tracks_latency() {
2090 migration_tracks_latency_driver(Stage::Off, Operation::Read, vec![Origin::Old]).await;
2091 migration_tracks_latency_driver(Stage::DualWrite, Operation::Read, vec![Origin::Old]).await;
2092 migration_tracks_latency_driver(
2093 Stage::Shadow,
2094 Operation::Read,
2095 vec![Origin::Old, Origin::New],
2096 )
2097 .await;
2098 migration_tracks_latency_driver(
2099 Stage::Live,
2100 Operation::Read,
2101 vec![Origin::Old, Origin::New],
2102 )
2103 .await;
2104 migration_tracks_latency_driver(Stage::Rampdown, Operation::Read, vec![Origin::New]).await;
2105 migration_tracks_latency_driver(Stage::Complete, Operation::Read, vec![Origin::New]).await;
2106 migration_tracks_latency_driver(Stage::Off, Operation::Write, vec![Origin::Old]).await;
2107 migration_tracks_latency_driver(
2108 Stage::DualWrite,
2109 Operation::Write,
2110 vec![Origin::Old, Origin::New],
2111 )
2112 .await;
2113 migration_tracks_latency_driver(
2114 Stage::Shadow,
2115 Operation::Write,
2116 vec![Origin::Old, Origin::New],
2117 )
2118 .await;
2119 migration_tracks_latency_driver(
2120 Stage::Live,
2121 Operation::Write,
2122 vec![Origin::Old, Origin::New],
2123 )
2124 .await;
2125 migration_tracks_latency_driver(
2126 Stage::Rampdown,
2127 Operation::Write,
2128 vec![Origin::Old, Origin::New],
2129 )
2130 .await;
2131 migration_tracks_latency_driver(Stage::Complete, Operation::Write, vec![Origin::New]).await;
2132 }
2133
2134 async fn migration_tracks_latency_driver(
2135 stage: Stage,
2136 operation: Operation,
2137 origins: Vec<Origin>,
2138 ) {
2139 let td = TestData::new();
2140 td.use_preconfigured_flag(basic_migration_flag("stage-flag", stage));
2141 let (client, event_rx) = make_client_with_test_data(&td);
2142 let client = Arc::new(client);
2143 client.start_with_default_executor();
2144
2145 let mut migrator = MigratorBuilder::new(client.clone())
2146 .track_latency(true)
2147 .read(
2148 |_| {
2149 async move {
2150 async_std::task::sleep(Duration::from_millis(100)).await;
2151 Ok(serde_json::Value::Null)
2152 }
2153 .boxed()
2154 },
2155 |_| {
2156 async move {
2157 async_std::task::sleep(Duration::from_millis(100)).await;
2158 Ok(serde_json::Value::Null)
2159 }
2160 .boxed()
2161 },
2162 Some(|_, _| true),
2163 )
2164 .write(
2165 |_| {
2166 async move {
2167 async_std::task::sleep(Duration::from_millis(100)).await;
2168 Ok(serde_json::Value::Null)
2169 }
2170 .boxed()
2171 },
2172 |_| {
2173 async move {
2174 async_std::task::sleep(Duration::from_millis(100)).await;
2175 Ok(serde_json::Value::Null)
2176 }
2177 .boxed()
2178 },
2179 )
2180 .build()
2181 .expect("migrator should build");
2182
2183 let context = ContextBuilder::new("bob")
2184 .build()
2185 .expect("Failed to create context");
2186
2187 if let Operation::Read = operation {
2188 migrator
2189 .read(
2190 &context,
2191 "stage-flag".into(),
2192 Stage::Off,
2193 serde_json::Value::Null,
2194 )
2195 .await;
2196 } else {
2197 migrator
2198 .write(
2199 &context,
2200 "stage-flag".into(),
2201 Stage::Off,
2202 serde_json::Value::Null,
2203 )
2204 .await;
2205 }
2206
2207 client.flush();
2208 client.close();
2209
2210 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2211 assert_eq!(events.len(), 3);
2212 match &events[1] {
2213 OutputEvent::MigrationOp(event) => {
2214 assert!(event.latency.len() == origins.len());
2215 assert!(event
2216 .latency
2217 .values()
2218 .all(|l| l > &Duration::from_millis(100)));
2219 }
2220 _ => panic!("Expected migration event"),
2221 }
2222 }
2223
2224 #[tokio::test]
2225 async fn migration_tracks_read_errors() {
2226 migration_tracks_read_errors_driver(Stage::Off, vec![Origin::Old]).await;
2227 migration_tracks_read_errors_driver(Stage::DualWrite, vec![Origin::Old]).await;
2228 migration_tracks_read_errors_driver(Stage::Shadow, vec![Origin::Old, Origin::New]).await;
2229 migration_tracks_read_errors_driver(Stage::Live, vec![Origin::Old, Origin::New]).await;
2230 migration_tracks_read_errors_driver(Stage::Rampdown, vec![Origin::New]).await;
2231 migration_tracks_read_errors_driver(Stage::Complete, vec![Origin::New]).await;
2232 }
2233
2234 async fn migration_tracks_read_errors_driver(stage: Stage, origins: Vec<Origin>) {
2235 let td = TestData::new();
2236 td.use_preconfigured_flag(basic_migration_flag("stage-flag", stage));
2237 let (client, event_rx) = make_client_with_test_data(&td);
2238 let client = Arc::new(client);
2239 client.start_with_default_executor();
2240
2241 let mut migrator = MigratorBuilder::new(client.clone())
2242 .track_latency(true)
2243 .read(
2244 |_| async move { Err("fail".into()) }.boxed(),
2245 |_| async move { Err("fail".into()) }.boxed(),
2246 Some(|_: &String, _: &String| true),
2247 )
2248 .write(
2249 |_| async move { Err("fail".into()) }.boxed(),
2250 |_| async move { Err("fail".into()) }.boxed(),
2251 )
2252 .build()
2253 .expect("migrator should build");
2254
2255 let context = ContextBuilder::new("bob")
2256 .build()
2257 .expect("Failed to create context");
2258
2259 migrator
2260 .read(
2261 &context,
2262 "stage-flag".into(),
2263 Stage::Off,
2264 serde_json::Value::Null,
2265 )
2266 .await;
2267 client.flush();
2268 client.close();
2269
2270 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2271 assert_eq!(events.len(), 3);
2272 match &events[1] {
2273 OutputEvent::MigrationOp(event) => {
2274 assert!(event.errors.len() == origins.len());
2275 assert!(event.errors.iter().all(|i| origins.contains(i)));
2276 }
2277 _ => panic!("Expected migration event"),
2278 }
2279 }
2280
2281 #[tokio::test]
2282 async fn migration_tracks_authoritative_write_errors() {
2283 migration_tracks_authoritative_write_errors_driver(Stage::Off, vec![Origin::Old]).await;
2284 migration_tracks_authoritative_write_errors_driver(Stage::DualWrite, vec![Origin::Old])
2285 .await;
2286 migration_tracks_authoritative_write_errors_driver(Stage::Shadow, vec![Origin::Old]).await;
2287 migration_tracks_authoritative_write_errors_driver(Stage::Live, vec![Origin::New]).await;
2288 migration_tracks_authoritative_write_errors_driver(Stage::Rampdown, vec![Origin::New])
2289 .await;
2290 migration_tracks_authoritative_write_errors_driver(Stage::Complete, vec![Origin::New])
2291 .await;
2292 }
2293
2294 async fn migration_tracks_authoritative_write_errors_driver(
2295 stage: Stage,
2296 origins: Vec<Origin>,
2297 ) {
2298 let td = TestData::new();
2299 td.use_preconfigured_flag(basic_migration_flag("stage-flag", stage));
2300 let (client, event_rx) = make_client_with_test_data(&td);
2301 let client = Arc::new(client);
2302 client.start_with_default_executor();
2303
2304 let mut migrator = MigratorBuilder::new(client.clone())
2305 .track_latency(true)
2306 .read(
2307 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2308 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2309 None,
2310 )
2311 .write(
2312 |_| async move { Err("fail".into()) }.boxed(),
2313 |_| async move { Err("fail".into()) }.boxed(),
2314 )
2315 .build()
2316 .expect("migrator should build");
2317
2318 let context = ContextBuilder::new("bob")
2319 .build()
2320 .expect("Failed to create context");
2321
2322 migrator
2323 .write(
2324 &context,
2325 "stage-flag".into(),
2326 Stage::Off,
2327 serde_json::Value::Null,
2328 )
2329 .await;
2330
2331 client.flush();
2332 client.close();
2333
2334 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2335 assert_eq!(events.len(), 3);
2336 match &events[1] {
2337 OutputEvent::MigrationOp(event) => {
2338 assert!(event.errors.len() == origins.len());
2339 assert!(event.errors.iter().all(|i| origins.contains(i)));
2340 }
2341 _ => panic!("Expected migration event"),
2342 }
2343 }
2344
2345 #[tokio::test]
2346 async fn migration_tracks_nonauthoritative_write_errors() {
2347 migration_tracks_nonauthoritative_write_errors_driver(
2348 Stage::DualWrite,
2349 false,
2350 true,
2351 vec![Origin::New],
2352 )
2353 .await;
2354 migration_tracks_nonauthoritative_write_errors_driver(
2355 Stage::Shadow,
2356 false,
2357 true,
2358 vec![Origin::New],
2359 )
2360 .await;
2361 migration_tracks_nonauthoritative_write_errors_driver(
2362 Stage::Live,
2363 true,
2364 false,
2365 vec![Origin::Old],
2366 )
2367 .await;
2368 migration_tracks_nonauthoritative_write_errors_driver(
2369 Stage::Rampdown,
2370 true,
2371 false,
2372 vec![Origin::Old],
2373 )
2374 .await;
2375 }
2376
2377 async fn migration_tracks_nonauthoritative_write_errors_driver(
2378 stage: Stage,
2379 fail_old: bool,
2380 fail_new: bool,
2381 origins: Vec<Origin>,
2382 ) {
2383 let td = TestData::new();
2384 td.use_preconfigured_flag(basic_migration_flag("stage-flag", stage));
2385 let (client, event_rx) = make_client_with_test_data(&td);
2386 let client = Arc::new(client);
2387 client.start_with_default_executor();
2388
2389 let mut migrator = MigratorBuilder::new(client.clone())
2390 .track_latency(true)
2391 .read(
2392 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2393 |_| async move { Ok(serde_json::Value::Null) }.boxed(),
2394 None,
2395 )
2396 .write(
2397 move |_| {
2398 async move {
2399 if fail_old {
2400 Err("fail".into())
2401 } else {
2402 Ok(serde_json::Value::Null)
2403 }
2404 }
2405 .boxed()
2406 },
2407 move |_| {
2408 async move {
2409 if fail_new {
2410 Err("fail".into())
2411 } else {
2412 Ok(serde_json::Value::Null)
2413 }
2414 }
2415 .boxed()
2416 },
2417 )
2418 .build()
2419 .expect("migrator should build");
2420
2421 let context = ContextBuilder::new("bob")
2422 .build()
2423 .expect("Failed to create context");
2424
2425 migrator
2426 .write(
2427 &context,
2428 "stage-flag".into(),
2429 Stage::Off,
2430 serde_json::Value::Null,
2431 )
2432 .await;
2433
2434 client.flush();
2435 client.close();
2436
2437 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2438 assert_eq!(events.len(), 3);
2439 match &events[1] {
2440 OutputEvent::MigrationOp(event) => {
2441 assert!(event.errors.len() == origins.len());
2442 assert!(event.errors.iter().all(|i| origins.contains(i)));
2443 }
2444 _ => panic!("Expected migration event"),
2445 }
2446 }
2447
2448 #[tokio::test]
2449 async fn migration_tracks_consistency() {
2450 migration_tracks_consistency_driver(Stage::Shadow, "same", "same", true).await;
2451 migration_tracks_consistency_driver(Stage::Shadow, "same", "different", false).await;
2452 migration_tracks_consistency_driver(Stage::Live, "same", "same", true).await;
2453 migration_tracks_consistency_driver(Stage::Live, "same", "different", false).await;
2454 }
2455
2456 async fn migration_tracks_consistency_driver(
2457 stage: Stage,
2458 old_return: &'static str,
2459 new_return: &'static str,
2460 expected_consistency: bool,
2461 ) {
2462 let td = TestData::new();
2463 td.use_preconfigured_flag(basic_migration_flag("stage-flag", stage));
2464 let (client, event_rx) = make_client_with_test_data(&td);
2465 let client = Arc::new(client);
2466 client.start_with_default_executor();
2467
2468 let mut migrator = MigratorBuilder::new(client.clone())
2469 .track_latency(true)
2470 .read(
2471 |_| {
2472 async move {
2473 async_std::task::sleep(Duration::from_millis(100)).await;
2474 Ok(serde_json::Value::String(old_return.to_string()))
2475 }
2476 .boxed()
2477 },
2478 |_| {
2479 async move {
2480 async_std::task::sleep(Duration::from_millis(100)).await;
2481 Ok(serde_json::Value::String(new_return.to_string()))
2482 }
2483 .boxed()
2484 },
2485 Some(|lhs, rhs| lhs == rhs),
2486 )
2487 .write(
2488 |_| {
2489 async move {
2490 async_std::task::sleep(Duration::from_millis(100)).await;
2491 Ok(serde_json::Value::Null)
2492 }
2493 .boxed()
2494 },
2495 |_| {
2496 async move {
2497 async_std::task::sleep(Duration::from_millis(100)).await;
2498 Ok(serde_json::Value::Null)
2499 }
2500 .boxed()
2501 },
2502 )
2503 .build()
2504 .expect("migrator should build");
2505
2506 let context = ContextBuilder::new("bob")
2507 .build()
2508 .expect("Failed to create context");
2509
2510 migrator
2511 .read(
2512 &context,
2513 "stage-flag".into(),
2514 Stage::Off,
2515 serde_json::Value::Null,
2516 )
2517 .await;
2518
2519 client.flush();
2520 client.close();
2521
2522 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2523 assert_eq!(events.len(), 3);
2524 match &events[1] {
2525 OutputEvent::MigrationOp(event) => {
2526 assert!(event.consistency_check == Some(expected_consistency))
2527 }
2528 _ => panic!("Expected migration event"),
2529 }
2530 }
2531
2532 #[tokio::test]
2533 async fn client_flush_blocking_completes_successfully() {
2534 let (client, event_rx) = make_mocked_client();
2535 client.start_with_default_executor();
2536 client.wait_for_initialization(Duration::from_secs(1)).await;
2537
2538 let context = ContextBuilder::new("user-key")
2539 .build()
2540 .expect("Failed to create context");
2541
2542 client.identify(context);
2543
2544 let result = client.flush_blocking(Duration::from_secs(5)).await;
2545 assert!(result, "flush_blocking should complete successfully");
2546
2547 client.close();
2548
2549 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2550 assert!(!events.is_empty(), "Should have received identify event");
2551 }
2552
2553 #[tokio::test]
2554 async fn client_flush_blocking_with_zero_timeout() {
2555 let (client, event_rx) = make_mocked_client();
2556 client.start_with_default_executor();
2557 client.wait_for_initialization(Duration::from_secs(1)).await;
2558
2559 let context = ContextBuilder::new("user-key")
2560 .build()
2561 .expect("Failed to create context");
2562
2563 client.identify(context);
2564
2565 let result = client.flush_blocking(Duration::ZERO).await;
2566 assert!(
2567 result,
2568 "flush_blocking with zero timeout should complete successfully"
2569 );
2570
2571 client.close();
2572
2573 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2574 assert!(!events.is_empty(), "Should have received identify event");
2575 }
2576
2577 #[tokio::test]
2578 async fn client_flush_blocking_with_no_events() {
2579 let (client, _event_rx) = make_mocked_client();
2580 client.start_with_default_executor();
2581 client.wait_for_initialization(Duration::from_secs(1)).await;
2582
2583 let result = client.flush_blocking(Duration::from_secs(1)).await;
2584 assert!(
2585 result,
2586 "flush_blocking with no events should complete immediately"
2587 );
2588
2589 client.close();
2590 }
2591
2592 #[tokio::test]
2593 async fn client_flush_blocking_multiple_concurrent_calls() {
2594 let (client, event_rx) = make_mocked_client();
2595 client.start_with_default_executor();
2596 client.wait_for_initialization(Duration::from_secs(1)).await;
2597
2598 let context = ContextBuilder::new("user-key")
2599 .build()
2600 .expect("Failed to create context");
2601
2602 client.identify(context);
2603
2604 let (result1, result2, result3) = tokio::join!(
2606 client.flush_blocking(Duration::from_secs(5)),
2607 client.flush_blocking(Duration::from_secs(5)),
2608 client.flush_blocking(Duration::from_secs(5)),
2609 );
2610
2611 assert!(result1, "First flush_blocking should succeed");
2612 assert!(result2, "Second flush_blocking should succeed");
2613 assert!(result3, "Third flush_blocking should succeed");
2614
2615 client.close();
2616
2617 let events = event_rx.iter().collect::<Vec<OutputEvent>>();
2618 assert!(!events.is_empty(), "Should have received identify event");
2619 }
2620
2621 fn make_mocked_client_with_delay(
2622 delay: u64,
2623 offline: bool,
2624 daemon_mode: bool,
2625 ) -> (Client, Receiver<OutputEvent>) {
2626 let updates = Arc::new(MockDataSource::new_with_init_delay(delay));
2627 let (event_sender, event_rx) = create_event_sender();
2628
2629 let config = ConfigBuilder::new("sdk-key")
2630 .offline(offline)
2631 .daemon_mode(daemon_mode)
2632 .data_source(MockDataSourceBuilder::new().data_source(updates))
2633 .event_processor(
2634 EventProcessorBuilder::<launchdarkly_sdk_transport::HyperTransport>::new()
2635 .event_sender(Arc::new(event_sender)),
2636 )
2637 .build()
2638 .expect("config should build");
2639
2640 let client = Client::build(config).expect("Should be built.");
2641
2642 (client, event_rx)
2643 }
2644
2645 fn make_mocked_offline_client() -> (Client, Receiver<OutputEvent>) {
2646 make_mocked_client_with_delay(0, true, false)
2647 }
2648
2649 fn make_mocked_client() -> (Client, Receiver<OutputEvent>) {
2650 make_mocked_client_with_delay(0, false, false)
2651 }
2652
2653 fn make_client_with_test_data(td: &TestData) -> (Client, Receiver<OutputEvent>) {
2654 let (event_sender, event_rx) = create_event_sender();
2655 let config = ConfigBuilder::new("sdk-key")
2656 .data_source(td)
2657 .event_processor(
2658 EventProcessorBuilder::<launchdarkly_sdk_transport::HyperTransport>::new()
2659 .event_sender(Arc::new(event_sender)),
2660 )
2661 .build()
2662 .expect("config should build");
2663 let client = Client::build(config).expect("Should be built.");
2664 (client, event_rx)
2665 }
2666
2667 fn make_client_with_test_data_fdv2(td: &TestData) -> Client {
2668 let mut data_system = DataSystemBuilder::custom();
2669 data_system.synchronizer(td.clone());
2670 let config = ConfigBuilder::new("sdk-key")
2671 .data_system(&data_system)
2672 .event_processor(&NullEventProcessorBuilder::new())
2673 .build()
2674 .expect("config should build");
2675 Client::build(config).expect("Should be built.")
2676 }
2677
2678 async fn wait_until(mut condition: impl FnMut() -> bool) {
2681 for _ in 0..100 {
2682 if condition() {
2683 return;
2684 }
2685 tokio::time::sleep(Duration::from_millis(10)).await;
2686 }
2687 panic!("condition was not met within the timeout");
2688 }
2689
2690 #[tokio::test]
2691 async fn fdv2_test_data_serves_and_updates_flags() {
2692 let td = TestData::new();
2693 td.update(FlagBuilder::new("my-flag").variation_for_all(true));
2694
2695 let client = make_client_with_test_data_fdv2(&td);
2696 client.start_with_default_executor();
2697 client.wait_for_initialization(Duration::from_secs(5)).await;
2698
2699 let context = ContextBuilder::new("user")
2700 .build()
2701 .expect("context should build");
2702
2703 assert!(client.bool_variation(&context, "my-flag", false));
2705
2706 td.update(FlagBuilder::new("my-flag").variation_for_all(false));
2708 wait_until(|| !client.bool_variation(&context, "my-flag", true)).await;
2709
2710 client.close();
2711 }
2712
2713 #[test]
2714 fn client_builds_successfully() {
2715 let config = ConfigBuilder::new("sdk-key")
2716 .offline(true)
2717 .build()
2718 .expect("config should build");
2719
2720 let client = Client::build(config).expect("client should build successfully");
2721
2722 assert!(
2723 !client.started.load(Ordering::SeqCst),
2724 "client should not be started yet"
2725 );
2726 assert!(client.offline, "client should be in offline mode");
2727 assert_eq!(client.sdk_key, "sdk-key", "sdk_key should match");
2728 }
2729}