226 lines
12 KiB
Rust
226 lines
12 KiB
Rust
// file: crates/ksp-app-config-desk/unit_tests/logging_editor.rs
|
|
// version: 7
|
|
|
|
#[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<ksp_config_lib::ConfigManagement> {
|
|
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));
|
|
}
|