use std::sync::{Mutex, OnceLock};
use provide_telemetry::testing::acquire_test_state_lock;
use provide_telemetry::{
classify_error, get_error_count_for_tests, get_request_count_for_tests, record_red_metrics,
record_use_metrics, reset_slo_for_tests, slo_initialized_for_tests,
};
static SLO_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
fn slo_lock() -> &'static Mutex<()> {
SLO_LOCK.get_or_init(|| Mutex::new(()))
}
fn category(error_name: &str, code: u16) -> String {
classify_error(error_name, Some(code))["error.category"].clone()
}
fn severity(error_name: &str, code: u16) -> String {
classify_error(error_name, Some(code))["error.severity"].clone()
}
#[test]
fn slo_classify_error_zero_is_timeout() {
assert_eq!(category("SomeError", 0), "timeout");
}
#[test]
fn slo_classify_error_400_is_client_error() {
assert_eq!(category("BadReq", 400), "client_error");
}
#[test]
fn slo_classify_error_499_is_client_error() {
assert_eq!(category("BadReq", 499), "client_error");
}
#[test]
fn slo_classify_error_500_is_server_error() {
assert_eq!(category("InternalError", 500), "server_error");
}
#[test]
fn slo_classify_error_599_is_server_error() {
assert_eq!(category("InternalError", 599), "server_error");
}
#[test]
fn slo_classify_error_399_is_unclassified() {
assert_eq!(category("Unknown", 399), "unclassified");
}
#[test]
fn slo_classify_error_600_is_unclassified() {
assert_eq!(category("Unknown", 600), "unclassified");
}
#[test]
fn slo_classify_error_200_is_unclassified() {
assert_eq!(category("Success", 200), "unclassified");
}
#[test]
fn slo_classify_error_timeout_in_name_is_timeout() {
assert_eq!(category("ConnectionTimeout", 200), "timeout");
}
#[test]
fn slo_classify_error_408_is_timeout() {
assert_eq!(category("SomeError", 408), "timeout");
}
#[test]
fn slo_classify_error_504_is_timeout() {
assert_eq!(category("SomeError", 504), "timeout");
}
#[test]
fn slo_classify_error_429_has_critical_severity() {
assert_eq!(severity("RateLimit", 429), "critical");
}
#[test]
fn slo_classify_error_404_has_warning_severity() {
assert_eq!(severity("NotFound", 404), "warning");
}
#[test]
fn slo_classify_error_returns_all_spec_keys() {
let result = classify_error("TestError", Some(503));
assert!(result.contains_key("error_type"), "short key");
assert!(result.contains_key("error_code"), "short key");
assert!(result.contains_key("error_name"), "short key");
assert!(result.contains_key("error.type"), "spec key");
assert!(result.contains_key("error.category"), "spec key");
assert!(result.contains_key("error.severity"), "spec key");
assert!(result.contains_key("http.status_code"), "spec key");
assert_eq!(result["error.type"], "TestError");
assert_eq!(result["http.status_code"], "503");
}
#[test]
fn slo_red_metrics_status_500_increments_error_count() {
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
record_red_metrics("/api", "POST", 500, 10.0);
assert_eq!(
get_error_count_for_tests(),
1,
"status 500 must increment error counter (kills >= vs > mutation)"
);
assert_eq!(get_request_count_for_tests(), 1);
}
#[test]
fn slo_red_metrics_status_499_does_not_increment_error_count() {
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
record_red_metrics("/api", "GET", 499, 5.0);
assert_eq!(
get_error_count_for_tests(),
0,
"status 499 must not increment error counter"
);
assert_eq!(get_request_count_for_tests(), 1);
}
#[test]
fn slo_red_metrics_ws_method_503_does_not_increment_error_count() {
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
record_red_metrics("/ws", "WS", 503, 0.0);
assert_eq!(
get_error_count_for_tests(),
0,
"WS method must never increment error counter regardless of status"
);
assert_eq!(get_request_count_for_tests(), 1);
}
#[test]
fn slo_red_metrics_non_ws_method_500_increments_error_count() {
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
record_red_metrics("/api", "GET", 500, 10.0);
assert_eq!(
get_error_count_for_tests(),
1,
"non-WS method with status 500 must increment error counter"
);
}
#[test]
fn slo_red_metrics_ws_method_200_does_not_increment_error_count() {
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
record_red_metrics("/ws", "WS", 200, 1.0);
assert_eq!(get_error_count_for_tests(), 0);
}
#[test]
fn slo_red_metrics_non_ws_499_does_not_increment_error_count() {
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
record_red_metrics("/page", "POST", 499, 2.0);
assert_eq!(
get_error_count_for_tests(),
0,
"non-WS with status 499 must not increment error counter (kills && -> || mutation)"
);
}
#[test]
fn slo_red_metrics_request_count_always_increments() {
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
record_red_metrics("/a", "GET", 200, 1.0);
record_red_metrics("/b", "POST", 500, 2.0);
record_red_metrics("/c", "WS", 503, 0.0);
assert_eq!(
get_request_count_for_tests(),
3,
"every call must increment request counter"
);
assert_eq!(
get_error_count_for_tests(),
1,
"only the POST 500 should count as an error"
);
}
#[test]
fn slo_use_metrics_does_not_panic_for_boundary_values() {
record_use_metrics("cpu", 0);
record_use_metrics("cpu", 100);
record_use_metrics("cpu", i32::MAX);
record_use_metrics("cpu", i32::MIN);
}
#[test]
fn slo_use_metrics_sets_initialized_flag() {
let _guard = acquire_test_state_lock();
let _guard = slo_lock().lock().expect("slo lock");
reset_slo_for_tests();
assert!(!slo_initialized_for_tests());
record_use_metrics("cpu", 50);
assert!(slo_initialized_for_tests());
}