mod common;
use std::collections::HashMap;
use std::path::PathBuf;
use apcore_cli::config::ConfigResolver;
use tempfile::tempdir;
#[test]
fn test_config_resolver_instantiation() {
let resolver = ConfigResolver::new(None, None);
assert!(!resolver.defaults.is_empty());
}
#[test]
fn test_config_resolver_with_cli_flags() {
let mut flags = HashMap::new();
flags.insert("--extensions-dir".to_string(), Some("/cli".to_string()));
let resolver = ConfigResolver::new(Some(flags.clone()), None);
assert_eq!(resolver.cli_flags, flags);
}
#[test]
fn test_defaults_contains_expected_keys() {
let resolver = ConfigResolver::new(None, None);
for key in [
"extensions.root",
"logging.level",
"cli.help_text_max_length",
"cli.approval_timeout",
"cli.strategy",
"cli.group_depth",
] {
assert!(
resolver.defaults.contains_key(key),
"missing default: {key}"
);
}
}
#[test]
fn test_resolve_tier1_cli_flag_wins() {
unsafe { std::env::set_var("TCFG01_EXT_ROOT", "/env-path") };
let dir = tempdir().unwrap(); let config_path = dir.path().join("apcore.yaml");
std::fs::write(&config_path, "extensions:\n root: /config-path\n").unwrap();
let mut flags = HashMap::new();
flags.insert(
"--extensions-dir".to_string(),
Some("/cli-path".to_string()),
);
let resolver = ConfigResolver::new(Some(flags), Some(config_path));
let result = resolver.resolve(
"extensions.root",
Some("--extensions-dir"),
Some("TCFG01_EXT_ROOT"),
);
assert_eq!(result, Some("/cli-path".to_string()), "CLI flag must win");
unsafe { std::env::remove_var("TCFG01_EXT_ROOT") };
}
#[test]
fn test_resolve_tier2_env_var_wins() {
unsafe { std::env::set_var("TCFG02_EXT_ROOT", "/env-path") };
let dir = tempdir().unwrap(); let config_path = dir.path().join("apcore.yaml");
std::fs::write(&config_path, "extensions:\n root: /config-path\n").unwrap();
let resolver = ConfigResolver::new(None, Some(config_path));
let result = resolver.resolve(
"extensions.root",
Some("--extensions-dir"),
Some("TCFG02_EXT_ROOT"),
);
assert_eq!(
result,
Some("/env-path".to_string()),
"env var must win over config file"
);
unsafe { std::env::remove_var("TCFG02_EXT_ROOT") };
}
#[test]
fn test_resolve_tier3_config_file_wins() {
let dir = tempdir().unwrap(); let config_path = dir.path().join("apcore.yaml");
std::fs::write(&config_path, "extensions:\n root: /config-path\n").unwrap();
let resolver = ConfigResolver::new(None, Some(config_path));
let result = resolver.resolve("extensions.root", None, None);
assert_eq!(
result,
Some("/config-path".to_string()),
"config file must win over default"
);
}
#[test]
fn test_resolve_tier4_default_wins() {
let resolver = ConfigResolver::new(None, Some(PathBuf::from("/nonexistent/apcore.yaml")));
let result = resolver.resolve("extensions.root", None, None);
assert_eq!(
result,
Some("./extensions".to_string()),
"built-in default must be returned"
);
}
#[test]
fn test_resolve_cli_flag_none_skips_tier1() {
unsafe { std::env::set_var("TCFG09_EXT_ROOT", "/env-path") };
let mut flags = HashMap::new();
flags.insert("--extensions-dir".to_string(), None);
let resolver = ConfigResolver::new(Some(flags), None);
let result = resolver.resolve(
"extensions.root",
Some("--extensions-dir"),
Some("TCFG09_EXT_ROOT"),
);
assert_eq!(
result,
Some("/env-path".to_string()),
"None CLI flag must fall through to env var"
);
unsafe { std::env::remove_var("TCFG09_EXT_ROOT") };
}
#[test]
fn test_resolve_env_var_empty_string_skips_tier2() {
unsafe { std::env::set_var("TCFG08_EXT_ROOT", "") };
let dir = tempdir().unwrap(); let config_path = dir.path().join("apcore.yaml");
std::fs::write(&config_path, "extensions:\n root: /config-path\n").unwrap();
let resolver = ConfigResolver::new(None, Some(config_path));
let result = resolver.resolve("extensions.root", None, Some("TCFG08_EXT_ROOT"));
assert_eq!(
result,
Some("/config-path".to_string()),
"empty-string env var must fall through to config file"
);
unsafe { std::env::remove_var("TCFG08_EXT_ROOT") };
}
#[test]
fn test_resolve_unknown_key_returns_none() {
let resolver = ConfigResolver::new(None, None);
let result = resolver.resolve("nonexistent.key", None, None);
assert!(result.is_none());
}
#[test]
fn test_load_config_file_valid_yaml() {
let dir = tempdir().unwrap(); let config_path = dir.path().join("apcore.yaml");
std::fs::write(
&config_path,
"extensions:\n root: /custom/path\nlogging:\n level: DEBUG\n",
)
.unwrap();
let resolver = ConfigResolver::new(None, Some(config_path));
let file_map = resolver
.config_file
.as_ref()
.expect("config file must be loaded");
assert_eq!(
file_map.get("extensions.root"),
Some(&"/custom/path".to_string())
);
assert_eq!(file_map.get("logging.level"), Some(&"DEBUG".to_string()));
}
#[test]
fn test_load_config_file_not_found() {
let resolver = ConfigResolver::new(None, Some(PathBuf::from("/nonexistent/apcore.yaml")));
assert!(resolver.config_file.is_none());
}
#[test]
fn test_config_file_not_found_silent() {
let resolver = ConfigResolver::new(None, Some(PathBuf::from("/this/does/not/exist.yaml")));
assert!(resolver.config_file.is_none());
let result = resolver.resolve("extensions.root", None, None);
assert_eq!(result, Some("./extensions".to_string()));
}
#[test]
fn test_config_file_malformed_yaml() {
let dir = tempdir().unwrap(); let config_path = dir.path().join("apcore.yaml");
std::fs::write(&config_path, "{ invalid yaml: [unclosed").unwrap();
let resolver = ConfigResolver::new(None, Some(config_path));
assert!(
resolver.config_file.is_none(),
"malformed YAML must result in None config_file"
);
}
#[test]
fn test_config_file_root_not_dict() {
let dir = tempdir().unwrap(); let config_path = dir.path().join("apcore.yaml");
std::fs::write(&config_path, "- item1\n- item2\n").unwrap();
let resolver = ConfigResolver::new(None, Some(config_path));
assert!(
resolver.config_file.is_none(),
"list root must result in None config_file"
);
}
#[test]
fn test_flatten_dict_nested() {
let resolver = ConfigResolver::new(None, None);
let map = serde_json::json!({"extensions": {"root": "/path"}});
let result = resolver.flatten_dict(map);
assert_eq!(result.get("extensions.root"), Some(&"/path".to_string()));
assert_eq!(result.len(), 1);
}
#[test]
fn test_flatten_dict_deeply_nested() {
let resolver = ConfigResolver::new(None, None);
let map = serde_json::json!({"a": {"b": {"c": "deep_value"}}});
let result = resolver.flatten_dict(map);
assert_eq!(result.get("a.b.c"), Some(&"deep_value".to_string()));
assert_eq!(result.len(), 1);
}