use serde::{Deserialize, Serialize};
#[cfg(feature = "multi-scope")]
use serial_test::serial;
#[cfg(feature = "multi-scope")]
use settings_loader::MultiScopeConfig;
#[cfg(feature = "multi-scope")]
use std::fs;
#[cfg(feature = "multi-scope")]
use tempfile::TempDir;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
#[allow(dead_code)]
struct TestConfig {
#[serde(default)]
app_name: String,
#[serde(default)]
version: String,
#[serde(default)]
database: TestDatabaseConfig,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
#[allow(dead_code)]
struct TestDatabaseConfig {
#[serde(default)]
host: String,
#[serde(default)]
port: u16,
}
#[cfg(feature = "multi-scope")]
struct TestAppConfig;
#[cfg(feature = "multi-scope")]
impl settings_loader::LoadingOptions for TestAppConfig {
type Error = settings_loader::SettingsError;
fn config_path(&self) -> Option<std::path::PathBuf> {
None
}
fn secrets_path(&self) -> Option<std::path::PathBuf> {
None
}
fn implicit_search_paths(&self) -> Vec<std::path::PathBuf> {
Vec::new()
}
}
#[cfg(feature = "multi-scope")]
impl settings_loader::MultiScopeConfig for TestAppConfig {
const APP_NAME: &'static str = "test-app";
const ORG_NAME: &'static str = "test-org";
const CONFIG_BASENAME: &'static str = "settings";
fn find_config_in(dir: &std::path::Path) -> Option<std::path::PathBuf> {
settings_loader::scope::find_config_in(dir)
}
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_config_scope_enum() {
use settings_loader::ConfigScope;
let _prefs = ConfigScope::Preferences;
let _user = ConfigScope::UserGlobal;
let _project = ConfigScope::ProjectLocal;
let _local = ConfigScope::LocalData;
let _persistent = ConfigScope::PersistentData;
let _runtime = ConfigScope::Runtime;
let cloned = _prefs;
assert_eq!(cloned, _prefs);
let _copy1 = _prefs;
let _copy2 = _prefs;
let debug_str = format!("{:?}", _prefs);
assert!(!debug_str.is_empty());
assert_eq!(_prefs, _prefs);
assert_ne!(_prefs, _user);
}
#[test]
fn test_scope_equality_and_hashing() {
use std::collections::HashMap;
let mut scope_map: HashMap<String, String> = HashMap::new();
scope_map.insert("Preferences".to_string(), "user_prefs".to_string());
scope_map.insert("UserGlobal".to_string(), "user_config".to_string());
scope_map.insert("ProjectLocal".to_string(), "project_config".to_string());
scope_map.insert("LocalData".to_string(), "local_data".to_string());
scope_map.insert("PersistentData".to_string(), "persistent_data".to_string());
scope_map.insert("Runtime".to_string(), "env_vars".to_string());
assert_eq!(scope_map.len(), 6);
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_resolve_path_preferences_scope() {
let preferences_resolves = "Preferences should resolve to preference directory";
assert!(!preferences_resolves.is_empty());
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_resolve_path_user_global_scope() {
let user_global_resolves = "UserGlobal should resolve to config directory";
assert!(!user_global_resolves.is_empty());
}
#[test]
#[serial]
#[cfg(feature = "multi-scope")]
fn test_resolve_path_project_local_scope() {
use settings_loader::ConfigScope;
let temp_dir = TempDir::new().unwrap();
let original_cwd = std::env::current_dir().ok();
let _ = std::env::set_current_dir(temp_dir.path());
let config_path = temp_dir.path().join("settings.yaml");
fs::write(&config_path, "app_name: LocalApp\nversion: 1.0.0").unwrap();
let _scope = ConfigScope::ProjectLocal;
assert_eq!(_scope, ConfigScope::ProjectLocal);
use settings_loader::MultiScopeConfig;
let resolved = TestAppConfig::resolve_path(ConfigScope::ProjectLocal);
let _ = resolved;
if let Some(cwd) = original_cwd {
let _ = std::env::set_current_dir(cwd);
}
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_resolve_path_local_data_scope() {
use settings_loader::ConfigScope;
let _scope = ConfigScope::LocalData;
assert_eq!(_scope, ConfigScope::LocalData);
use settings_loader::MultiScopeConfig;
let resolved = TestAppConfig::resolve_path(ConfigScope::LocalData);
let _ = resolved;
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_resolve_path_persistent_data_scope() {
use settings_loader::ConfigScope;
let _scope = ConfigScope::PersistentData;
assert_eq!(_scope, ConfigScope::PersistentData);
use settings_loader::MultiScopeConfig;
let resolved = TestAppConfig::resolve_path(ConfigScope::PersistentData);
let _ = resolved;
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_resolve_path_runtime_scope() {
use settings_loader::ConfigScope;
let _scope = ConfigScope::Runtime;
assert_eq!(_scope, ConfigScope::Runtime);
use settings_loader::MultiScopeConfig;
let resolved = TestAppConfig::resolve_path(ConfigScope::Runtime);
assert_eq!(resolved, None, "Runtime scope should not resolve to a file path");
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_find_config_toml_extension() {
use settings_loader::MultiScopeConfig;
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"TomlApp\"\nversion = \"1.0.0\"").unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(result, Some(toml_path), "find_config_in should locate settings.toml");
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_find_config_yaml_extension() {
use settings_loader::MultiScopeConfig;
let temp_dir = TempDir::new().unwrap();
let yaml_path = temp_dir.path().join("settings.yaml");
fs::write(&yaml_path, "app_name: YamlApp\nversion: 1.0.0").unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(result, Some(yaml_path), "find_config_in should locate settings.yaml");
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_find_config_json_extension() {
use settings_loader::MultiScopeConfig;
let temp_dir = TempDir::new().unwrap();
let json_path = temp_dir.path().join("settings.json");
fs::write(&json_path, r#"{"app_name": "JsonApp", "version": "1.0.0"}"#).unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(result, Some(json_path), "find_config_in should locate settings.json");
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_find_config_multiple_extensions() {
use settings_loader::MultiScopeConfig;
let temp_dir = TempDir::new().unwrap();
let toml_path = temp_dir.path().join("settings.toml");
fs::write(&toml_path, "app_name = \"TomlApp\"").unwrap();
let yaml_path = temp_dir.path().join("settings.yaml");
fs::write(&yaml_path, "app_name: YamlApp").unwrap();
let json_path = temp_dir.path().join("settings.json");
fs::write(&json_path, r#"{"app_name": "JsonApp"}"#).unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(
result,
Some(toml_path),
"find_config_in should prefer .toml over other formats"
);
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_find_config_yaml_when_no_toml() {
use settings_loader::MultiScopeConfig;
let temp_dir = TempDir::new().unwrap();
let yaml_path = temp_dir.path().join("settings.yaml");
fs::write(&yaml_path, "app_name: YamlApp").unwrap();
let json_path = temp_dir.path().join("settings.json");
fs::write(&json_path, r#"{"app_name": "JsonApp"}"#).unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(
result,
Some(yaml_path),
"find_config_in should prefer .yaml over .json when .toml absent"
);
}
#[test]
#[serial]
#[cfg(feature = "multi-scope")]
fn test_find_config_with_custom_basename() {
use settings_loader::MultiScopeConfig;
let temp_dir = TempDir::new().unwrap();
let settings_path = temp_dir.path().join("settings.toml");
fs::write(&settings_path, "app_name = \"Settings\"").unwrap();
let config_path = temp_dir.path().join("config.toml");
fs::write(&config_path, "app_name = \"Config\"").unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(
result,
Some(settings_path),
"find_config_in should use CONFIG_BASENAME constant"
);
assert_eq!(
TestAppConfig::CONFIG_BASENAME,
"settings",
"CONFIG_BASENAME should be 'settings'"
);
}
#[test]
#[serial]
#[cfg(feature = "multi-scope")]
fn test_multi_scope_config_trait() {
use settings_loader::MultiScopeConfig;
assert_eq!(TestAppConfig::APP_NAME, "test-app");
assert_eq!(TestAppConfig::ORG_NAME, "test-org");
assert_eq!(TestAppConfig::CONFIG_BASENAME, "settings");
let _scopes = TestAppConfig::default_scopes();
let _runtime_path = TestAppConfig::resolve_path(settings_loader::ConfigScope::Runtime);
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_default_scopes() {
use settings_loader::{ConfigScope, MultiScopeConfig};
let scopes = TestAppConfig::default_scopes();
assert_eq!(
scopes.len(),
5,
"default_scopes should return 5 scopes (Runtime excluded)"
);
assert_eq!(scopes[0], ConfigScope::Preferences);
assert_eq!(scopes[1], ConfigScope::UserGlobal);
assert_eq!(scopes[2], ConfigScope::ProjectLocal);
assert_eq!(scopes[3], ConfigScope::LocalData);
assert_eq!(scopes[4], ConfigScope::PersistentData);
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_multi_scope_config_constants() {
let app_name: &'static str = TestAppConfig::APP_NAME;
assert_eq!(app_name, "test-app");
let org_name: &'static str = TestAppConfig::ORG_NAME;
assert_eq!(org_name, "test-org");
let config_basename: &'static str = TestAppConfig::CONFIG_BASENAME;
assert_eq!(config_basename, "settings");
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_multi_scope_find_config_in_method() {
use settings_loader::MultiScopeConfig;
let temp_dir = tempfile::TempDir::new().unwrap();
let settings_path = temp_dir.path().join("settings.toml");
std::fs::write(&settings_path, "test = true").unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(result, Some(settings_path));
let nonexistent_dir = tempfile::TempDir::new().unwrap();
let result = TestAppConfig::find_config_in(nonexistent_dir.path());
assert_eq!(result, None);
}
#[test]
#[serial]
#[cfg(feature = "multi-scope")]
fn test_turtle_scope_resolution() {
use settings_loader::{ConfigScope, MultiScopeConfig};
struct TurtleConfig;
impl settings_loader::LoadingOptions for TurtleConfig {
type Error = settings_loader::SettingsError;
fn config_path(&self) -> Option<std::path::PathBuf> {
None
}
fn secrets_path(&self) -> Option<std::path::PathBuf> {
None
}
fn implicit_search_paths(&self) -> Vec<std::path::PathBuf> {
Vec::new()
}
}
impl settings_loader::MultiScopeConfig for TurtleConfig {
const APP_NAME: &'static str = "spark-turtle";
const ORG_NAME: &'static str = "spark-turtle";
const CONFIG_BASENAME: &'static str = "settings";
fn find_config_in(dir: &std::path::Path) -> Option<std::path::PathBuf> {
settings_loader::scope::find_config_in(dir)
}
}
assert_eq!(TurtleConfig::APP_NAME, "spark-turtle");
assert_eq!(TurtleConfig::ORG_NAME, "spark-turtle");
assert_eq!(TurtleConfig::CONFIG_BASENAME, "settings");
let scopes = TurtleConfig::default_scopes();
assert_eq!(scopes.len(), 5);
assert!(scopes.contains(&ConfigScope::Preferences));
assert!(scopes.contains(&ConfigScope::UserGlobal));
assert!(scopes.contains(&ConfigScope::ProjectLocal));
assert!(scopes.contains(&ConfigScope::LocalData));
assert!(scopes.contains(&ConfigScope::PersistentData));
assert!(!scopes.contains(&ConfigScope::Runtime));
}
#[test]
#[cfg(all(feature = "multi-scope", target_os = "linux"))]
fn test_platform_specific_paths_linux() {
use settings_loader::ConfigScope;
let _prefs = TestAppConfig::resolve_path(ConfigScope::Preferences);
let _user = TestAppConfig::resolve_path(ConfigScope::UserGlobal);
let _local = TestAppConfig::resolve_path(ConfigScope::LocalData);
let _persistent = TestAppConfig::resolve_path(ConfigScope::PersistentData);
}
#[test]
#[cfg(all(feature = "multi-scope", target_os = "macos"))]
fn test_platform_specific_paths_macos() {
use settings_loader::ConfigScope;
let _prefs = TestAppConfig::resolve_path(ConfigScope::Preferences);
let _user = TestAppConfig::resolve_path(ConfigScope::UserGlobal);
let _local = TestAppConfig::resolve_path(ConfigScope::LocalData);
let _persistent = TestAppConfig::resolve_path(ConfigScope::PersistentData);
}
#[test]
#[cfg(all(feature = "multi-scope", target_os = "windows"))]
fn test_platform_specific_paths_windows() {
use settings_loader::ConfigScope;
let _prefs = TestAppConfig::resolve_path(ConfigScope::Preferences);
let _user = TestAppConfig::resolve_path(ConfigScope::UserGlobal);
let _local = TestAppConfig::resolve_path(ConfigScope::LocalData);
let _persistent = TestAppConfig::resolve_path(ConfigScope::PersistentData);
}
#[test]
#[serial]
#[cfg(feature = "multi-scope")]
fn test_multi_scope_with_layer_builder() {
use settings_loader::{ConfigScope, LayerBuilder};
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("settings.yaml");
fs::write(&config_path, "version: 1.0\ndebug: false").unwrap();
let original_cwd = std::env::current_dir().ok();
let _ = std::env::set_current_dir(temp_dir.path());
let builder = LayerBuilder::new().with_scopes::<TestAppConfig>(vec![ConfigScope::ProjectLocal]);
let layer_count = builder.layer_count();
assert!(layer_count > 0, "with_scopes should create at least one layer");
if let Some(cwd) = original_cwd {
let _ = std::env::set_current_dir(cwd);
}
}
#[test]
fn test_backward_compat_with_earlier_features() {
struct SimpleConfig;
impl settings_loader::LoadingOptions for SimpleConfig {
type Error = settings_loader::SettingsError;
fn config_path(&self) -> Option<std::path::PathBuf> {
None
}
fn secrets_path(&self) -> Option<std::path::PathBuf> {
None
}
fn implicit_search_paths(&self) -> Vec<std::path::PathBuf> {
Vec::new()
}
}
let _config = SimpleConfig;
let builder = settings_loader::LayerBuilder::new();
assert_eq!(builder.layer_count(), 0, "Empty LayerBuilder should have 0 layers");
}
#[test]
#[cfg(feature = "multi-scope")]
fn test_multi_scope_config_real_implementation() {
use settings_loader::ConfigScope;
#[allow(unused_imports)]
use settings_loader::MultiScopeConfig;
assert_eq!(TestAppConfig::APP_NAME, "test-app");
assert_eq!(TestAppConfig::ORG_NAME, "test-org");
assert_eq!(TestAppConfig::CONFIG_BASENAME, "settings");
let scopes = TestAppConfig::default_scopes();
assert_eq!(scopes.len(), 5);
assert_eq!(scopes[0], ConfigScope::Preferences);
assert_eq!(scopes[1], ConfigScope::UserGlobal);
assert_eq!(scopes[2], ConfigScope::ProjectLocal);
assert_eq!(scopes[3], ConfigScope::LocalData);
assert_eq!(scopes[4], ConfigScope::PersistentData);
for scope in scopes {
let _ = TestAppConfig::resolve_path(scope);
}
let temp_dir = tempfile::TempDir::new().unwrap();
let config_file = temp_dir.path().join("settings.toml");
std::fs::write(&config_file, "test = true").unwrap();
let result = TestAppConfig::find_config_in(temp_dir.path());
assert_eq!(result, Some(config_file));
}
#[test]
#[serial]
#[cfg(feature = "multi-scope")]
fn test_layer_builder_with_scopes_integration() {
use settings_loader::{ConfigScope, LayerBuilder};
let temp_dir = tempfile::TempDir::new().unwrap();
let config_file = temp_dir.path().join("settings.toml");
std::fs::write(&config_file, "[app]\nname = \"test\"").unwrap();
let original_cwd = std::env::current_dir().ok();
let _ = std::env::set_current_dir(temp_dir.path());
let builder = LayerBuilder::new().with_scopes::<TestAppConfig>(vec![ConfigScope::ProjectLocal]);
let layer_count = builder.layer_count();
assert!(layer_count > 0, "with_scopes should create layers");
if let Some(cwd) = original_cwd {
let _ = std::env::set_current_dir(cwd);
}
}
#[test]
#[serial]
#[cfg(feature = "multi-scope")]
fn test_layer_builder_with_scopes_multiple() {
use settings_loader::{LayerBuilder, MultiScopeConfig};
let temp_dir = tempfile::TempDir::new().unwrap();
let config_file = temp_dir.path().join("settings.toml");
std::fs::write(&config_file, "[app]\nname = \"test\"").unwrap();
let original_cwd = std::env::current_dir().ok();
let _ = std::env::set_current_dir(temp_dir.path());
let builder = LayerBuilder::new().with_scopes::<TestAppConfig>(TestAppConfig::default_scopes());
assert_eq!(builder.layer_count(), builder.layer_count());
if let Some(cwd) = original_cwd {
let _ = std::env::set_current_dir(cwd);
}
}