#![allow( unused_variables, clippy::assertions_on_constants, clippy::len_zero )] //! Unit Tests for ML Metrics Module //! //! This test suite validates Prometheus metrics registration and helper functions //! for ML model performance monitoring. use prometheus::core::Collector; use trading_service::ml_metrics::*; #[test] fn test_ml_inference_latency_metric_exists() { // Verify histogram is registered with correct labels let metric = &*ML_INFERENCE_LATENCY_US; let desc = metric.desc(); assert!( desc.len() > 0, "ML inference latency histogram should have descriptors" ); // Test that we can observe values metric.with_label_values(&["DQN"]).observe(100.0); metric.with_label_values(&["PPO"]).observe(250.0); metric.with_label_values(&["TFT"]).observe(500.0); // Verify histogram buckets are defined (should have 7 buckets) let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_model_accuracy_metric_exists() { let metric = &*ML_MODEL_ACCURACY; let desc = metric.desc(); assert!( desc.len() > 0, "ML model accuracy gauge should have descriptors" ); // Test setting accuracy values (0-100%) metric.with_label_values(&["DQN"]).set(87.5); metric.with_label_values(&["PPO"]).set(92.3); metric.with_label_values(&["TFT"]).set(89.1); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_model_health_metric_exists() { let metric = &*ML_MODEL_HEALTH; let desc = metric.desc(); assert!( desc.len() > 0, "ML model health gauge should have descriptors" ); // Test health status values (0=Healthy, 1=Degraded, 2=Unhealthy, 3=Failed, 4=Offline) metric.with_label_values(&["DQN"]).set(0.0); // Healthy metric.with_label_values(&["PPO"]).set(1.0); // Degraded metric.with_label_values(&["TFT"]).set(2.0); // Unhealthy let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_fallback_counter_exists() { let metric = &*ML_FALLBACK_TOTAL; let desc = metric.desc(); assert!( desc.len() > 0, "ML fallback counter should have descriptors" ); // Test fallback events with 3 labels (from_model, to_model, reason) metric .with_label_values(&["DQN", "PPO", "high_latency"]) .inc(); metric .with_label_values(&["PPO", "TFT", "prediction_error"]) .inc(); metric .with_label_values(&["TFT", "DQN", "model_failure"]) .inc(); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_predictions_counter_exists() { let metric = &*ML_PREDICTIONS_TOTAL; let desc = metric.desc(); assert!( desc.len() > 0, "ML predictions counter should have descriptors" ); // Test prediction types (buy/sell/hold) metric.with_label_values(&["DQN", "buy"]).inc(); metric.with_label_values(&["DQN", "sell"]).inc(); metric.with_label_values(&["DQN", "hold"]).inc(); metric.with_label_values(&["PPO", "buy"]).inc_by(5.0); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_prediction_errors_counter_exists() { let metric = &*ML_PREDICTION_ERRORS_TOTAL; let desc = metric.desc(); assert!( desc.len() > 0, "ML prediction errors counter should have descriptors" ); // Test error types metric .with_label_values(&["DQN", "inference_timeout"]) .inc(); metric.with_label_values(&["PPO", "invalid_input"]).inc(); metric.with_label_values(&["TFT", "model_not_loaded"]).inc(); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_alerts_counter_exists() { let metric = &*ML_ALERTS_TOTAL; let desc = metric.desc(); assert!(desc.len() > 0, "ML alerts counter should have descriptors"); // Test alerts with 3 labels (model_id, alert_type, severity) metric .with_label_values(&["DQN", "high_latency", "warning"]) .inc(); metric .with_label_values(&["PPO", "low_accuracy", "critical"]) .inc(); metric .with_label_values(&["TFT", "model_drift", "emergency"]) .inc(); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_model_drift_score_metric_exists() { let metric = &*ML_MODEL_DRIFT_SCORE; let desc = metric.desc(); assert!( desc.len() > 0, "ML model drift score gauge should have descriptors" ); // Test drift scores (percentage change) metric.with_label_values(&["DQN"]).set(2.5); // 2.5% drift metric.with_label_values(&["PPO"]).set(5.1); // 5.1% drift metric.with_label_values(&["TFT"]).set(10.8); // 10.8% drift let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_model_confidence_metric_exists() { let metric = &*ML_MODEL_CONFIDENCE; let desc = metric.desc(); assert!( desc.len() > 0, "ML model confidence gauge should have descriptors" ); // Test confidence scores (0-1) metric.with_label_values(&["DQN"]).set(0.85); metric.with_label_values(&["PPO"]).set(0.92); metric.with_label_values(&["TFT"]).set(0.78); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_model_memory_metric_exists() { let metric = &*ML_MODEL_MEMORY_MB; let desc = metric.desc(); assert!( desc.len() > 0, "ML model memory gauge should have descriptors" ); // Test memory usage in MB metric.with_label_values(&["DQN"]).set(6.0); // 6 MB metric.with_label_values(&["PPO"]).set(145.0); // 145 MB metric.with_label_values(&["TFT"]).set(125.0); // 125 MB metric.with_label_values(&["MAMBA2"]).set(164.0); // 164 MB let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_model_cpu_metric_exists() { let metric = &*ML_MODEL_CPU_PERCENT; let desc = metric.desc(); assert!(desc.len() > 0, "ML model CPU gauge should have descriptors"); // Test CPU utilization (0-100%) metric.with_label_values(&["DQN"]).set(15.5); metric.with_label_values(&["PPO"]).set(28.3); metric.with_label_values(&["TFT"]).set(45.7); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_ml_circuit_breaker_transitions_metric_exists() { let metric = &*ML_CIRCUIT_BREAKER_TRANSITIONS; let desc = metric.desc(); assert!( desc.len() > 0, "ML circuit breaker transitions counter should have descriptors" ); // Test state transitions (from_state, to_state) metric.with_label_values(&["DQN", "closed", "open"]).inc(); metric .with_label_values(&["PPO", "open", "half_open"]) .inc(); metric .with_label_values(&["TFT", "half_open", "closed"]) .inc(); let collected = metric.collect(); assert!(!collected.is_empty(), "Should have collected metrics"); } #[test] fn test_multiple_labels_per_metric() { // Verify we can track multiple models independently ML_MODEL_ACCURACY.with_label_values(&["model_1"]).set(85.0); ML_MODEL_ACCURACY.with_label_values(&["model_2"]).set(90.0); ML_MODEL_ACCURACY.with_label_values(&["model_3"]).set(87.5); ML_INFERENCE_LATENCY_US .with_label_values(&["model_1"]) .observe(100.0); ML_INFERENCE_LATENCY_US .with_label_values(&["model_2"]) .observe(200.0); ML_INFERENCE_LATENCY_US .with_label_values(&["model_3"]) .observe(150.0); // Each model should have independent metrics let accuracy_collected = ML_MODEL_ACCURACY.collect(); let latency_collected = ML_INFERENCE_LATENCY_US.collect(); assert!(!accuracy_collected.is_empty()); assert!(!latency_collected.is_empty()); } #[test] fn test_metric_increments() { // Test that counters can be incremented multiple times let initial_count = ML_PREDICTIONS_TOTAL .with_label_values(&["test_model", "buy"]) .get(); ML_PREDICTIONS_TOTAL .with_label_values(&["test_model", "buy"]) .inc(); ML_PREDICTIONS_TOTAL .with_label_values(&["test_model", "buy"]) .inc(); ML_PREDICTIONS_TOTAL .with_label_values(&["test_model", "buy"]) .inc_by(3.0); // Counter should have increased (we can't easily check exact value due to other tests) let collected = ML_PREDICTIONS_TOTAL.collect(); assert!(!collected.is_empty()); } #[test] fn test_histogram_buckets() { // Verify histogram has correct bucket configuration // Buckets: [10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0] // Test values in different buckets ML_INFERENCE_LATENCY_US .with_label_values(&["bucket_test"]) .observe(5.0); // < 10 ML_INFERENCE_LATENCY_US .with_label_values(&["bucket_test"]) .observe(75.0); // 50-100 ML_INFERENCE_LATENCY_US .with_label_values(&["bucket_test"]) .observe(750.0); // 500-1000 ML_INFERENCE_LATENCY_US .with_label_values(&["bucket_test"]) .observe(5500.0); // 5000-10000 ML_INFERENCE_LATENCY_US .with_label_values(&["bucket_test"]) .observe(15000.0); // > 10000 let collected = ML_INFERENCE_LATENCY_US.collect(); assert!( !collected.is_empty(), "Should have collected histogram metrics" ); } #[test] fn test_gauge_set_operations() { // Test that gauges can be set to arbitrary values ML_MODEL_HEALTH.with_label_values(&["gauge_test"]).set(0.0); ML_MODEL_HEALTH.with_label_values(&["gauge_test"]).set(1.0); ML_MODEL_HEALTH.with_label_values(&["gauge_test"]).set(2.0); ML_MODEL_HEALTH.with_label_values(&["gauge_test"]).set(3.0); ML_MODEL_HEALTH.with_label_values(&["gauge_test"]).set(4.0); // Verify latest value is set (should be 4.0) let collected = ML_MODEL_HEALTH.collect(); assert!(!collected.is_empty()); } #[test] fn test_all_metrics_are_registered() { // Verify all static metrics are successfully registered (no panics during initialization) let _ = &*ML_INFERENCE_LATENCY_US; let _ = &*ML_MODEL_ACCURACY; let _ = &*ML_MODEL_HEALTH; let _ = &*ML_FALLBACK_TOTAL; let _ = &*ML_PREDICTIONS_TOTAL; let _ = &*ML_PREDICTION_ERRORS_TOTAL; let _ = &*ML_ALERTS_TOTAL; let _ = &*ML_MODEL_DRIFT_SCORE; let _ = &*ML_MODEL_CONFIDENCE; let _ = &*ML_MODEL_MEMORY_MB; let _ = &*ML_MODEL_CPU_PERCENT; let _ = &*ML_CIRCUIT_BREAKER_TRANSITIONS; // If we got here without panicking, all metrics are registered successfully assert!(true, "All metrics initialized successfully"); }