// file: crates/ksp-config-lib/unit_tests/composite.rs // version: 4 const TEST_COMPOSITE_FILENAME: &str = "examples/composite.example.json"; const TEST_COMPOSITE_FILE_ID: &str = "cfg.composite.test"; #[test] fn fixture_composite_example_resolves_default_document_profile() { let engine = fixture_example_engine(); let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID); assert!(engine.is_ok(), "composite test engine should be constructible: {engine:?}"); assert!(file_id.is_ok(), "composite test file_id should be valid: {file_id:?}"); if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) { let resolved = engine.load_resolved_composite(&file_id, std::option::Option::None); assert!(resolved.is_ok(), "fixture composite example should resolve: {resolved:?}"); if let std::result::Result::Ok(resolved) = resolved { assert_eq!(resolved.profile_id(), "local_default"); assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile); let logging = resolved.component("logging"); assert!(logging.is_some(), "logging component should be resolved"); if let std::option::Option::Some(logging) = logging { assert_eq!(logging.resolved().file_id().as_str(), crate::FILE_ID_STD_LOGGING); let logging_document = engine.load_validated_document(logging.resolved().file_id()); assert!(logging_document.is_ok(), "referenced Logging document should validate: {logging_document:?}"); if let std::result::Result::Ok(logging_document) = logging_document { let default_profile = logging_document.value().get("default_profile").and_then(serde_json::Value::as_str); assert_eq!(default_profile, std::option::Option::Some(logging.resolved().profile_id())); } assert_eq!(logging.resolved().selection_source(), crate::ConfigProfileSelectionSource::DefaultProfile); assert_eq!(logging.resolved().origin("logs_directory"), std::option::Option::Some(crate::ConfigValueOrigin::Global)); } } } } #[test] fn composite_profile_override_marks_referenced_profile_selection_as_composite() { let engine = fixture_example_engine(); let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID); assert!(engine.is_ok(), "composite test engine should be constructible: {engine:?}"); assert!(file_id.is_ok(), "composite test file_id should be valid: {file_id:?}"); if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) { let resolved = engine.load_resolved_composite(&file_id, std::option::Option::Some("local_explicit")); assert!(resolved.is_ok(), "explicit composite profile should resolve: {resolved:?}"); if let std::result::Result::Ok(resolved) = resolved { assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Explicit); let logging = resolved.component("logging"); assert!(logging.is_some(), "logging component should be resolved"); if let std::option::Option::Some(logging) = logging { assert_eq!(logging.resolved().profile_id(), "local_dev"); assert_eq!(logging.resolved().selection_source(), crate::ConfigProfileSelectionSource::Composite); } } } } #[test] fn unknown_composite_profile_has_profile_not_found_error() { let engine = fixture_example_engine(); let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID); assert!(engine.is_ok(), "composite test engine should be constructible: {engine:?}"); assert!(file_id.is_ok(), "composite test file_id should be valid: {file_id:?}"); if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) { let result = engine.load_resolved_composite(&file_id, std::option::Option::Some("missing")); assert!(result.is_err(), "unknown composite profile must be rejected: {result:?}"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_PROFILE_NOT_FOUND); } } } #[test] fn unknown_referenced_file_id_is_rejected_during_composite_validation() { let fixture = fixture_roots("unknown-reference"); let composite = r#"{ "format_version": 1, "default_profile": "test", "profiles": [{ "profile_id": "test", "documents": [{"component_id": "missing", "file_id": "cfg.std.missing"}] }] }"#; let prepared = prepare_fixture(&fixture, composite); assert!(prepared.is_ok(), "composite fixture should be writable: {prepared:?}"); if prepared.is_ok() { let engine = fixture_engine(&fixture); let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID); assert!(engine.is_ok(), "fixture engine should be constructible: {engine:?}"); if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) { let result = engine.load_validated_document(&file_id); assert!(result.is_err(), "unknown composite reference must be rejected: {result:?}"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_COMPOSITE_REFERENCE_INVALID); } } } cleanup_fixture(&fixture); } #[test] fn duplicate_component_ids_are_rejected_inside_one_composite_profile() { let fixture = fixture_roots("duplicate-component"); let composite = r#"{ "format_version": 1, "default_profile": "test", "profiles": [{ "profile_id": "test", "documents": [ {"component_id": "logging", "file_id": "cfg.std.logging"}, {"component_id": "logging", "file_id": "cfg.std.logging"} ] }] }"#; let prepared = prepare_fixture(&fixture, composite); assert!(prepared.is_ok(), "composite fixture should be writable: {prepared:?}"); if prepared.is_ok() { let engine = fixture_engine(&fixture); let file_id = crate::ConfigFileId::new(TEST_COMPOSITE_FILE_ID); assert!(engine.is_ok(), "fixture engine should be constructible: {engine:?}"); if let (std::result::Result::Ok(engine), std::result::Result::Ok(file_id)) = (engine, file_id) { let result = engine.load_validated_document(&file_id); assert!(result.is_err(), "duplicate component_id must be rejected: {result:?}"); if let std::result::Result::Err(error) = result { assert_eq!(error.code(), crate::ERROR_CODE_DOCUMENT_SEMANTIC_INVALID); } } } cleanup_fixture(&fixture); } fn fixture_example_engine() -> ksp_core_lib::Result { let workspace = workspace_root(); let fixture_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("unit_tests/fixtures"); let bootstrap = crate::ConfigBootstrapOptions::from_paths(fixture_root, 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 = test_registry(TEST_COMPOSITE_FILENAME); return match registry { std::result::Result::Ok(value) => std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, value)), std::result::Result::Err(error) => std::result::Result::Err(error), }; } fn fixture_engine(fixture: &FixtureRoots) -> ksp_core_lib::Result { let bootstrap = crate::ConfigBootstrapOptions::from_paths(fixture.config.as_path(), fixture.schemas.as_path()); let bootstrap = match bootstrap { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let registry = test_registry("composite.test.json"); return match registry { std::result::Result::Ok(value) => std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, value)), std::result::Result::Err(error) => std::result::Result::Err(error), }; } fn test_registry(composite_filename: &'static str) -> ksp_core_lib::Result { let logging = crate::registry::ConfigFileDescriptor::new( crate::FILE_ID_STD_LOGGING, crate::ConfigFileKind::Config, crate::DEFAULT_STD_LOGGING_FILENAME, std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_LOGGING), ); let logging = match logging { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let logging_schema = crate::registry::ConfigFileDescriptor::new( crate::FILE_ID_SCHEMA_STD_LOGGING, crate::ConfigFileKind::Schema, crate::DEFAULT_STD_LOGGING_SCHEMA_FILENAME, std::option::Option::None, ); let logging_schema = match logging_schema { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let composite_schema = crate::registry::ConfigFileDescriptor::new( crate::FILE_ID_SCHEMA_COMPOSITE, crate::ConfigFileKind::Schema, crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME, std::option::Option::None, ); let composite_schema = match composite_schema { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let composite = crate::registry::ConfigFileDescriptor::new( TEST_COMPOSITE_FILE_ID, crate::ConfigFileKind::Config, composite_filename, std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE), ); let composite = match composite { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return crate::build_registry([logging, logging_schema, composite_schema, composite]); } fn prepare_fixture(fixture: &FixtureRoots, composite: &str) -> std::io::Result<()> { cleanup_fixture(fixture); let config = std::fs::create_dir_all(fixture.config.as_path()); if let std::result::Result::Err(error) = config { return std::result::Result::Err(error); } let schemas = std::fs::create_dir_all(fixture.schemas.as_path()); if let std::result::Result::Err(error) = schemas { return std::result::Result::Err(error); } let workspace = workspace_root(); let stable_logging = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("unit_tests/fixtures/std.logging.json"); let logging = std::fs::copy(stable_logging, fixture.config.join(crate::DEFAULT_STD_LOGGING_FILENAME)); if let std::result::Result::Err(error) = logging { return std::result::Result::Err(error); } let logging_schema = std::fs::copy(workspace.join("config/schemas/std.logging.schema.json"), fixture.schemas.join(crate::DEFAULT_STD_LOGGING_SCHEMA_FILENAME)); if let std::result::Result::Err(error) = logging_schema { return std::result::Result::Err(error); } let composite_schema = std::fs::copy(workspace.join("config/schemas/composite.schema.json"), fixture.schemas.join(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME)); if let std::result::Result::Err(error) = composite_schema { return std::result::Result::Err(error); } return std::fs::write(fixture.config.join("composite.test.json"), composite); } fn workspace_root() -> std::path::PathBuf { return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."); } struct FixtureRoots { root: std::path::PathBuf, config: std::path::PathBuf, schemas: std::path::PathBuf, } fn fixture_roots(name: &str) -> FixtureRoots { let root = std::env::temp_dir().join(format!("ksp-config-composite-{name}-{}", std::process::id())); return FixtureRoots { config: root.join("config"), schemas: root.join("schemas"), root }; } fn cleanup_fixture(fixture: &FixtureRoots) { let removal = std::fs::remove_dir_all(fixture.root.as_path()); if let std::result::Result::Err(error) = removal && error.kind() != std::io::ErrorKind::NotFound { eprintln!("unable to cleanup Config composite fixture {}: {error}", fixture.root.display()); } }