use std::collections::HashMap;
use serde_json::json;
use provide_telemetry::testing::acquire_test_state_lock;
use provide_telemetry::{
configure_logging, enable_json_capture_for_tests, get_logger, reset_logging_config_for_tests,
sanitize_payload, set_queue_policy, set_strict_schema, take_json_capture, LoggingConfig,
QueuePolicy, Signal, TelemetryConfig,
};
#[test]
fn parity_test_config_headers_plus_preserved() {
let mut env = HashMap::new();
env.insert(
"OTEL_EXPORTER_OTLP_HEADERS".to_string(),
"Authorization=Bearer+token".to_string(),
);
let cfg = TelemetryConfig::from_map(&env).expect("parse should succeed");
assert_eq!(
cfg.logging
.otlp_headers
.get("Authorization")
.map(String::as_str),
Some("Bearer+token"),
"plus sign must be preserved as literal"
);
}
#[test]
fn parity_test_config_headers_percent_encoded_space() {
let mut env = HashMap::new();
env.insert(
"OTEL_EXPORTER_OTLP_HEADERS".to_string(),
"my%20key=my%20value".to_string(),
);
let cfg = TelemetryConfig::from_map(&env).expect("parse should succeed");
assert_eq!(
cfg.logging.otlp_headers.get("my key").map(String::as_str),
Some("my value"),
"percent-encoded spaces must decode to spaces"
);
}
#[test]
fn parity_test_config_headers_empty_key_skipped() {
let mut env = HashMap::new();
env.insert(
"OTEL_EXPORTER_OTLP_HEADERS".to_string(),
"=value,key=val".to_string(),
);
let cfg = TelemetryConfig::from_map(&env).expect("parse should succeed");
assert!(
!cfg.logging.otlp_headers.contains_key(""),
"empty key must be skipped"
);
assert_eq!(
cfg.logging.otlp_headers.get("key").map(String::as_str),
Some("val"),
);
}
#[test]
fn parity_test_config_headers_no_equals_skipped() {
let mut env = HashMap::new();
env.insert(
"OTEL_EXPORTER_OTLP_HEADERS".to_string(),
"malformed,key=val".to_string(),
);
let cfg = TelemetryConfig::from_map(&env).expect("parse should succeed");
assert!(
!cfg.logging.otlp_headers.contains_key("malformed"),
"no-equals entry must be skipped"
);
assert_eq!(
cfg.logging.otlp_headers.get("key").map(String::as_str),
Some("val"),
);
}
#[test]
fn parity_test_config_headers_value_containing_equals() {
let mut env = HashMap::new();
env.insert(
"OTEL_EXPORTER_OTLP_HEADERS".to_string(),
"Authorization=Bearer token=xyz".to_string(),
);
let cfg = TelemetryConfig::from_map(&env).expect("parse should succeed");
assert_eq!(
cfg.logging
.otlp_headers
.get("Authorization")
.map(String::as_str),
Some("Bearer token=xyz"),
"value containing '=' must be preserved intact"
);
}
#[test]
fn parity_test_config_headers_empty_string_returns_empty() {
let mut env = HashMap::new();
env.insert("OTEL_EXPORTER_OTLP_HEADERS".to_string(), String::new());
let cfg = TelemetryConfig::from_map(&env).expect("parse should succeed");
assert!(
cfg.logging.otlp_headers.is_empty(),
"empty header string must produce empty map"
);
}
#[test]
fn parity_test_default_sensitive_keys_all_redacted() {
let canonical_keys = [
"password",
"passwd",
"secret",
"token",
"api_key",
"apikey",
"auth",
"authorization",
"credential",
"private_key",
"ssn",
"credit_card",
"creditcard",
"cvv",
"pin",
"account_number",
"cookie",
];
for key in &canonical_keys {
let payload = json!({ *key: "sensitive-value" });
let result = sanitize_payload(&payload, true, 32);
assert_eq!(
result[*key], "***",
"default-sensitive key '{key}' must be auto-redacted"
);
}
}
#[test]
fn parity_test_default_sensitive_keys_case_insensitive() {
let payload = json!({"API_KEY": "abc123", "PASSWORD": "secret123"}); let result = sanitize_payload(&payload, true, 32);
assert_eq!(result["API_KEY"], "***", "API_KEY must be redacted");
assert_eq!(result["PASSWORD"], "***", "PASSWORD must be redacted"); }
#[test]
fn parity_test_backpressure_unlimited_zero_size_always_succeeds() {
let _guard = acquire_test_state_lock();
provide_telemetry::backpressure::_reset_backpressure_for_tests();
set_queue_policy(QueuePolicy {
logs_maxsize: 0,
traces_maxsize: 0,
metrics_maxsize: 0,
});
let ticket = provide_telemetry::try_acquire(Signal::Logs);
assert!(ticket.is_some(), "size=0 must always succeed (unlimited)");
}
#[test]
fn parity_test_backpressure_unlimited_100_acquires_all_succeed() {
let _guard = acquire_test_state_lock();
provide_telemetry::backpressure::_reset_backpressure_for_tests();
set_queue_policy(QueuePolicy {
logs_maxsize: 0,
traces_maxsize: 0,
metrics_maxsize: 0,
});
for i in 0..100 {
let ticket = provide_telemetry::try_acquire(Signal::Logs);
assert!(
ticket.is_some(),
"acquire #{i} must succeed on unlimited queue"
);
}
}
#[test]
fn parity_test_backpressure_unlimited_bounded_rejects_second() {
let _guard = acquire_test_state_lock();
provide_telemetry::backpressure::_reset_backpressure_for_tests();
set_queue_policy(QueuePolicy {
logs_maxsize: 1,
traces_maxsize: 0,
metrics_maxsize: 0,
});
let first = provide_telemetry::try_acquire(Signal::Logs);
let second = provide_telemetry::try_acquire(Signal::Logs);
assert!(
first.is_some(),
"first acquire must succeed for bounded queue"
);
assert!(
second.is_none(),
"second acquire without release must be rejected"
);
if let Some(t) = first {
provide_telemetry::release(t);
}
provide_telemetry::backpressure::_reset_backpressure_for_tests();
}
#[test]
fn parity_test_sampling_signal_validation_valid_logs_accepted() {
let result = provide_telemetry::should_sample(Signal::Logs, None);
assert!(result.is_ok(), "Signal::Logs must be accepted");
}
#[test]
fn parity_test_sampling_signal_validation_valid_traces_accepted() {
let result = provide_telemetry::should_sample(Signal::Traces, None);
assert!(result.is_ok(), "Signal::Traces must be accepted");
}
#[test]
fn parity_test_sampling_signal_validation_valid_metrics_accepted() {
let result = provide_telemetry::should_sample(Signal::Metrics, None);
assert!(result.is_ok(), "Signal::Metrics must be accepted");
}
#[test]
fn parity_test_schema_strict_mode_lenient_accepts_uppercase() {
let _guard = acquire_test_state_lock();
provide_telemetry::schema::_reset_schema_for_tests();
set_strict_schema(false);
let result = provide_telemetry::event(&["A", "B", "C"]);
provide_telemetry::schema::_reset_schema_for_tests();
assert!(
result.is_ok(),
"lenient mode must accept uppercase segments"
);
}
#[test]
fn parity_test_schema_strict_mode_lenient_accepts_mixed_case() {
let _guard = acquire_test_state_lock();
provide_telemetry::schema::_reset_schema_for_tests();
set_strict_schema(false);
let result = provide_telemetry::event(&["User", "Login", "Ok"]);
provide_telemetry::schema::_reset_schema_for_tests();
assert!(
result.is_ok(),
"lenient mode must accept mixed-case segments"
);
}
#[test]
fn parity_test_schema_strict_mode_strict_rejects_uppercase() {
let _guard = acquire_test_state_lock();
provide_telemetry::schema::_reset_schema_for_tests();
set_strict_schema(true);
let result = provide_telemetry::event(&["User", "login", "ok"]);
provide_telemetry::schema::_reset_schema_for_tests();
assert!(
result.is_err(),
"strict mode must reject uppercase-starting segments"
);
}
#[test]
fn parity_test_schema_strict_mode_strict_accepts_valid_lowercase() {
let _guard = acquire_test_state_lock();
provide_telemetry::schema::_reset_schema_for_tests();
set_strict_schema(true);
let result = provide_telemetry::event(&["user", "login", "ok"]);
provide_telemetry::schema::_reset_schema_for_tests();
assert!(
result.is_ok(),
"strict mode must accept valid lowercase segments"
);
}
#[test]
fn parity_test_pii_depth_within_max_depth_is_redacted() {
let payload = json!({"outer": {"password": "secret"}}); let result = sanitize_payload(&payload, true, 3);
assert_eq!(
result["outer"]["password"],
"***", "sensitive key within max_depth must be redacted"
);
}
#[test]
fn parity_test_pii_depth_at_max_depth_is_untouched() {
let inner = json!({"password": "secret"}); let payload = json!({"a": {"b": inner}});
let result = sanitize_payload(&payload, true, 2);
assert_eq!(
result["a"]["b"]["password"],
"secret", "sensitive key at/beyond max_depth must NOT be redacted"
);
}
#[test]
fn parity_test_pii_depth_default_is_eight() {
let payload = json!({
"l1": {
"l2": {
"l3": {
"l4": {
"l5": {
"l6": {
"l7": { "password": "s" } }
}
}
}
}
}
});
let result = sanitize_payload(&payload, true, 8);
assert_eq!(
result["l1"]["l2"]["l3"]["l4"]["l5"]["l6"]["l7"]["password"],
"***", "key at depth 7 (within default max_depth=8) must be redacted"
);
}
#[test]
fn parity_test_log_output_format_json_contains_parity_marker() {
let _guard = acquire_test_state_lock();
configure_logging(LoggingConfig {
fmt: "json".to_string(),
include_timestamp: false,
..LoggingConfig::default()
});
enable_json_capture_for_tests();
let log = get_logger(Some("probe"));
log.info("log.output.parity");
let raw = take_json_capture();
reset_logging_config_for_tests();
let text = String::from_utf8_lossy(&raw);
assert!(
text.contains("log.output.parity"),
"JSON output must contain the log.output.parity marker, got: {text}"
);
let parsed: serde_json::Value =
serde_json::from_str(text.lines().next().unwrap_or("{}")).expect("valid JSON");
assert!(
parsed.get("message").is_some() || parsed.get("msg").is_some(),
"JSON log line must contain 'message' or 'msg' field"
);
assert!(
parsed.get("level").is_some(),
"JSON log line must contain 'level' field"
);
}