Files
2026-08-15 20:22:02 +02:00

227 lines
10 KiB
Rust

// file: crates/ksp-logging-lib/unit_tests/settings.rs
// version: 3
fn unrestricted_file(output_id: &str) -> crate::FileSettings {
return crate::FileSettings::new(
output_id,
true,
"logs",
format!("{output_id}.log"),
crate::FileRotation::Daily,
crate::LogFormat::Human,
crate::OutputFilter::unrestricted(),
);
}
#[test]
fn level_variants_are_distinct() {
assert_ne!(crate::LogFilterLevel::Off, crate::LogFilterLevel::Error);
assert_ne!(crate::LogFilterLevel::Error, crate::LogFilterLevel::Warn);
assert_ne!(crate::LogFilterLevel::Warn, crate::LogFilterLevel::Info);
assert_ne!(crate::LogFilterLevel::Info, crate::LogFilterLevel::Debug);
assert_ne!(crate::LogFilterLevel::Debug, crate::LogFilterLevel::Trace);
}
#[test]
fn format_variants_are_distinct() {
assert_ne!(crate::LogFormat::Human, crate::LogFormat::Compact);
assert_ne!(crate::LogFormat::Compact, crate::LogFormat::Pretty);
assert_ne!(crate::LogFormat::Pretty, crate::LogFormat::Json);
}
#[test]
fn target_filter_preserves_prefix_and_level() {
let filter = crate::TargetFilter::new("ksp-store-lib", crate::LogFilterLevel::Trace);
assert_eq!(filter.target_prefix(), "ksp-store-lib");
assert_eq!(filter.level(), crate::LogFilterLevel::Trace);
}
#[test]
fn output_filter_preserves_level_targets_and_domains() {
let filter = crate::OutputFilter::new(
crate::LogFilterLevel::Debug,
std::vec!["ksp-config-lib".to_string(), "ksp-logging-lib".to_string()],
std::vec!["config".to_string(), "logging.runtime".to_string()],
);
assert_eq!(filter.level(), crate::LogFilterLevel::Debug);
assert_eq!(filter.targets().len(), 2);
assert_eq!(filter.targets()[0], "ksp-config-lib");
assert_eq!(filter.targets()[1], "ksp-logging-lib");
assert_eq!(filter.domains().len(), 2);
assert_eq!(filter.domains()[0], "config");
assert_eq!(filter.domains()[1], "logging.runtime");
let unrestricted = crate::OutputFilter::unrestricted();
assert_eq!(unrestricted.level(), crate::LogFilterLevel::Trace);
assert_eq!(unrestricted.targets(), &["*".to_string()]);
assert_eq!(unrestricted.domains(), &["*".to_string()]);
}
#[test]
fn console_settings_preserve_enabled_stream_ansi_format_and_filter() {
let filter = crate::OutputFilter::new(crate::LogFilterLevel::Debug, std::vec!["*".to_string()], std::vec!["config".to_string()]);
let settings = crate::ConsoleSettings::new(true, crate::ConsoleOutput::Stderr, true, crate::LogFormat::Compact, filter.clone());
assert!(settings.enabled());
assert_eq!(settings.output(), crate::ConsoleOutput::Stderr);
assert!(settings.ansi());
assert_eq!(settings.format(), crate::LogFormat::Compact);
assert_eq!(settings.filter(), &filter);
}
#[test]
fn compatibility_console_constructors_are_unrestricted_human_and_non_ansi() {
let stdout = crate::ConsoleSettings::stdout();
let stderr = crate::ConsoleSettings::stderr();
assert_eq!(stdout.output(), crate::ConsoleOutput::Stdout);
assert_eq!(stderr.output(), crate::ConsoleOutput::Stderr);
assert!(stdout.enabled());
assert!(!stdout.ansi());
assert_eq!(stdout.format(), crate::LogFormat::Human);
assert_eq!(stdout.filter(), &crate::OutputFilter::unrestricted());
}
#[test]
fn file_settings_preserve_multi_output_contract() {
let filter = crate::OutputFilter::new(crate::LogFilterLevel::Error, std::vec!["ksp-config-lib".to_string()], std::vec!["config".to_string()]);
let settings =
crate::FileSettings::new("file.config.error", true, "logs/config", "error.jsonl", crate::FileRotation::Daily, crate::LogFormat::Json, filter.clone());
assert_eq!(settings.output_id(), "file.config.error");
assert!(settings.enabled());
assert_eq!(settings.directory(), std::path::Path::new("logs/config"));
assert_eq!(settings.file_name_prefix(), "error.jsonl");
assert_eq!(settings.rotation(), crate::FileRotation::Daily);
assert_eq!(settings.format(), crate::LogFormat::Json);
assert!(!settings.ansi());
assert_eq!(settings.filter(), &filter);
}
#[test]
fn logging_settings_represent_multiple_file_outputs() {
let first = unrestricted_file("file.debug");
let second = crate::FileSettings::new(
"file.config.error",
false,
"logs/config",
"error.jsonl",
crate::FileRotation::Hourly,
crate::LogFormat::Json,
crate::OutputFilter::new(crate::LogFilterLevel::Error, std::vec!["ksp-config-lib".to_string()], std::vec!["config".to_string()]),
);
let settings = crate::LoggingSettings::new(
crate::LogFilterLevel::Info,
crate::SpanEvents::NewAndClose,
std::option::Option::Some(crate::ConsoleSettings::stdout()),
std::vec![first, second],
)
.with_target_filter(crate::TargetFilter::new("ksp-store-lib", crate::LogFilterLevel::Trace));
assert_eq!(settings.default_filter(), crate::LogFilterLevel::Info);
assert_eq!(settings.span_events(), crate::SpanEvents::NewAndClose);
assert_eq!(settings.target_filters().len(), 1);
assert_eq!(settings.files().len(), 2);
assert_eq!(settings.files()[0].output_id(), "file.debug");
assert_eq!(settings.files()[1].format(), crate::LogFormat::Json);
assert_eq!(settings.console().map(crate::ConsoleSettings::output), std::option::Option::Some(crate::ConsoleOutput::Stdout));
assert!(settings.validate().is_ok());
}
#[test]
fn validation_rejects_empty_or_external_global_target_prefix() {
let empty = crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::None, std::vec::Vec::new())
.with_target_filter(crate::TargetFilter::new("", crate::LogFilterLevel::Debug));
let external = crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::None, std::vec::Vec::new())
.with_target_filter(crate::TargetFilter::new("sqlx", crate::LogFilterLevel::Debug));
assert!(empty.validate().is_err());
assert!(external.validate().is_err());
}
#[test]
fn validation_rejects_invalid_file_identity_and_duplicate_output_ids() {
let invalid_id =
crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::None, std::vec![unrestricted_file("File.Debug")]);
let duplicate_id = crate::LoggingSettings::new(
crate::LogFilterLevel::Info,
crate::SpanEvents::Off,
std::option::Option::None,
std::vec![unrestricted_file("file.debug"), unrestricted_file("file.debug")],
);
assert!(invalid_id.validate().is_err());
assert!(duplicate_id.validate().is_err());
}
#[test]
fn validation_rejects_empty_file_paths_and_persistent_ansi() {
let empty_directory = crate::FileSettings::new(
"file.empty.dir",
true,
"",
"debug.log",
crate::FileRotation::Never,
crate::LogFormat::Human,
crate::OutputFilter::unrestricted(),
);
let empty_prefix = crate::FileSettings::new(
"file.empty.prefix",
true,
"logs",
"",
crate::FileRotation::Never,
crate::LogFormat::Human,
crate::OutputFilter::unrestricted(),
);
let ansi_file = crate::FileSettings::new(
"file.ansi",
true,
"logs",
"ansi.log",
crate::FileRotation::Never,
crate::LogFormat::Human,
crate::OutputFilter::unrestricted(),
)
.with_ansi(true);
let empty_directory_settings =
crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::None, std::vec![empty_directory]);
let empty_prefix_settings =
crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::None, std::vec![empty_prefix]);
let ansi_file_settings = crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::None, std::vec![ansi_file]);
assert!(empty_directory_settings.validate().is_err());
assert!(empty_prefix_settings.validate().is_err());
assert!(ansi_file_settings.validate().is_err());
}
#[test]
fn validation_rejects_ansi_json_console() {
let console = crate::ConsoleSettings::new(true, crate::ConsoleOutput::Stdout, true, crate::LogFormat::Json, crate::OutputFilter::unrestricted());
let settings = crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::Some(console), std::vec::Vec::new());
assert!(settings.validate().is_err());
}
#[test]
fn validation_rejects_invalid_output_selectors() {
let external_target = crate::OutputFilter::new(crate::LogFilterLevel::Debug, std::vec!["sqlx".to_string()], std::vec!["*".to_string()]);
let wildcard_mix =
crate::OutputFilter::new(crate::LogFilterLevel::Debug, std::vec!["*".to_string(), "ksp-config-lib".to_string()], std::vec!["*".to_string()]);
let duplicate_domain =
crate::OutputFilter::new(crate::LogFilterLevel::Debug, std::vec!["*".to_string()], std::vec!["config".to_string(), "config".to_string()]);
for filter in [external_target, wildcard_mix, duplicate_domain] {
let console = crate::ConsoleSettings::new(true, crate::ConsoleOutput::Stdout, false, crate::LogFormat::Human, filter);
let settings =
crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::Some(console), std::vec::Vec::new());
assert!(settings.validate().is_err());
}
}
#[test]
fn settings_allow_all_outputs_to_be_disabled() {
let console = crate::ConsoleSettings::new(false, crate::ConsoleOutput::Stderr, true, crate::LogFormat::Pretty, crate::OutputFilter::unrestricted());
let file = crate::FileSettings::new(
"file.disabled",
false,
"logs",
"disabled.jsonl",
crate::FileRotation::Daily,
crate::LogFormat::Json,
crate::OutputFilter::new(crate::LogFilterLevel::Error, std::vec!["ksp-config-lib".to_string()], std::vec!["config".to_string()]),
);
let settings = crate::LoggingSettings::new(crate::LogFilterLevel::Off, crate::SpanEvents::Off, std::option::Option::Some(console), std::vec![file]);
assert!(settings.validate().is_ok());
}