use std::fs::File;
use std::time::Duration;
use common::FLAGD_CONFIG;
use open_feature::provider::FeatureProvider;
use open_feature::{EvaluationContext, Value};
use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType};
use std::io::Write;
use tempfile::NamedTempFile;
use test_log::test;
mod common;
#[test(tokio::test)]
async fn test_in_process_file_sync() {
let mut temp_file = NamedTempFile::new().unwrap();
write!(temp_file, "{}", FLAGD_CONFIG).unwrap();
let file_path = temp_file.path().to_str().unwrap().to_string();
let provider = FlagdProvider::new(FlagdOptions {
source_configuration: Some(file_path.clone()),
resolver_type: ResolverType::File,
cache_settings: None,
..Default::default()
})
.await
.unwrap();
let context = EvaluationContext::default();
let bool_result = provider
.resolve_bool_value("bool-flag", &context)
.await
.unwrap();
assert_eq!(bool_result.value, true);
let updated_config =
FLAGD_CONFIG.replace("\"defaultVariant\": \"on\"", "\"defaultVariant\": \"off\"");
let mut file = File::create(&file_path).unwrap();
write!(file, "{}", updated_config).unwrap();
tokio::time::sleep(Duration::from_secs(2)).await;
let updated_result = provider
.resolve_bool_value("bool-flag", &context)
.await
.unwrap();
assert_eq!(updated_result.value, false);
}
#[test(tokio::test)]
async fn test_file_connector_error_handling() {
let provider = FlagdProvider::new(FlagdOptions {
source_configuration: Some("/nonexistent/path".to_string()),
resolver_type: ResolverType::File,
..Default::default()
})
.await;
assert!(provider.is_err());
let mut temp_file = NamedTempFile::new().unwrap();
write!(temp_file, "invalid json").unwrap();
let provider = FlagdProvider::new(FlagdOptions {
source_configuration: Some(temp_file.path().to_str().unwrap().to_string()),
resolver_type: ResolverType::File,
..Default::default()
})
.await;
assert!(provider.is_err());
}
#[test(tokio::test)]
async fn test_file_connector_file_deletion() {
let mut temp_file = NamedTempFile::new().unwrap();
write!(temp_file, "{}", FLAGD_CONFIG).unwrap();
let file_path = temp_file.path().to_str().unwrap().to_string();
let provider = FlagdProvider::new(FlagdOptions {
source_configuration: Some(file_path.clone()),
resolver_type: ResolverType::File,
cache_settings: None,
..Default::default()
})
.await
.unwrap();
let context = EvaluationContext::default();
let initial_result = provider
.resolve_bool_value("bool-flag", &context)
.await
.unwrap();
assert_eq!(initial_result.value, true);
drop(temp_file);
tokio::time::sleep(Duration::from_secs(2)).await;
let cached_result = provider
.resolve_bool_value("bool-flag", &context)
.await
.unwrap();
assert_eq!(cached_result.value, true);
}
#[test(tokio::test)]
async fn test_file_resolver_all_types() {
let mut temp_file = NamedTempFile::new().unwrap();
write!(temp_file, "{}", FLAGD_CONFIG).unwrap();
let file_path = temp_file.path().to_str().unwrap().to_string();
let provider = FlagdProvider::new(FlagdOptions {
source_configuration: Some(file_path),
resolver_type: ResolverType::File,
..Default::default()
})
.await
.unwrap();
let context = EvaluationContext::default().with_targeting_key("test-user");
let bool_result = provider
.resolve_bool_value("bool-flag", &context)
.await
.unwrap();
assert_eq!(bool_result.value, true);
let string_result = provider
.resolve_string_value("string-flag", &context)
.await
.unwrap();
assert_eq!(string_result.value, "hello");
let int_result = provider
.resolve_int_value("int-flag", &context)
.await
.unwrap();
assert_eq!(int_result.value, 42);
let float_result = provider
.resolve_float_value("float-flag", &context)
.await
.unwrap();
assert_eq!(float_result.value, 3.14);
let struct_result = provider
.resolve_struct_value("struct-flag", &context)
.await
.unwrap();
assert!(struct_result.value.fields.contains_key("key"));
assert_eq!(
struct_result.value.fields["key"],
Value::String("value".to_string())
);
}
#[test(tokio::test)]
async fn test_file_resolver_requires_source_configuration() {
let result = FlagdProvider::new(FlagdOptions {
resolver_type: ResolverType::File,
source_configuration: None, ..Default::default()
})
.await;
assert!(
result.is_err(),
"Expected error when source_configuration is missing"
);
let err = result.unwrap_err();
let err_msg = format!("{}", err);
assert!(
err_msg.contains("source_configuration")
|| err_msg.contains("FLAGD_OFFLINE_FLAG_SOURCE_PATH"),
"Error message should mention source_configuration or FLAGD_OFFLINE_FLAG_SOURCE_PATH, got: {}",
err_msg
);
}