#![allow(dead_code)] #![allow(clippy::type_complexity)] use chrono::{DateTime, Utc}; use configcat::OverrideBehavior::{LocalOnly, LocalOverRemote, RemoteOverLocal}; use configcat::{Client, FileDataSource, OverrideBehavior, User, UserValue}; use std::str::FromStr; #[tokio::test] async fn prerequisite_flag_overrides() { let tests: Vec<(&str, &str, &str, Option, Option<&str>)> = vec![ ("stringDependsOnString", "1", "john@sensitivecompany.com", None, Some("Dog")), ("stringDependsOnString", "1", "john@sensitivecompany.com", Some(RemoteOverLocal), Some("Dog")), ("stringDependsOnString", "1", "john@sensitivecompany.com", Some(LocalOverRemote), Some("Dog")), ("stringDependsOnString", "1", "john@sensitivecompany.com", Some(LocalOnly), None), ("stringDependsOnString", "2", "john@notsensitivecompany.com", None, Some("Cat")), ("stringDependsOnString", "2", "john@notsensitivecompany.com", Some(RemoteOverLocal), Some("Cat")), ("stringDependsOnString", "2", "john@notsensitivecompany.com", Some(LocalOverRemote), Some("Dog")), ("stringDependsOnString", "2", "john@notsensitivecompany.com", Some(LocalOnly), None), ("stringDependsOnInt", "1", "john@sensitivecompany.com", None, Some("Dog")), ("stringDependsOnInt", "1", "john@sensitivecompany.com", Some(RemoteOverLocal), Some("Dog")), ("stringDependsOnInt", "1", "john@sensitivecompany.com", Some(LocalOverRemote), Some("Cat")), ("stringDependsOnInt", "1", "john@sensitivecompany.com", Some(LocalOnly), None), ("stringDependsOnInt", "2", "john@notsensitivecompany.com", None, Some("Cat")), ("stringDependsOnInt", "2", "john@notsensitivecompany.com", Some(RemoteOverLocal), Some("Cat")), ("stringDependsOnInt", "2", "john@notsensitivecompany.com", Some(LocalOverRemote), Some("Dog")), ("stringDependsOnInt", "2", "john@notsensitivecompany.com", Some(LocalOnly), None), ]; for test in tests { let mut builder = Client::builder("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/JoGwdqJZQ0K2xDy7LnbyOg"); if test.3.is_some() { builder = builder.overrides(Box::new(FileDataSource::new("tests/data/test_override_flagdependency_v6.json").unwrap()), test.3.unwrap()); } let client = builder.build().unwrap(); let user = User::new(test.1).email(test.2); let details = client.get_flag_details(test.0, Some(user)).await; if test.4.is_none() { assert!(details.value.is_none()); } else { assert_eq!(details.value.unwrap().as_str().unwrap(), test.4.unwrap()); } } } #[tokio::test] async fn segment_overrides() { let tests: Vec<(&str, &str, &str, Option, Option)> = vec![ ("developerAndBetaUserSegment", "1", "john@example.com", None, Some(false)), ("developerAndBetaUserSegment", "1", "john@example.com", Some(RemoteOverLocal), Some(false)), ("developerAndBetaUserSegment", "1", "john@example.com", Some(LocalOverRemote), Some(true)), ("developerAndBetaUserSegment", "1", "john@example.com", Some(LocalOnly), Some(true)), ("notDeveloperAndNotBetaUserSegment", "2", "kate@example.com", None, Some(true)), ("notDeveloperAndNotBetaUserSegment", "2", "kate@example.com", Some(RemoteOverLocal), Some(true)), ("notDeveloperAndNotBetaUserSegment", "2", "kate@example.com", Some(LocalOverRemote), Some(true)), ("notDeveloperAndNotBetaUserSegment", "2", "kate@example.com", Some(LocalOnly), None), ]; for test in tests { let mut builder = Client::builder("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/h99HYXWWNE2bH8eWyLAVMA"); if test.3.is_some() { builder = builder.overrides(Box::new(FileDataSource::new("tests/data/test_override_segments_v6.json").unwrap()), test.3.unwrap()); } let client = builder.build().unwrap(); let user = User::new(test.1).email(test.2); let details = client.get_flag_details(test.0, Some(user)).await; if test.4.is_none() { assert!(details.value.is_none()); } else { assert_eq!(details.value.unwrap().as_bool().unwrap(), test.4.unwrap()); } } } #[tokio::test] async fn matched_eval_rule_percentage_opts() { let tests: Vec<(&str, Option<&str>, Option<&str>, Option<&str>, &str, bool, bool)> = vec![ ("stringMatchedTargetingRuleAndOrPercentageOption", None, None, None, "Cat", false, false), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), None, None, "Cat", false, false), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), Some("a@example.com"), None, "Dog", true, false), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), Some("a@configcat.com"), None, "Cat", false, false), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), Some("a@configcat.com"), Some(""), "Frog", true, true), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), Some("a@configcat.com"), Some("US"), "Fish", true, true), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), Some("b@configcat.com"), None, "Cat", false, false), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), Some("b@configcat.com"), Some(""), "Falcon", false, true), ("stringMatchedTargetingRuleAndOrPercentageOption", Some("12345"), Some("b@configcat.com"), Some("US"), "Spider", false, true), ]; let client = Client::new("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/P4e3fAz_1ky2-Zg2e4cbkw").unwrap(); for test in tests { let mut user = None; if test.1.is_some() { let mut u = User::new(test.1.unwrap()); if test.2.is_some() { u = u.email(test.2.unwrap()); } if test.3.is_some() { u = u.custom("PercentageBase", test.3.unwrap()); } user = Some(u); } let details = client.get_flag_details(test.0, user).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.4); assert_eq!(details.matched_targeting_rule.is_some(), test.5); assert_eq!(details.matched_percentage_option.is_some(), test.6); } } #[tokio::test] async fn comp_attr_canonical_str_representation() { let tests: Vec<(&str, UserValue, &str)> = vec![ ("numberToStringConversion", UserValue::Float(0.12345), "1"), ("numberToStringConversionInt", UserValue::Int(125), "4"), ("numberToStringConversionInt", UserValue::UInt(125), "4"), ("numberToStringConversionPositiveExp", UserValue::Float(-1.23456789e96), "2"), ("numberToStringConversionNegativeExp", UserValue::Float(-12345.6789E-100), "4"), ("numberToStringConversionNaN", UserValue::Float(f64::NAN), "3"), ("numberToStringConversionPositiveInf", UserValue::Float(f64::INFINITY), "4"), ("numberToStringConversionNegativeInf", UserValue::Float(f64::NEG_INFINITY), "3"), ("dateToStringConversion", DateTime::::from_str("2023-03-31T23:59:59.999Z").unwrap().into(), "3"), ("dateToStringConversion", UserValue::Float(1680307199.999), "3"), ("dateToStringConversionNaN", UserValue::Float(f64::NAN), "3"), ("dateToStringConversionPositiveInf", UserValue::Float(f64::INFINITY), "1"), ("dateToStringConversionNegativeInf", UserValue::Float(f64::NEG_INFINITY), "5"), ("stringArrayToStringConversion", vec!["read", "Write", " eXecute "].into(), "4"), ("stringArrayToStringConversionEmpty", UserValue::StringVec(vec![]), "5"), ("stringArrayToStringConversionSpecialChars", vec!["+<>%\"'\\/\t\r\n"].into(), "3"), ("stringArrayToStringConversionUnicode", vec!["äöüÄÖÜçéèñışğ⢙✓😀"].into(), "2"), ]; let client = Client::builder("local").overrides(Box::new(FileDataSource::new("tests/data/comparison_attribute_conversion.json").unwrap()), LocalOnly).build().unwrap(); for test in tests { let user = User::new("12345").custom("Custom1", test.1.clone()); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.2); } } #[tokio::test] async fn spec_chars() { let tests: Vec<(&str, &str, &str)> = vec![("specialCharacters", "äöüÄÖÜçéèñışğ⢙✓😀", "äöüÄÖÜçéèñışğ⢙✓😀"), ("specialCharactersHashed", "äöüÄÖÜçéèñışğ⢙✓😀", "äöüÄÖÜçéèñışğ⢙✓😀")]; let client = Client::new("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/u28_1qNyZ0Wz-ldYHIU7-g").unwrap(); for test in tests { let user = User::new(test.1); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.2); } } #[tokio::test] async fn attr_trim() { let tests: Vec<(&str, &str)> = vec![ ("isoneof", "no trim"), ("isnotoneof", "no trim"), ("isoneofhashed", "no trim"), ("isnotoneofhashed", "no trim"), ("equalshashed", "no trim"), ("notequalshashed", "no trim"), ("arraycontainsanyofhashed", "no trim"), ("arraynotcontainsanyofhashed", "no trim"), ("equals", "no trim"), ("notequals", "no trim"), ("startwithanyof", "no trim"), ("notstartwithanyof", "no trim"), ("endswithanyof", "no trim"), ("notendswithanyof", "no trim"), ("arraycontainsanyof", "no trim"), ("arraynotcontainsanyof", "no trim"), ("startwithanyofhashed", "no trim"), ("notstartwithanyofhashed", "no trim"), ("endswithanyofhashed", "no trim"), ("notendswithanyofhashed", "no trim"), ("semverisoneof", "4 trim"), ("semverisnotoneof", "5 trim"), ("semverless", "6 trim"), ("semverlessequals", "7 trim"), ("semvergreater", "8 trim"), ("semvergreaterequals", "9 trim"), ("numberequals", "10 trim"), ("numbernotequals", "11 trim"), ("numberless", "12 trim"), ("numberlessequals", "13 trim"), ("numbergreater", "14 trim"), ("numbergreaterequals", "15 trim"), ("datebefore", "18 trim"), ("dateafter", "19 trim"), ("containsanyof", "no trim"), ("notcontainsanyof", "no trim"), ]; let client = Client::builder("local").overrides(Box::new(FileDataSource::new("tests/data/comparison_attribute_trimming.json").unwrap()), LocalOnly).build().unwrap(); for test in tests { let user = User::new(" 12345 ").country("[\" USA \"]").custom("Version", " 1.0.0 ").custom("Number", " 3 ").custom("Date", " 1705253400 "); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.1, "{}", test.0); } } #[tokio::test] async fn comp_val_trim() { let tests: Vec<(&str, &str)> = vec![ ("isoneof", "no trim"), ("isnotoneof", "no trim"), ("containsanyof", "no trim"), ("notcontainsanyof", "no trim"), ("isoneofhashed", "no trim"), ("isnotoneofhashed", "no trim"), ("equalshashed", "no trim"), ("notequalshashed", "no trim"), ("arraycontainsanyofhashed", "no trim"), ("arraynotcontainsanyofhashed", "no trim"), ("equals", "no trim"), ("notequals", "no trim"), ("startwithanyof", "no trim"), ("notstartwithanyof", "no trim"), ("endswithanyof", "no trim"), ("notendswithanyof", "no trim"), ("arraycontainsanyof", "no trim"), ("arraynotcontainsanyof", "no trim"), ("startwithanyofhashed", "no trim"), ("notstartwithanyofhashed", "no trim"), ("endswithanyofhashed", "no trim"), ("notendswithanyofhashed", "no trim"), ("semverisoneof", "4 trim"), ("semverisnotoneof", "5 trim"), ("semverless", "6 trim"), ("semverlessequals", "7 trim"), ("semvergreater", "8 trim"), ("semvergreaterequals", "9 trim"), ]; let client = Client::builder("local").overrides(Box::new(FileDataSource::new("tests/data/comparison_value_trimming.json").unwrap()), LocalOnly).build().unwrap(); for test in tests { let user = User::new("12345").country("[\"USA\"]").custom("Version", "1.0.0").custom("Number", "3").custom("Date", "1705253400"); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.1, "{}", test.0); } } #[tokio::test] async fn attr_val_conv_str() { let tests: Vec<(&str, &str, &str, UserValue, &str)> = vec![ ("lessThanWithPercentage", "12345", "Custom1", "0.0".into(), "20%"), ("lessThanWithPercentage", "12345", "Custom1", "0.9.9".into(), "< 1.0.0"), ("lessThanWithPercentage", "12345", "Custom1", "1.0.0".into(), "20%"), ("lessThanWithPercentage", "12345", "Custom1", "1.1".into(), "20%"), ("lessThanWithPercentage", "12345", "Custom1", 0.into(), "20%"), ("lessThanWithPercentage", "12345", "Custom1", 0.9.into(), "20%"), ("lessThanWithPercentage", "12345", "Custom1", 2.into(), "20%"), ]; let client = Client::new("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/iV8vH2MBakKxkFZylxHmTg").unwrap(); for test in tests { let user = User::new(test.1).custom(test.2, test.3); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.4); } } #[tokio::test] async fn attr_val_conv_num() { let tests: Vec<(&str, &str, &str, UserValue, &str)> = vec![ ("numberWithPercentage", "12345", "Custom1", (-1_i8).into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 2_i8.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_i8.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_i8.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", 2_u8.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_u8.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_u8.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", 2_u16.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_u16.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_u16.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", (-1_i64).into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 2_i32.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_i32.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_i32.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", 2_u32.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_u32.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_u32.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", i32::MIN.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 2_i64.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_i64.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_i64.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", i64::MAX.into(), ">5"), ("numberWithPercentage", "12345", "Custom1", 2_u64.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_u64.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_u64.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", u64::MAX.into(), ">5"), ("numberWithPercentage", "12345", "Custom1", f64::NEG_INFINITY.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", (-1_f32).into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 2_f32.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 2.1_f32.into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", 3_f32.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", 5_f32.into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", f64::INFINITY.into(), ">5"), ("numberWithPercentage", "12345", "Custom1", f64::NAN.into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", "-Infinity".into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", "-1".into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", "2".into(), "<2.1"), ("numberWithPercentage", "12345", "Custom1", "2.1".into(), "<=2,1"), ("numberWithPercentage", "12345", "Custom1", "2,1".into(), "<=2,1"), ("numberWithPercentage", "12345", "Custom1", "3".into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", "5".into(), ">=5"), ("numberWithPercentage", "12345", "Custom1", "Infinity".into(), ">5"), ("numberWithPercentage", "12345", "Custom1", "NaN".into(), "<>4.2"), ("numberWithPercentage", "12345", "Custom1", "NaNa".into(), "80%"), ]; let client = Client::new("configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw").unwrap(); for test in tests { let user = User::new(test.1).custom(test.2, test.3); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.4); } } #[tokio::test] async fn attr_val_conv_date() { let tests: Vec<(&str, &str, &str, UserValue, bool)> = vec![ ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-03-31T23:59:59.999Z").unwrap().into(), false), ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-04-01T01:59:59.999+02:00").unwrap().into(), false), ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-04-01T00:00:00.001Z").unwrap().into(), true), ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-04-01T02:00:00.0010000+02:00").unwrap().into(), true), ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-04-30T23:59:59.999Z").unwrap().into(), true), ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-05-01T01:59:59.999+02:00").unwrap().into(), true), ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-05-01T00:00:00.001Z").unwrap().into(), false), ("boolTrueIn202304", "12345", "Custom1", DateTime::::from_str("2023-05-01T02:00:00.001+02:00").unwrap().into(), false), ("boolTrueIn202304", "12345", "Custom1", f64::NEG_INFINITY.into(), false), ("boolTrueIn202304", "12345", "Custom1", 1680307199.999.into(), false), ("boolTrueIn202304", "12345", "Custom1", 1680307200.001.into(), true), ("boolTrueIn202304", "12345", "Custom1", 1682899199.999.into(), true), ("boolTrueIn202304", "12345", "Custom1", 1682899200.001.into(), false), ("boolTrueIn202304", "12345", "Custom1", f64::INFINITY.into(), false), ("boolTrueIn202304", "12345", "Custom1", f64::NAN.into(), false), ("boolTrueIn202304", "12345", "Custom1", 1680307199.into(), false), ("boolTrueIn202304", "12345", "Custom1", 1680307201.into(), true), ("boolTrueIn202304", "12345", "Custom1", 1682899199.into(), true), ("boolTrueIn202304", "12345", "Custom1", 1682899201.into(), false), ("boolTrueIn202304", "12345", "Custom1", "-Infinity".into(), false), ("boolTrueIn202304", "12345", "Custom1", "1680307199.999".into(), false), ("boolTrueIn202304", "12345", "Custom1", "1680307200.001".into(), true), ("boolTrueIn202304", "12345", "Custom1", "1682899199.999".into(), true), ("boolTrueIn202304", "12345", "Custom1", "1682899200.001".into(), false), ("boolTrueIn202304", "12345", "Custom1", "+Infinity".into(), false), ("boolTrueIn202304", "12345", "Custom1", "NaN".into(), false), ]; let client = Client::new("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ").unwrap(); for test in tests { let user = User::new(test.1).custom(test.2, test.3.clone()); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_bool().unwrap(), test.4, "{:?}", test.3); } } #[tokio::test] async fn attr_val_conv_str_vec() { let tests: Vec<(&str, &str, &str, UserValue, &str)> = vec![ ("stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", ["x", "read"].into(), "Dog"), ("stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", ["x", "Read"].into(), "Cat"), ("stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", "[\"x\", \"read\"]".into(), "Dog"), ("stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", "[\"x\", \"Read\"]".into(), "Cat"), ("stringArrayContainsAnyOfDogDefaultCat", "12345", "Custom1", "x, read".into(), "Cat"), ]; let client = Client::new("configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ").unwrap(); for test in tests { let user = User::new(test.1).custom(test.2, test.3); let details = client.get_flag_details(test.0, Some(user)).await; assert_eq!(details.value.unwrap().as_str().unwrap(), test.4); } }