// file: crates/ksp-app-config-desk/unit_tests/logging_editor.rs // version: 5 #[test] fn committed_logging_document_maps_complete_read_only_editor_contract() { let management = committed_management(); assert!(management.is_ok(), "committed management should construct: {management:?}"); if let std::result::Result::Ok(management) = management { let source = management.load_logging_document(); assert!(source.is_ok(), "typed Logging source should load: {source:?}"); let document = super::document_from_management(&management); assert!(document.is_ok(), "Logging editor document should load: {document:?}"); if let (std::result::Result::Ok(source), std::result::Result::Ok(document)) = (source, document) { assert_eq!(document.file_id, ksp_config_lib::FILE_ID_STD_LOGGING); assert_eq!(document.format_version, source.format_version()); assert_eq!(document.logs_directory, source.logs_directory()); assert_eq!(document.default_profile, source.default_profile()); assert_eq!(document.profiles.len(), source.profiles().len()); for (profile, source_profile) in document.profiles.iter().zip(source.profiles()) { assert_eq!(profile.profile_id, source_profile.profile_id()); assert_eq!(profile.default_filter, source_profile.default_filter()); assert_eq!(profile.span_events, source_profile.span_events()); assert_eq!(profile.console.enabled, source_profile.console().enabled()); assert_eq!(profile.console.output, source_profile.console().output()); assert_eq!(profile.console.format, source_profile.console().format()); assert_eq!(profile.files.len(), source_profile.files().len()); assert_eq!(profile.target_filters.len(), source_profile.target_filters().len()); } } } } #[test] fn logging_editor_mapping_preserves_multiple_targets_and_domains() { let filter = ksp_config_lib::LoggingOutputFilterConfig::new( "trace", std::vec!["ksp-config-lib".to_owned(), "ksp-app-config-desk".to_owned()], std::vec!["config".to_owned(), "frontend".to_owned()], ); let mapped = super::project_output_filter(&filter); assert_eq!(mapped.level, "trace"); assert_eq!(mapped.targets, std::vec!["ksp-config-lib".to_owned(), "ksp-app-config-desk".to_owned()]); assert_eq!(mapped.domains, std::vec!["config".to_owned(), "frontend".to_owned()]); } fn committed_management() -> ksp_core_lib::Result { let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."); let bootstrap = ksp_config_lib::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 = ksp_config_lib::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(ksp_config_lib::ConfigManagement::new(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry))); } #[test] fn editor_candidate_round_trips_through_typed_config_contract() { let management = committed_management(); assert!(management.is_ok(), "committed management should construct: {management:?}"); if let std::result::Result::Ok(management) = management { let document = super::document_from_management(&management); assert!(document.is_ok(), "Logging editor document should load: {document:?}"); if let std::result::Result::Ok(document) = document { let candidate = crate::LoggingDocumentCandidateDto { logs_directory: document.logs_directory.clone(), default_profile: document.default_profile.clone(), profiles: document.profiles.clone(), }; let config = super::config_candidate(candidate); assert_eq!(config.logs_directory(), document.logs_directory); assert_eq!(config.default_profile(), document.default_profile); assert_eq!(config.profiles().len(), document.profiles.len()); for (config_profile, document_profile) in config.profiles().iter().zip(document.profiles.iter()) { assert_eq!(config_profile.profile_id(), document_profile.profile_id); assert_eq!(config_profile.files().len(), document_profile.files.len()); for (config_file, document_file) in config_profile.files().iter().zip(document_profile.files.iter()) { assert_eq!(config_file.output_id(), document_file.output_id); assert_eq!(config_file.ansi(), document_file.ansi); } } } } } #[test] fn profile_candidate_preserves_multi_file_and_target_filter_shape() { let profile = crate::LoggingProfileDto { profile_id: "clone_test".to_owned(), default_filter: "info".to_owned(), span_events: "full".to_owned(), console: crate::LoggingConsoleDto { enabled: true, output: "stdout".to_owned(), ansi: false, format: "human".to_owned(), filter: crate::LoggingOutputFilterDto { level: "info".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] }, }, files: std::vec![ crate::LoggingFileDto { output_id: "file.one".to_owned(), enabled: true, path: "one.log".to_owned(), rotation: "daily".to_owned(), format: "human".to_owned(), ansi: false, filter: crate::LoggingOutputFilterDto { level: "debug".to_owned(), targets: std::vec!["*".to_owned()], domains: std::vec!["*".to_owned()] }, }, crate::LoggingFileDto { output_id: "file.two".to_owned(), enabled: false, path: "two.jsonl".to_owned(), rotation: "hourly".to_owned(), format: "json".to_owned(), ansi: false, filter: crate::LoggingOutputFilterDto { level: "error".to_owned(), targets: std::vec!["ksp-config-lib".to_owned()], domains: std::vec!["config".to_owned()], }, }, ], target_filters: std::vec![crate::LoggingTargetFilterDto { target_prefix: "ksp-app-config-desk".to_owned(), level: "trace".to_owned() }], }; let config = super::config_profile(profile); assert_eq!(config.profile_id(), "clone_test"); assert_eq!(config.files().len(), 2); assert_eq!(config.files()[1].rotation(), "hourly"); assert_eq!(config.target_filters().len(), 1); assert_eq!(config.target_filters()[0].level(), "trace"); } #[test] fn runtime_failure_rollback_restores_previous_logging_source() { let fixture = rollback_fixture(); let fixture = match fixture { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; let (root, management, file_id, previous) = fixture; let mut candidate = management.load_logging_document(); assert!(candidate.is_ok(), "rollback fixture should load: {candidate:?}"); if let std::result::Result::Ok(candidate) = candidate.as_mut() { candidate.set_logs_directory(format!("temporary-runtime-failure-{}", std::process::id())); } if let std::result::Result::Ok(candidate) = candidate { let saved = management.save_logging_document(&candidate); assert!(saved.is_ok(), "rollback fixture mutation should persist: {saved:?}"); if let std::result::Result::Ok(saved) = saved { let runtime_error = ksp_core_lib::Error::new(ksp_logging_lib::ERROR_CODE_RELOAD_FAILED, "synthetic runtime reload failure"); let rolled_back = super::rollback_after_runtime_failure(&management, &file_id, previous.as_str(), saved.source_changed(), runtime_error); assert!(rolled_back.is_err(), "runtime failure should remain visible after successful source rollback"); if let std::result::Result::Err(error) = rolled_back { assert_eq!(error.code(), ksp_logging_lib::ERROR_CODE_RELOAD_FAILED); } let restored = std::fs::read_to_string(root.join("config").join(ksp_config_lib::DEFAULT_STD_LOGGING_FILENAME)).ok(); assert_eq!(restored, std::option::Option::Some(previous)); } } let cleanup = std::fs::remove_dir_all(root.as_path()); assert!(cleanup.is_ok(), "rollback fixture should cleanup: {cleanup:?}"); } fn rollback_fixture() -> ksp_core_lib::Result<(std::path::PathBuf, ksp_config_lib::ConfigManagement, ksp_config_lib::ConfigFileId, String)> { let root = std::env::temp_dir().join(format!("ksp-config-desk-logging-rollback-{}", std::process::id())); let cleanup = std::fs::remove_dir_all(root.as_path()); if let std::result::Result::Err(error) = cleanup && error.kind() != std::io::ErrorKind::NotFound { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "cannot cleanup previous rollback fixture").with_source(error), ); } let config_root = root.join("config"); let schema_root = config_root.join("schemas"); let create = std::fs::create_dir_all(schema_root.as_path()); if let std::result::Result::Err(error) = create { return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "cannot create rollback fixture").with_source(error)); } let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."); let source_config = workspace.join("config").join(ksp_config_lib::DEFAULT_STD_LOGGING_FILENAME); let source_schema = workspace.join("config/schemas").join(ksp_config_lib::DEFAULT_STD_LOGGING_SCHEMA_FILENAME); let config_path = config_root.join(ksp_config_lib::DEFAULT_STD_LOGGING_FILENAME); let schema_path = schema_root.join(ksp_config_lib::DEFAULT_STD_LOGGING_SCHEMA_FILENAME); let copied = std::fs::copy(source_config.as_path(), config_path.as_path()); if let std::result::Result::Err(error) = copied { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "cannot copy rollback Config fixture").with_source(error), ); } let copied = std::fs::copy(source_schema.as_path(), schema_path.as_path()); if let std::result::Result::Err(error) = copied { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "cannot copy rollback schema fixture").with_source(error), ); } let previous = std::fs::read_to_string(config_path.as_path()); let previous = match previous { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "cannot read rollback fixture").with_source(error)); }, }; let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(config_root.as_path(), schema_root.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 = ksp_config_lib::ConfigFileRegistry::defaults(); let registry = match registry { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_STD_LOGGING); let file_id = match file_id { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let management = ksp_config_lib::ConfigManagement::new(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, registry)); return std::result::Result::Ok((root, management, file_id, previous)); }