294 lines
16 KiB
Rust
294 lines
16 KiB
Rust
// file: crates/ksp-config-lib/unit_tests/store.rs
|
|
// version: 4
|
|
|
|
#[test]
|
|
fn committed_store_profile_maps_exact_runtime_settings_and_secret_fallback() {
|
|
let engine = committed_engine();
|
|
let engine = match engine {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
|
let resolved = engine.load_resolved_store_config(std::option::Option::None, &environment);
|
|
assert!(resolved.is_ok(), "committed Store profile should map without opening PostgreSQL: {resolved:?}");
|
|
if let std::result::Result::Ok(resolved) = resolved {
|
|
assert_eq!(resolved.file_id().as_str(), crate::FILE_ID_STD_STORE);
|
|
assert_eq!(resolved.profile_id(), "devnet");
|
|
assert_eq!(resolved.target_id(), "devnet");
|
|
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile);
|
|
assert_eq!(resolved.settings().backend_kind(), ksp_store_lib::StoreBackendKind::Postgres);
|
|
assert_eq!(resolved.settings().network().as_str(), "devnet");
|
|
assert_eq!(resolved.settings().shutdown_timeout(), std::time::Duration::from_millis(5_000));
|
|
let postgres = match resolved.settings().backend() {
|
|
ksp_store_lib::StoreBackendSettings::Postgres(postgres) => std::option::Option::Some(postgres),
|
|
_ => std::option::Option::None,
|
|
};
|
|
assert!(postgres.is_some(), "pre.004 fixture should map to the PostgreSQL Store backend");
|
|
if let std::option::Option::Some(postgres) = postgres {
|
|
assert_eq!(postgres.pool().max_connections(), 8);
|
|
assert_eq!(postgres.pool().connect_timeout(), std::time::Duration::from_millis(10_000));
|
|
assert_eq!(postgres.pool().wait_timeout(), std::time::Duration::from_millis(5_000));
|
|
assert_eq!(postgres.pool().create_timeout(), std::time::Duration::from_millis(10_000));
|
|
assert_eq!(postgres.pool().recycle_timeout(), std::time::Duration::from_millis(5_000));
|
|
assert_eq!(postgres.tls_mode(), ksp_store_lib::PostgresTlsMode::Disabled);
|
|
assert!(postgres.bootstrap().auto_migrate());
|
|
assert!(postgres.bootstrap().schema_autocreate());
|
|
assert!(postgres.bootstrap().schema_autoupdate());
|
|
assert_eq!(postgres.bootstrap().migration_timeout(), std::time::Duration::from_millis(30_000));
|
|
assert_eq!(postgres.bootstrap().migration_lock_timeout(), std::time::Duration::from_millis(10_000));
|
|
}
|
|
assert!(resolved.effective().sensitivity().is_secret());
|
|
let safe = resolved.effective().safe_value().to_string();
|
|
assert!(!safe.contains("postgresql://localhost/ksp_devnet"));
|
|
assert!(safe.contains(crate::REDACTED_CONFIG_VALUE));
|
|
let debug = format!("{resolved:?}");
|
|
assert!(!debug.contains("postgresql://localhost/ksp_devnet"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn process_store_uri_wins_and_remains_redacted_in_safe_views() {
|
|
let engine = committed_engine();
|
|
let engine = match engine {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let canary = "postgresql://secret-user:secret-pass@db.example/ksp_devnet";
|
|
let mut process = std::collections::BTreeMap::<String, String>::new();
|
|
process.insert("KSP_SECRET_STORE_DEVNET_POSTGRES_URI".to_owned(), canary.to_owned());
|
|
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
|
let resolved = engine.load_resolved_store_config(std::option::Option::None, &environment);
|
|
assert!(resolved.is_ok(), "secret process Store URI should map: {resolved:?}");
|
|
if let std::result::Result::Ok(resolved) = resolved {
|
|
assert_eq!(resolved.effective().value().pointer("/postgres/connection_uri").and_then(serde_json::Value::as_str), std::option::Option::Some(canary));
|
|
assert!(!resolved.effective().safe_value().to_string().contains(canary));
|
|
assert!(!format!("{resolved:?}").contains(canary));
|
|
let provenance = resolved.effective().provenance_at("/postgres/connection_uri");
|
|
assert!(provenance.is_some());
|
|
if let std::option::Option::Some(provenance) = provenance {
|
|
assert!(provenance.iter().any(|item| return item.environment_source() == std::option::Option::Some(crate::ConfigEnvironmentSource::Process)));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn literal_or_nonsecret_store_uri_is_rejected_by_effective_adapter() {
|
|
for value in ["postgresql://literal.invalid/ksp", "${KSP_PUBLIC_STORE_POSTGRES_URI:-postgresql://public.invalid/ksp}"] {
|
|
let fixture = tempfile::tempdir();
|
|
assert!(fixture.is_ok());
|
|
let fixture = match fixture {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = committed_document_value();
|
|
let mut source = match source {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let profiles = source.get_mut("profiles").and_then(serde_json::Value::as_array_mut);
|
|
if let std::option::Option::Some(profiles) = profiles
|
|
&& let std::option::Option::Some(profile) = profiles.first_mut()
|
|
{
|
|
profile["postgres"]["connection_uri"] = serde_json::Value::String(value.to_owned());
|
|
}
|
|
let engine = fixture_engine_with_document(fixture.path(), &source);
|
|
assert!(engine.is_ok());
|
|
let engine = match engine {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
|
let resolved = engine.load_resolved_store_config(std::option::Option::None, &environment);
|
|
assert!(resolved.is_err(), "Store URI without secret provenance must be rejected");
|
|
if let std::result::Result::Err(error) = resolved {
|
|
assert_eq!(error.code(), crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID);
|
|
assert!(!format!("{error:?}").contains("literal.invalid"));
|
|
assert!(!format!("{error:?}").contains("public.invalid"));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn named_store_targets_select_one_network_and_database_without_runtime_multiplexing() {
|
|
let engine = committed_engine();
|
|
let engine = match engine {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let mut process = std::collections::BTreeMap::<String, String>::new();
|
|
process.insert("KSP_SECRET_STORE_DEVNET_POSTGRES_URI".to_owned(), "postgresql://devnet.invalid/ksp_devnet".to_owned());
|
|
process.insert("KSP_SECRET_STORE_MAINNET_POSTGRES_URI".to_owned(), "postgresql://mainnet.invalid/ksp_mainnet".to_owned());
|
|
process.insert("KSP_SECRET_STORE_TESTNET_POSTGRES_URI".to_owned(), "postgresql://testnet.invalid/ksp_testnet".to_owned());
|
|
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
|
for (target_id, network, expected_uri) in [
|
|
("devnet", "devnet", "postgresql://devnet.invalid/ksp_devnet"),
|
|
("mainnet", "mainnet-beta", "postgresql://mainnet.invalid/ksp_mainnet"),
|
|
("testnet", "testnet", "postgresql://testnet.invalid/ksp_testnet"),
|
|
] {
|
|
let resolved = engine.load_resolved_store_config(std::option::Option::Some(target_id), &environment);
|
|
assert!(resolved.is_ok(), "named Store target should resolve independently: {target_id}: {resolved:?}");
|
|
if let std::result::Result::Ok(resolved) = resolved {
|
|
assert_eq!(resolved.target_id(), target_id);
|
|
assert_eq!(resolved.profile_id(), target_id);
|
|
assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
|
assert_eq!(resolved.settings().network().as_str(), network);
|
|
let postgres = match resolved.settings().backend() {
|
|
ksp_store_lib::StoreBackendSettings::Postgres(postgres) => std::option::Option::Some(postgres),
|
|
_ => std::option::Option::None,
|
|
};
|
|
assert!(postgres.is_some(), "committed Store target should remain PostgreSQL: {target_id}");
|
|
if let std::option::Option::Some(postgres) = postgres {
|
|
assert_eq!(postgres.tls_mode(), ksp_store_lib::PostgresTlsMode::Disabled);
|
|
}
|
|
assert_eq!(
|
|
resolved.effective().value().pointer("/postgres/connection_uri").and_then(serde_json::Value::as_str),
|
|
std::option::Option::Some(expected_uri),
|
|
);
|
|
assert!(!resolved.effective().safe_value().to_string().contains(expected_uri));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_003_fix_001_v1_auto_migrate_remains_backward_readable_and_maps_both_schema_policies() {
|
|
for auto_migrate in [false, true] {
|
|
let fixture = tempfile::tempdir();
|
|
assert!(fixture.is_ok());
|
|
let fixture = match fixture {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = committed_document_value();
|
|
let mut source = match source {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
source["format_version"] = serde_json::Value::from(1);
|
|
let profiles = source.get_mut("profiles").and_then(serde_json::Value::as_array_mut);
|
|
if let std::option::Option::Some(profiles) = profiles {
|
|
for profile in profiles {
|
|
let bootstrap = profile.pointer_mut("/postgres/bootstrap").and_then(serde_json::Value::as_object_mut);
|
|
if let std::option::Option::Some(bootstrap) = bootstrap {
|
|
bootstrap.remove("schema_autocreate");
|
|
bootstrap.remove("schema_autoupdate");
|
|
bootstrap.insert("auto_migrate".to_owned(), serde_json::Value::Bool(auto_migrate));
|
|
}
|
|
}
|
|
}
|
|
let engine = fixture_engine_with_document(fixture.path(), &source);
|
|
assert!(engine.is_ok());
|
|
let engine = match engine {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
|
let resolved = engine.load_resolved_store_config(std::option::Option::None, &environment);
|
|
assert!(resolved.is_ok(), "Store Config V1 compatibility mapping failed: {resolved:?}");
|
|
if let std::result::Result::Ok(resolved) = resolved
|
|
&& let ksp_store_lib::StoreBackendSettings::Postgres(postgres) = resolved.settings().backend()
|
|
{
|
|
assert_eq!(postgres.bootstrap().schema_autocreate(), auto_migrate);
|
|
assert_eq!(postgres.bootstrap().schema_autoupdate(), auto_migrate);
|
|
assert_eq!(postgres.bootstrap().auto_migrate(), auto_migrate);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#[test]
|
|
fn pre_003_fix_001_v2_schema_creation_and_update_policies_map_independently() {
|
|
let fixture = tempfile::tempdir();
|
|
assert!(fixture.is_ok());
|
|
let fixture = match fixture {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let source = committed_document_value();
|
|
let mut source = match source {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let profiles = source.get_mut("profiles").and_then(serde_json::Value::as_array_mut);
|
|
if let std::option::Option::Some(profiles) = profiles
|
|
&& let std::option::Option::Some(profile) = profiles.first_mut()
|
|
{
|
|
profile["postgres"]["bootstrap"]["schema_autocreate"] = serde_json::Value::Bool(false);
|
|
profile["postgres"]["bootstrap"]["schema_autoupdate"] = serde_json::Value::Bool(true);
|
|
}
|
|
let engine = fixture_engine_with_document(fixture.path(), &source);
|
|
assert!(engine.is_ok());
|
|
let engine = match engine {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
|
let resolved = engine.load_resolved_store_config(std::option::Option::None, &environment);
|
|
assert!(resolved.is_ok(), "Store Config V2 split schema policy mapping failed: {resolved:?}");
|
|
if let std::result::Result::Ok(resolved) = resolved
|
|
&& let ksp_store_lib::StoreBackendSettings::Postgres(postgres) = resolved.settings().backend()
|
|
{
|
|
assert!(!postgres.bootstrap().schema_autocreate());
|
|
assert!(postgres.bootstrap().schema_autoupdate());
|
|
}
|
|
return;
|
|
}
|
|
|
|
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
|
let workspace = workspace_root();
|
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
|
let bootstrap = match bootstrap {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let registry = crate::ConfigFileRegistry::defaults();
|
|
let registry = match registry {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
|
}
|
|
|
|
fn fixture_engine_with_document(root: &std::path::Path, document: &serde_json::Value) -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
|
let config_root = root.join("config");
|
|
if let std::result::Result::Err(error) = std::fs::create_dir_all(config_root.as_path()) {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "test Config root cannot be created").with_source(error),
|
|
);
|
|
}
|
|
let bytes = serde_json::to_vec_pretty(document);
|
|
let bytes = match bytes {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_SYNTAX_INVALID, "test Store Config cannot be encoded").with_source(error),
|
|
);
|
|
},
|
|
};
|
|
let path = config_root.join(crate::DEFAULT_STD_STORE_FILENAME);
|
|
if let std::result::Result::Err(error) = std::fs::write(path.as_path(), bytes) {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_JSON_FILE_READ_FAILED, "test Store Config cannot be written").with_source(error),
|
|
);
|
|
}
|
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(config_root, workspace_root().join("config/schemas"));
|
|
let bootstrap = match bootstrap {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let registry = crate::ConfigFileRegistry::defaults();
|
|
let registry = match registry {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
|
}
|
|
|
|
fn committed_document_value() -> std::result::Result<serde_json::Value, serde_json::Error> {
|
|
return serde_json::from_str(include_str!("../../../config/std.store.json"));
|
|
}
|
|
|
|
fn workspace_root() -> std::path::PathBuf {
|
|
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
|
}
|