Files
khadhroony-solana-project/crates/ksp-config-lib/unit_tests/environment.rs

367 lines
19 KiB
Rust

// file: crates/ksp-config-lib/unit_tests/environment.rs
// version: 5
#[test]
fn process_environment_wins_over_dotenv_and_fallback_even_when_empty() {
let mut process = std::collections::BTreeMap::<String, String>::new();
process.insert("KSP_LOGS_DIRECTORY".to_owned(), String::new());
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), "dotenv-logs".to_owned());
let environment = super::ConfigEnvironment::from_maps(process, dotenv);
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
assert!(resolved.is_ok(), "process value should resolve");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.value(), "");
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::Process);
}
#[test]
fn dotenv_wins_over_fallback_when_process_value_is_absent() {
let process = std::collections::BTreeMap::<String, String>::new();
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), "dotenv-logs".to_owned());
let environment = super::ConfigEnvironment::from_maps(process, dotenv);
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
assert!(resolved.is_ok(), "dotenv value should resolve");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.value(), "dotenv-logs");
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::DotEnv);
}
#[test]
fn empty_dotenv_value_is_defined_and_beats_fallback() {
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), String::new());
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
assert!(resolved.is_ok(), "empty dotenv value should resolve");
if let std::result::Result::Ok(resolved) = resolved {
assert_eq!(resolved.value(), "");
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::DotEnv);
}
}
#[test]
fn fallback_is_used_only_when_external_sources_are_absent() {
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::Some("fallback-logs"));
assert!(resolved.is_ok(), "fallback should resolve");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.value(), "fallback-logs");
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::Fallback);
}
#[test]
fn missing_variable_without_fallback_is_a_distinct_error() {
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let resolved = environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::None);
let error = match resolved {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING);
assert!(!error.to_string().contains("fallback-logs"));
}
#[test]
fn ksp_and_kspb_namespaces_are_supported_but_external_names_are_rejected() {
let mut process = std::collections::BTreeMap::<String, String>::new();
process.insert("KSP_LOGS_DIRECTORY".to_owned(), "logs".to_owned());
let bot_variable = ["KSPB_", "TEST_KEY"].concat();
process.insert(bot_variable.clone(), "hidden".to_owned());
let environment = super::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
assert!(environment.resolve_variable("KSP_LOGS_DIRECTORY", std::option::Option::None).is_ok());
assert!(environment.resolve_variable(bot_variable.as_str(), std::option::Option::None).is_ok());
let external = environment.resolve_variable("OTHER_NETWORK", std::option::Option::None);
let error = match external {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_ENVIRONMENT_VARIABLE_INVALID);
}
#[test]
fn text_resolver_supports_multiple_placeholders_and_literal_fallbacks() {
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let resolved = environment.resolve_text("logs=${KSP_LOGS_DIRECTORY:-logs};second=${KSP_LOGS_DIRECTORY:-other}");
assert!(resolved.is_ok(), "multiple placeholders should resolve");
if let std::result::Result::Ok(resolved) = resolved {
assert_eq!(resolved, "logs=logs;second=other");
}
}
#[test]
fn malformed_or_nested_placeholders_are_rejected() {
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let unclosed = environment.resolve_text("${KSP_LOGS_DIRECTORY");
let unclosed = match unclosed {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(unclosed.code(), crate::ERROR_CODE_ENVIRONMENT_PLACEHOLDER_INVALID);
let nested = environment.resolve_text("${KSP_LOGS_DIRECTORY:-${KSP_LOGS_DIRECTORY}}}");
let nested = match nested {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(nested.code(), crate::ERROR_CODE_ENVIRONMENT_PLACEHOLDER_INVALID);
}
#[test]
fn json_resolver_walks_objects_and_arrays_without_changing_keys() {
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
dotenv.insert("KSP_LOGS_DIRECTORY".to_owned(), "runtime-logs".to_owned());
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
let source = serde_json::json!({"path": "${KSP_LOGS_DIRECTORY}", "items": [1, "${KSP_LOGS_DIRECTORY}"], "enabled": true});
let resolved = environment.resolve_json(&source);
assert!(resolved.is_ok(), "recursive JSON resolution should succeed");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved["path"], serde_json::Value::String("runtime-logs".to_owned()));
assert_eq!(resolved["items"][1], serde_json::Value::String("runtime-logs".to_owned()));
assert_eq!(resolved["enabled"], serde_json::Value::Bool(true));
}
#[test]
fn dotenv_parser_supports_comments_export_quotes_empty_values_and_ignores_external_keys() {
let path = std::path::Path::new("fixture.env");
let content = "# comment\nexport KSP_LOGS_DIRECTORY = 'quoted logs'\nOTHER_TOOL=value\n";
let parsed = super::parse_dotenv_content(path, content);
assert!(parsed.is_ok(), "dotenv fixture should parse");
let parsed = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(parsed.get("KSP_LOGS_DIRECTORY").map(String::as_str), std::option::Option::Some("quoted logs"));
assert!(!parsed.contains_key("OTHER_TOOL"));
}
#[test]
fn dotenv_duplicate_ksp_key_is_rejected() {
let parsed = super::parse_dotenv_content(std::path::Path::new("fixture.env"), "KSP_LOGS_DIRECTORY=one\nKSP_LOGS_DIRECTORY=two\n");
let error = match parsed {
std::result::Result::Ok(_) => return,
std::result::Result::Err(error) => error,
};
assert_eq!(error.code(), crate::ERROR_CODE_DOTENV_SYNTAX_INVALID);
}
#[test]
fn fake_process_collection_filters_unrelated_names_without_mutating_real_environment() {
let values = vec![
(std::ffi::OsString::from("KSP_LOGS_DIRECTORY"), std::ffi::OsString::from("process-logs")),
(std::ffi::OsString::from("OTHER_TOOL"), std::ffi::OsString::from("ignored")),
];
let collected = super::collect_process_environment(values);
assert!(collected.is_ok(), "fake process environment should collect");
let collected = match collected {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(collected.len(), 1);
assert_eq!(collected.get("KSP_LOGS_DIRECTORY").map(String::as_str), std::option::Option::Some("process-logs"));
}
#[test]
fn logging_fixture_profile_resolves_environment_fallback_without_changing_source_profile() {
let bootstrap = logging_fixture_bootstrap();
assert!(bootstrap.is_ok(), "bootstrap should resolve committed roots");
let bootstrap = match bootstrap {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let registry = crate::ConfigFileRegistry::defaults();
assert!(registry.is_ok(), "default registry should build");
let registry = match registry {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let engine = crate::ConfigDocumentEngine::new(bootstrap, registry);
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let profile = engine.load_resolved_profile(&file_id, std::option::Option::None);
assert!(profile.is_ok(), "committed Logging profile should resolve");
let profile = match profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let effective = profile.resolve_effective_environment(&environment);
assert!(effective.is_ok(), "committed Logging environment fallback should resolve");
let effective = match effective {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(profile.effective().get("logs_directory").and_then(serde_json::Value::as_str), std::option::Option::Some("${KSP_LOGS_DIRECTORY:-logs}"));
assert_eq!(effective.get("logs_directory").and_then(serde_json::Value::as_str), std::option::Option::Some("logs"));
}
#[test]
fn env_example_inventory_contains_current_runtime_variable_with_preceding_comment() {
let content = std::fs::read_to_string(workspace_root().join(crate::DEFAULT_DOTENV_EXAMPLE_PATH));
assert!(content.is_ok(), ".env.example must be committed at workspace root");
let content = match content {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let lines = content.lines().collect::<std::vec::Vec<&str>>();
let mut found = false;
for index in 0..lines.len() {
if lines[index].starts_with("KSP_LOGS_DIRECTORY=") {
found = true;
assert!(index > 0, "environment entry must have a preceding comment");
assert!(lines[index - 1].trim_start().starts_with('#'), "environment entry must be immediately preceded by a comment");
}
}
assert!(found, "current Config environment variable must appear in .env.example");
}
fn workspace_root() -> std::path::PathBuf {
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
}
fn logging_fixture_bootstrap() -> ksp_core_lib::Result<crate::ConfigBootstrapOptions> {
let fixture_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("unit_tests/fixtures");
let workspace = workspace_root();
return crate::ConfigBootstrapOptions::from_paths(fixture_root, workspace.join("config/schemas"));
}
#[test]
fn secret_environment_value_keeps_real_value_but_redacts_safe_and_debug_views() {
let canary = "KSP_SECRET_CANARY_91b7c6";
let mut process = std::collections::BTreeMap::<String, String>::new();
process.insert("KSP_SECRET_TEST_TOKEN".to_owned(), canary.to_owned());
let environment = super::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
let resolved = environment.resolve_variable("KSP_SECRET_TEST_TOKEN", std::option::Option::None);
assert!(resolved.is_ok(), "secret process value should resolve");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.value(), canary);
assert_eq!(resolved.safe_value(), crate::REDACTED_CONFIG_VALUE);
assert_eq!(resolved.sensitivity(), crate::ConfigSensitivity::Secret);
assert_eq!(resolved.source(), super::ConfigEnvironmentSource::Process);
let debug = format!("{resolved:?}");
assert!(!debug.contains(canary), "Debug must not reveal the secret canary");
assert!(debug.contains(crate::REDACTED_CONFIG_VALUE));
}
#[test]
fn detailed_text_redacts_only_secret_segments_and_keeps_ordered_provenance() {
let secret = "secret-canary-4a62";
let mut process = std::collections::BTreeMap::<String, String>::new();
process.insert("KSP_PUBLIC_HOST".to_owned(), "rpc.example.test".to_owned());
process.insert("KSP_SECRET_TOKEN".to_owned(), secret.to_owned());
let environment = super::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
let resolved = environment.resolve_text_detailed("https://${KSP_PUBLIC_HOST}/?token=${KSP_SECRET_TOKEN}");
assert!(resolved.is_ok(), "composed secret URL should resolve");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.value(), "https://rpc.example.test/?token=secret-canary-4a62");
assert_eq!(resolved.safe_value(), "https://rpc.example.test/?token=********");
assert_eq!(resolved.sensitivity(), crate::ConfigSensitivity::Secret);
assert_eq!(resolved.provenance().len(), 4);
assert_eq!(resolved.provenance()[0], crate::ConfigValueProvenance::DocumentLiteral);
assert_eq!(resolved.provenance()[1].variable_name(), std::option::Option::Some("KSP_PUBLIC_HOST"));
assert_eq!(resolved.provenance()[2], crate::ConfigValueProvenance::DocumentLiteral);
assert_eq!(resolved.provenance()[3].variable_name(), std::option::Option::Some("KSP_SECRET_TOKEN"));
let debug = format!("{resolved:?}");
assert!(!debug.contains(secret), "resolved text Debug must not reveal a secret segment");
}
#[test]
fn secret_fallback_inherits_secret_sensitivity_and_is_redacted() {
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let resolved = environment.resolve_text_detailed("token=${KSP_SECRET_TOKEN:-false-secret}");
assert!(resolved.is_ok(), "secret fallback should resolve");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.value(), "token=false-secret");
assert_eq!(resolved.safe_value(), "token=********");
assert_eq!(resolved.sensitivity(), crate::ConfigSensitivity::Secret);
assert_eq!(resolved.provenance()[1].environment_source(), std::option::Option::Some(super::ConfigEnvironmentSource::Fallback));
}
#[test]
fn detailed_json_preserves_safe_tree_sensitivity_and_pointer_provenance() {
let secret = "nested-secret-canary-2d11";
let mut dotenv = std::collections::BTreeMap::<String, String>::new();
dotenv.insert("KSP_SECRET_TOKEN".to_owned(), secret.to_owned());
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), dotenv);
let source = serde_json::json!({"transport": {"url": "https://host/?token=${KSP_SECRET_TOKEN}"}, "items": ["plain", 7]});
let resolved = environment.resolve_json_detailed(&source);
assert!(resolved.is_ok(), "detailed JSON should resolve");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.value()["transport"]["url"], serde_json::Value::String(format!("https://host/?token={secret}")));
assert_eq!(resolved.safe_value()["transport"]["url"], serde_json::Value::String("https://host/?token=********".to_owned()));
assert_eq!(resolved.sensitivity(), crate::ConfigSensitivity::Secret);
let provenance = resolved.provenance_at("/transport/url");
assert!(provenance.is_some(), "JSON pointer provenance should exist");
if let std::option::Option::Some(provenance) = provenance {
assert_eq!(provenance.last().and_then(crate::ConfigValueProvenance::variable_name), std::option::Option::Some("KSP_SECRET_TOKEN"));
}
let debug = format!("{resolved:?}");
assert!(!debug.contains(secret), "resolved JSON Debug must not reveal a secret canary");
}
#[test]
fn detailed_fixture_profile_environment_keeps_global_origin_and_adds_environment_provenance() {
let bootstrap = logging_fixture_bootstrap();
let bootstrap = match bootstrap {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let registry = crate::ConfigFileRegistry::defaults();
let registry = match registry {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING);
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let engine = crate::ConfigDocumentEngine::new(bootstrap, registry);
let profile = engine.load_resolved_profile(&file_id, std::option::Option::None);
let profile = match profile {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let environment = super::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
let effective = profile.resolve_effective_environment_detailed(&environment);
assert!(effective.is_ok(), "detailed committed Logging profile should resolve");
let effective = match effective {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(profile.origin("logs_directory"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
assert_eq!(effective.value()["logs_directory"], serde_json::Value::String("logs".to_owned()));
assert_eq!(effective.safe_value()["logs_directory"], serde_json::Value::String("logs".to_owned()));
assert_eq!(
effective.provenance_at("/logs_directory").and_then(|items| return items.last()).and_then(crate::ConfigValueProvenance::variable_name),
std::option::Option::Some("KSP_LOGS_DIRECTORY"),
);
}