use serde_json::json;
use std::sync::{Mutex, OnceLock};
use provide_telemetry::{
classify_error, clear_cardinality_limits, compute_error_fingerprint, event,
extract_w3c_context, get_cardinality_limits, get_health_snapshot, get_queue_policy,
get_sampling_policy, get_secret_patterns, record_red_metrics, record_use_metrics,
register_cardinality_limit, register_secret_pattern, reset_secret_patterns_for_tests,
sanitize_payload, set_queue_policy, set_sampling_policy, validate_required_keys,
CardinalityLimit, EventSchemaError, PIIMode, PIIRule, QueuePolicy, SamplingPolicy, Signal,
};
use rstest::rstest;
static PARITY_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
fn parity_lock() -> &'static Mutex<()> {
PARITY_LOCK.get_or_init(|| Mutex::new(()))
}
#[test]
fn parity_test_event_dars_matches_fixture() {
let evt = event(&["db", "query", "users", "ok"]).expect("event should build");
assert_eq!(evt.event, "db.query.users.ok");
assert_eq!(evt.domain, "db");
assert_eq!(evt.action, "query");
assert_eq!(evt.resource.as_deref(), Some("users"));
assert_eq!(evt.status, "ok");
}
#[test]
fn parity_test_event_rejects_invalid_segment_count() {
let err = event(&["too", "few"]).expect_err("invalid event should fail");
assert_eq!(
err,
EventSchemaError::new("event() requires 3 or 4 segments (DA[R]S), got 2")
);
}
#[test]
fn parity_test_required_keys_matches_fixture() {
let mut payload = std::collections::BTreeMap::new();
payload.insert(
"domain".to_string(),
serde_json::Value::String("auth".to_string()),
);
let err = validate_required_keys(&payload, &["domain".to_string(), "action".to_string()])
.expect_err("missing required key should fail");
assert_eq!(err.message, "missing required keys: action");
payload.insert(
"action".to_string(),
serde_json::Value::String("login".to_string()),
);
validate_required_keys(&payload, &["domain".to_string(), "action".to_string()])
.expect("required keys should pass");
}
#[test]
fn parity_test_secret_detection_matches_fixture() {
let payload = json!({"data": "AKIAIOSFODNN7EXAMPLE"}); let sanitized = sanitize_payload(&payload, true, 32);
assert_eq!(sanitized["data"], "***");
}
#[test]
fn parity_test_normal_string_unchanged() {
let payload = json!({"data": "not-a-secret"});
let sanitized = sanitize_payload(&payload, true, 32);
assert_eq!(sanitized["data"], "not-a-secret");
}
#[test]
fn parity_test_cardinality_clamping_zero_max_values() {
let _guard = parity_lock().lock().expect("parity lock poisoned");
clear_cardinality_limits();
register_cardinality_limit(
"k",
CardinalityLimit {
max_values: 0,
ttl_seconds: 10.0,
},
);
let limits = get_cardinality_limits();
assert_eq!(limits.get("k").map(|limit| limit.max_values), Some(1));
clear_cardinality_limits();
}
#[test]
fn parity_test_cardinality_clamping_zero_ttl() {
let _guard = parity_lock().lock().expect("parity lock poisoned");
clear_cardinality_limits();
register_cardinality_limit(
"k",
CardinalityLimit {
max_values: 10,
ttl_seconds: 0.0,
},
);
let limits = get_cardinality_limits();
assert_eq!(limits.get("k").map(|limit| limit.ttl_seconds), Some(1.0));
clear_cardinality_limits();
}
#[test]
fn parity_test_pii_hash_matches_fixture() {
let _guard = parity_lock().lock().expect("parity lock");
let payload = json!({"password": "secret"}); provide_telemetry::replace_pii_rules(vec![PIIRule {
path: vec!["password".to_string()],
mode: PIIMode::Hash,
truncate_to: 0,
}]);
let sanitized = sanitize_payload(&payload, true, 32);
assert_eq!(sanitized["password"], "2bb80d537b1d");
provide_telemetry::replace_pii_rules(Vec::new());
}
#[test]
fn parity_test_propagation_guards_limits_match_fixture() {
let traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
let tracestate_32 = format!("{}z=q", "a=b,".repeat(31));
let accepted = extract_w3c_context(Some(traceparent), Some(&tracestate_32), None);
assert_eq!(accepted.traceparent.as_deref(), Some(traceparent));
assert!(accepted.tracestate.is_some());
let discarded =
extract_w3c_context(Some(&"x".repeat(513)), Some("k=v"), Some(&"b".repeat(8193)));
assert!(discarded.traceparent.is_none());
assert_eq!(discarded.tracestate.as_deref(), Some("k=v"));
assert!(discarded.baggage.is_none());
}
#[rstest]
#[case(404, "client_error")]
#[case(503, "server_error")]
#[case(200, "unclassified")]
#[case(0, "timeout")]
fn parity_test_slo_classify_classification_matches_fixture(
#[case] status_code: u16,
#[case] expected: &str,
) {
let result = classify_error("TestError", Some(status_code));
assert_eq!(result["error.category"], expected);
}
#[test]
fn parity_test_pii_truncate_mode() {
let _guard = parity_lock().lock().expect("parity lock");
provide_telemetry::replace_pii_rules(vec![PIIRule {
path: vec!["note".to_string()],
mode: PIIMode::Truncate,
truncate_to: 5,
}]);
let payload = json!({"note": "hello world"});
let result = sanitize_payload(&payload, true, 32);
assert_eq!(result["note"], "hello...");
provide_telemetry::replace_pii_rules(Vec::new());
}
#[test]
fn parity_test_pii_redact_mode() {
let _guard = parity_lock().lock().expect("parity lock");
provide_telemetry::replace_pii_rules(vec![PIIRule {
path: vec!["note".to_string()],
mode: PIIMode::Redact,
truncate_to: 0,
}]);
let payload = json!({"note": "sensitive"});
let result = sanitize_payload(&payload, true, 32);
assert_eq!(result["note"], "***");
provide_telemetry::replace_pii_rules(Vec::new());
}
#[test]
fn parity_test_pii_drop_mode() {
let _guard = parity_lock().lock().expect("parity lock");
provide_telemetry::replace_pii_rules(vec![PIIRule {
path: vec!["note".to_string()],
mode: PIIMode::Drop,
truncate_to: 0,
}]);
let payload = json!({"note": "sensitive"});
let result = sanitize_payload(&payload, true, 32);
assert!(result.get("note").is_none());
provide_telemetry::replace_pii_rules(Vec::new());
}
#[test]
fn parity_test_jwt_detection() {
let payload = json!({"auth": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIn0"}); let result = sanitize_payload(&payload, true, 32);
assert_eq!(result["auth"], "***");
}
#[test]
fn parity_test_fingerprint_is_deterministic() {
let a = compute_error_fingerprint("ValueError", None);
let b = compute_error_fingerprint("ValueError", None);
assert_eq!(a, b, "same input must produce same fingerprint");
}
#[test]
fn parity_test_fingerprint_differs_by_error_type() {
let a = compute_error_fingerprint("ValueError", None);
let b = compute_error_fingerprint("TypeError", None);
assert_ne!(
a, b,
"different error names must produce different fingerprints"
);
}
#[test]
fn parity_test_backpressure_queue_policy_roundtrip() {
let policy = QueuePolicy {
logs_maxsize: 42,
traces_maxsize: 10,
metrics_maxsize: 10,
};
set_queue_policy(policy);
let got = get_queue_policy();
assert_eq!(got.logs_maxsize, 42);
}
#[test]
fn parity_test_sampling_policy_roundtrip() {
let policy = SamplingPolicy {
default_rate: 0.5,
overrides: Default::default(),
};
set_sampling_policy(Signal::Logs, policy).expect("set ok");
let got = get_sampling_policy(Signal::Logs).expect("get ok");
assert!((got.default_rate - 0.5).abs() < 1e-9);
}
#[test]
fn parity_test_health_snapshot_is_available() {
let snap = get_health_snapshot();
let _ = snap;
}
#[test]
fn parity_test_record_red_metrics_200_no_error_counter() {
record_red_metrics("/health", "GET", 200, 5.0);
}
#[test]
fn parity_test_record_red_metrics_500_increments_error() {
record_red_metrics("/api/v1/events", "POST", 500, 45.0);
}
#[test]
fn parity_test_record_red_metrics_ws_no_error_counter() {
record_red_metrics("/ws", "WS", 503, 0.0);
}
#[test]
fn parity_test_record_use_metrics_does_not_panic() {
record_use_metrics("cpu", 75);
record_use_metrics("memory", 90);
}
#[test]
fn parity_test_register_secret_pattern_custom_detection() {
let _guard = parity_lock().lock().expect("parity lock");
reset_secret_patterns_for_tests();
let pattern = regex::Regex::new(r"MYTOKEN-[A-Z0-9]{12,}").expect("valid regex");
register_secret_pattern("mytoken", pattern);
let payload = json!({"key": "MYTOKEN-ABCD12345678"}); let result = sanitize_payload(&payload, true, 32);
assert_eq!(result["key"], "***");
reset_secret_patterns_for_tests();
}
#[test]
fn parity_test_get_secret_patterns_includes_builtins() {
let patterns = get_secret_patterns();
assert!(!patterns.is_empty(), "must have at least built-in patterns");
let known_builtins = ["aws_key", "jwt", "github_token", "long_hex", "long_base64"];
assert!(
patterns
.iter()
.any(|p| known_builtins.contains(&p.name.as_str())),
"expected at least one generated built-in pattern name, got: {:?}",
patterns.iter().map(|p| &p.name).collect::<Vec<_>>()
);
}
#[test]
fn parity_test_register_secret_pattern_deduplication() {
let _guard = parity_lock().lock().expect("parity lock");
reset_secret_patterns_for_tests();
register_secret_pattern("othertoken", regex::Regex::new(r"OTHER").expect("valid"));
register_secret_pattern("mytoken", regex::Regex::new(r"TOK1").expect("valid"));
register_secret_pattern("mytoken", regex::Regex::new(r"TOK2").expect("valid")); let patterns = get_secret_patterns();
let custom: Vec<_> = patterns.iter().filter(|p| p.name == "mytoken").collect();
assert_eq!(custom.len(), 1, "duplicate name must replace, not append");
reset_secret_patterns_for_tests();
}
#[test]
fn parity_test_sampling_rate_bounds_negative_clamps_to_zero() {
let _guard = parity_lock().lock().expect("parity lock");
let stored = set_sampling_policy(
Signal::Logs,
SamplingPolicy {
default_rate: -0.5,
overrides: Default::default(),
},
)
.expect("out-of-range rates must clamp, not error");
assert_eq!(stored.default_rate, 0.0);
let fetched = get_sampling_policy(Signal::Logs).expect("get ok");
assert_eq!(fetched.default_rate, 0.0);
set_sampling_policy(Signal::Logs, SamplingPolicy::default()).expect("reset ok");
}
#[test]
fn parity_test_sampling_rate_bounds_above_one_clamps_to_one() {
let _guard = parity_lock().lock().expect("parity lock");
let stored = set_sampling_policy(
Signal::Logs,
SamplingPolicy {
default_rate: 1.5,
overrides: Default::default(),
},
)
.expect("out-of-range rates must clamp, not error");
assert_eq!(stored.default_rate, 1.0);
let fetched = get_sampling_policy(Signal::Logs).expect("get ok");
assert_eq!(fetched.default_rate, 1.0);
set_sampling_policy(Signal::Logs, SamplingPolicy::default()).expect("reset ok");
}
#[test]
fn parity_test_propagation_oversized_traceparent_rejected() {
let tp = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01-extra";
let ctx = extract_w3c_context(Some(tp), None, None);
assert!(ctx.trace_id.is_none(), "trace_id must be None");
assert!(ctx.span_id.is_none(), "span_id must be None");
assert!(
ctx.traceparent.is_none(),
"Rust nulls out traceparent when IDs fail to parse (language-local invariant)"
);
}
#[test]
fn parity_test_propagation_tracestate_grammar_matches_fixture() {
let tp = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
let key_256 = format!("{}=x", "a".repeat(256));
let key_257 = format!("{}=x", "a".repeat(257));
let cases: Vec<(&str, bool)> = vec![
("vendor=value\r\nx-injected: yes", false),
("vendor=va\x1b[31mlue", false),
("vendorvalue", false),
("vendor=ok,bad\r\nmember=x", false),
("Vendor=x", false),
(",", false),
(&key_257, false),
("vendor=a=b", false),
("k_e-y*a/b@c=1", true),
("0key=x", true),
("congo=t61rcWkgMzE,rojo=00f067aa0ba902b7", true),
("congo=t61, rojo=00f", true),
("congo=t61,\trojo=00f", true),
("az3@rojo=00f067aa", true),
("vendor=", true),
(&key_256, true),
];
for (tracestate, kept) in cases {
let ctx = extract_w3c_context(Some(tp), Some(tracestate), None);
if kept {
assert_eq!(
ctx.tracestate.as_deref(),
Some(tracestate),
"expected kept: {tracestate:?}"
);
} else {
assert!(
ctx.tracestate.is_none(),
"expected discarded: {tracestate:?}"
);
}
}
}
#[test]
fn parity_test_cardinality_saturation_fourth_value_overflows() {
use provide_telemetry::cardinality::OVERFLOW_VALUE;
use provide_telemetry::guard_attributes;
use std::collections::HashMap;
let _guard = parity_lock().lock().expect("parity lock");
clear_cardinality_limits();
register_cardinality_limit(
"route",
CardinalityLimit {
max_values: 3,
ttl_seconds: 300.0,
},
);
let values = ["/a", "/b", "/c", "/d"];
let mut observed: Vec<String> = Vec::with_capacity(values.len());
for v in values {
let mut attrs = HashMap::new();
attrs.insert("route".to_string(), v.to_string());
let out = guard_attributes(attrs);
observed.push(out.get("route").cloned().expect("route attr present"));
}
assert_eq!(
observed,
vec![
"/a".to_string(),
"/b".to_string(),
"/c".to_string(),
OVERFLOW_VALUE.to_string(),
]
);
assert_eq!(OVERFLOW_VALUE, "__overflow__");
clear_cardinality_limits();
}