// file: crates/ksp-config-lib/unit_tests/registry.rs // version: 9 #[test] fn descriptors_expose_complete_registry_in_deterministic_file_id_order() { let registry = crate::ConfigFileRegistry::defaults(); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let descriptors: std::vec::Vec<&crate::ConfigFileDescriptor> = registry.descriptors().collect(); assert_eq!(descriptors.len(), 10); assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); assert_eq!(descriptors[0].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); assert_eq!(descriptors[0].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE)); assert_eq!(descriptors[1].file_id().as_str(), crate::FILE_ID_STD_LOGGING); assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT); assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT); assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_STD_WALLET); assert_eq!(descriptors[4].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME)); assert_eq!(descriptors[4].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_WALLET)); assert_eq!(descriptors[5].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE); assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING); assert_eq!(descriptors[7].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT); assert_eq!(descriptors[8].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT); assert_eq!(descriptors[9].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_WALLET); assert!(descriptors[0..5].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Config)); assert!(descriptors[5..10].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Schema)); } } #[test] fn descriptors_reflect_filename_overrides_without_changing_logical_metadata() { let registry = crate::ConfigFileRegistry::defaults(); let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); assert!(file_id.is_ok(), "logging file_id should be valid: {file_id:?}"); if let (std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (registry, file_id) { let overridden = registry.with_filename_override(&file_id, "profiles/desktop.logging.json"); assert!(overridden.is_ok(), "filename override should remain valid: {overridden:?}"); if let std::result::Result::Ok(overridden) = overridden { let mut found: std::option::Option<&crate::ConfigFileDescriptor> = std::option::Option::None; for descriptor in overridden.descriptors() { if descriptor.file_id() == &file_id { found = std::option::Option::Some(descriptor); } } assert!(found.is_some(), "public descriptor inventory should retain the overridden logging descriptor"); if let std::option::Option::Some(descriptor) = found { assert_eq!(descriptor.file_id(), &file_id); assert_eq!(descriptor.kind(), crate::ConfigFileKind::Config); assert_eq!(descriptor.filename(), std::path::Path::new("profiles/desktop.logging.json")); let schema_file_id = descriptor.schema_file_id(); assert!(schema_file_id.is_some(), "filename override should preserve schema association"); if let std::option::Option::Some(schema_file_id) = schema_file_id { assert_eq!(schema_file_id.as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING); } } } } } #[test] fn defaults_register_logging_document_and_schema_with_distinct_roots() { let registry = crate::ConfigFileRegistry::defaults(); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let logging_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING); let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_LOGGING); assert!(logging_id.is_ok(), "logging file_id should be valid: {logging_id:?}"); assert!(schema_id.is_ok(), "logging schema file_id should be valid: {schema_id:?}"); if let (std::result::Result::Ok(logging_id), std::result::Result::Ok(schema_id)) = (logging_id, schema_id) { let logging = registry.descriptor(&logging_id); let schema = registry.descriptor(&schema_id); assert!(logging.is_ok(), "logging descriptor should exist: {logging:?}"); assert!(schema.is_ok(), "logging schema descriptor should exist: {schema:?}"); if let (std::result::Result::Ok(logging), std::result::Result::Ok(schema)) = (logging, schema) { assert_eq!(logging.kind(), crate::ConfigFileKind::Config); assert_eq!(logging.filename(), std::path::Path::new(crate::DEFAULT_STD_LOGGING_FILENAME)); let logging_schema = logging.schema_file_id(); assert!(logging_schema.is_some(), "logging document should declare its validation schema"); if let std::option::Option::Some(logging_schema) = logging_schema { assert_eq!(logging_schema, &schema_id); } assert_eq!(schema.kind(), crate::ConfigFileKind::Schema); assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_STD_LOGGING_SCHEMA_FILENAME)); } } } } #[test] fn defaults_register_offchain_transport_document_and_schema_with_distinct_roots() { let registry = crate::ConfigFileRegistry::defaults(); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let config_id = crate::ConfigFileId::new(crate::FILE_ID_STD_OFFCHAIN_TRANSPORT); let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT); assert!(config_id.is_ok(), "Off-chain Transport file_id should be valid: {config_id:?}"); assert!(schema_id.is_ok(), "Off-chain Transport schema file_id should be valid: {schema_id:?}"); if let (std::result::Result::Ok(config_id), std::result::Result::Ok(schema_id)) = (config_id, schema_id) { let config = registry.descriptor(&config_id); let schema = registry.descriptor(&schema_id); assert!(config.is_ok(), "Off-chain Transport descriptor should exist: {config:?}"); assert!(schema.is_ok(), "Off-chain Transport schema descriptor should exist: {schema:?}"); if let (std::result::Result::Ok(config), std::result::Result::Ok(schema)) = (config, schema) { assert_eq!(config.kind(), crate::ConfigFileKind::Config); assert_eq!(config.filename(), std::path::Path::new(crate::DEFAULT_STD_OFFCHAIN_TRANSPORT_FILENAME)); assert_eq!(config.schema_file_id(), std::option::Option::Some(&schema_id)); assert_eq!(schema.kind(), crate::ConfigFileKind::Schema); assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_STD_OFFCHAIN_TRANSPORT_SCHEMA_FILENAME)); } } } } #[test] fn defaults_register_transport_document_and_schema_with_distinct_roots() { let registry = crate::ConfigFileRegistry::defaults(); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let transport_id = crate::ConfigFileId::new(crate::FILE_ID_STD_TRANSPORT); let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_TRANSPORT); assert!(transport_id.is_ok(), "transport file_id should be valid: {transport_id:?}"); assert!(schema_id.is_ok(), "transport schema file_id should be valid: {schema_id:?}"); if let (std::result::Result::Ok(transport_id), std::result::Result::Ok(schema_id)) = (transport_id, schema_id) { let transport = registry.descriptor(&transport_id); let schema = registry.descriptor(&schema_id); assert!(transport.is_ok(), "transport descriptor should exist: {transport:?}"); assert!(schema.is_ok(), "transport schema descriptor should exist: {schema:?}"); if let (std::result::Result::Ok(transport), std::result::Result::Ok(schema)) = (transport, schema) { assert_eq!(transport.kind(), crate::ConfigFileKind::Config); assert_eq!(transport.filename(), std::path::Path::new(crate::DEFAULT_STD_TRANSPORT_FILENAME)); assert_eq!(transport.schema_file_id(), std::option::Option::Some(&schema_id)); assert_eq!(schema.kind(), crate::ConfigFileKind::Schema); assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME)); } } } } #[test] fn defaults_register_wallet_document_schema_and_wallet_desk_composite() { let registry = crate::ConfigFileRegistry::defaults(); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let wallet_id = crate::ConfigFileId::new(crate::FILE_ID_STD_WALLET); let wallet_schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_WALLET); let composite_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); assert!(wallet_id.is_ok()); assert!(wallet_schema_id.is_ok()); assert!(composite_id.is_ok()); if let (std::result::Result::Ok(wallet_id), std::result::Result::Ok(wallet_schema_id), std::result::Result::Ok(composite_id)) = (wallet_id, wallet_schema_id, composite_id) { let wallet = registry.descriptor(&wallet_id); let wallet_schema = registry.descriptor(&wallet_schema_id); let composite = registry.descriptor(&composite_id); assert!(wallet.is_ok()); assert!(wallet_schema.is_ok()); assert!(composite.is_ok()); if let (std::result::Result::Ok(wallet), std::result::Result::Ok(wallet_schema), std::result::Result::Ok(composite)) = (wallet, wallet_schema, composite) { assert_eq!(wallet.filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME)); assert_eq!(wallet.schema_file_id(), std::option::Option::Some(&wallet_schema_id)); assert_eq!(wallet_schema.filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_SCHEMA_FILENAME)); assert_eq!(composite.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); assert_eq!(composite.schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE)); } } } } #[test] fn resolve_path_uses_descriptor_kind_to_select_bootstrap_root() { let registry = crate::ConfigFileRegistry::defaults(); let bootstrap = crate::ConfigBootstrapOptions::from_paths("runtime-config", "runtime-schemas"); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); assert!(bootstrap.is_ok(), "bootstrap paths should be valid: {bootstrap:?}"); if let (std::result::Result::Ok(registry), std::result::Result::Ok(bootstrap)) = (registry, bootstrap) { let logging_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING); let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_LOGGING); if let (std::result::Result::Ok(logging_id), std::result::Result::Ok(schema_id)) = (logging_id, schema_id) { let logging = registry.resolve_path(&bootstrap, &logging_id); let schema = registry.resolve_path(&bootstrap, &schema_id); assert!(logging.is_ok(), "logging path should resolve: {logging:?}"); assert!(schema.is_ok(), "schema path should resolve: {schema:?}"); if let std::result::Result::Ok(logging) = logging { assert_eq!(logging, std::path::PathBuf::from("runtime-config/std.logging.json")); } if let std::result::Result::Ok(schema) = schema { assert_eq!(schema, std::path::PathBuf::from("runtime-schemas/std.logging.schema.json")); } } } } #[test] fn cli_filemap_override_replaces_filename_and_last_value_wins() { let args = [ std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--filemap=cfg.std.logging=first.logging.json"), std::ffi::OsString::from("--other-option"), std::ffi::OsString::from("--filemap=cfg.std.logging=profiles/custom.logging.json"), ]; let registry = crate::ConfigFileRegistry::from_args(&args); assert!(registry.is_ok(), "filemap overrides should parse: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING); if let std::result::Result::Ok(file_id) = file_id { let descriptor = registry.descriptor(&file_id); assert!(descriptor.is_ok(), "logging descriptor should remain registered: {descriptor:?}"); if let std::result::Result::Ok(descriptor) = descriptor { assert_eq!(descriptor.filename(), std::path::Path::new("profiles/custom.logging.json")); assert_eq!(descriptor.kind(), crate::ConfigFileKind::Config); } } } } #[test] fn programmatic_override_preserves_file_id_and_kind() { let registry = crate::ConfigFileRegistry::defaults(); let file_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_STD_LOGGING); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); assert!(file_id.is_ok(), "schema file_id should be valid: {file_id:?}"); if let (std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (registry, file_id) { let overridden = registry.with_filename_override(&file_id, "alternate/logging.schema.json"); assert!(overridden.is_ok(), "programmatic override should be valid: {overridden:?}"); if let std::result::Result::Ok(overridden) = overridden { let descriptor = overridden.descriptor(&file_id); if let std::result::Result::Ok(descriptor) = descriptor { assert_eq!(descriptor.file_id(), &file_id); assert_eq!(descriptor.kind(), crate::ConfigFileKind::Schema); assert_eq!(descriptor.filename(), std::path::Path::new("alternate/logging.schema.json")); } } } } #[test] fn unknown_file_id_override_is_rejected() { let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from("--filemap=cfg.unknown=unknown.json")]; let result = crate::ConfigFileRegistry::from_args(&args); assert!(result.is_err(), "unknown logical files must not be introduced by CLI override"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_FILE_ID_UNKNOWN); } } #[test] fn invalid_file_ids_are_rejected() { for value in ["", ".cfg", "cfg.", "cfg..logging", "CFG.logging", "cfg/logging"] { let result = crate::ConfigFileId::new(value); assert!(result.is_err(), "invalid file_id must be rejected: {value}"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_FILE_ID_INVALID); } } } #[test] fn absolute_and_traversing_filenames_are_rejected() { let registry = crate::ConfigFileRegistry::defaults(); let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_LOGGING); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); assert!(file_id.is_ok(), "logging file_id should be valid: {file_id:?}"); if let (std::result::Result::Ok(registry), std::result::Result::Ok(file_id)) = (registry, file_id) { let traversal = registry.clone().with_filename_override(&file_id, "../outside.json"); let current = registry.clone().with_filename_override(&file_id, "./logging.json"); let absolute = registry.with_filename_override(&file_id, absolute_fixture_path()); assert_mapping_invalid(traversal); assert_mapping_invalid(current); assert_mapping_invalid(absolute); } } #[test] fn malformed_filemap_arguments_are_rejected() { for argument in ["--filemap", "--filemap=cfg.std.logging", "--filemap==logging.json", "--filemap=cfg.std.logging="] { let args = [std::ffi::OsString::from("ksp-app"), std::ffi::OsString::from(argument)]; let result = crate::ConfigFileRegistry::from_args(&args); assert!(result.is_err(), "malformed filemap argument must be rejected: {argument}"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_FILE_MAPPING_INVALID); } } } #[test] fn duplicate_registry_ids_are_rejected() { let first = crate::ConfigFileDescriptor::new("cfg.duplicate", crate::ConfigFileKind::Config, "first.json", std::option::Option::None); let second = crate::ConfigFileDescriptor::new("cfg.duplicate", crate::ConfigFileKind::Config, "second.json", std::option::Option::None); assert!(first.is_ok(), "first descriptor should be valid: {first:?}"); assert!(second.is_ok(), "second descriptor should be valid: {second:?}"); if let (std::result::Result::Ok(first), std::result::Result::Ok(second)) = (first, second) { let result = crate::build_registry([first, second]); assert!(result.is_err(), "duplicate file_ids must be rejected"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_FILE_ID_DUPLICATE); } } } #[test] fn descriptor_kind_must_match_file_id_namespace() { let result = crate::ConfigFileDescriptor::new("schema.invalid-kind", crate::ConfigFileKind::Config, "invalid.json", std::option::Option::None); assert!(result.is_err(), "descriptor kind mismatch must be rejected"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_FILE_MAPPING_INVALID); } } #[test] fn config_schema_association_must_reference_registered_schema_descriptor() { let config = crate::ConfigFileDescriptor::new("cfg.test", crate::ConfigFileKind::Config, "test.json", std::option::Option::Some("schema.test")); assert!(config.is_ok(), "config descriptor should be valid before registry association validation: {config:?}"); if let std::result::Result::Ok(config) = config { let result = crate::build_registry([config]); assert!(result.is_err(), "registry must reject a missing schema association"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_FILE_MAPPING_INVALID); } } } fn assert_mapping_invalid(result: ksp_core_lib::Result) { assert!(result.is_err(), "invalid filename must be rejected: {result:?}"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_FILE_MAPPING_INVALID); } } fn absolute_fixture_path() -> std::path::PathBuf { let mut path = std::env::temp_dir(); path.push("ksp-config-lib-absolute-mapping.json"); return path; } #[test] fn defaults_register_generic_composite_schema_and_wallet_desk_runtime_composite() { let registry = crate::ConfigFileRegistry::defaults(); let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_COMPOSITE); let runtime_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); assert!(schema_id.is_ok(), "composite schema file_id should be valid: {schema_id:?}"); assert!(runtime_id.is_ok(), "Wallet Desk composite runtime file_id should be valid: {runtime_id:?}"); if let (std::result::Result::Ok(registry), std::result::Result::Ok(schema_id), std::result::Result::Ok(runtime_id)) = (registry, schema_id, runtime_id) { let schema = registry.descriptor(&schema_id); let runtime = registry.descriptor(&runtime_id); assert!(schema.is_ok(), "generic composite schema should be registered: {schema:?}"); assert!(runtime.is_ok(), "Wallet Desk runtime composite should be registered: {runtime:?}"); if let (std::result::Result::Ok(schema), std::result::Result::Ok(runtime)) = (schema, runtime) { assert_eq!(schema.kind(), crate::ConfigFileKind::Schema); assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME)); assert_eq!(runtime.kind(), crate::ConfigFileKind::Config); assert_eq!(runtime.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); assert_eq!(runtime.schema_file_id(), std::option::Option::Some(&schema_id)); } } }