//! Inference Engine Tests //! //! Comprehensive testing for ML inference engine covering: //! - Configuration validation //! - Fallback prediction logic //! - Feature bounds checking //! - Signal weighting //! - Prediction bounds enforcement //! - Error handling #![allow(unused_crate_dependencies)] use ml::integration::inference_engine::{ FallbackPredictionConfig, FeatureBounds, FeatureDefaults, InferenceEngineConfig, PredictionBounds, SignalScaling, SignalWeights, }; /// Test: Fallback prediction config - emergency defaults #[test] fn test_fallback_config_emergency_defaults() { let config = FallbackPredictionConfig::emergency_safe_defaults(); // Base prediction should be market neutral assert_eq!(config.base_prediction, 0.5); assert_eq!(config.neutral_prediction, 0.5); // Confidence should be very low for safety assert_eq!(config.default_confidence, 0.1); assert!(config.default_confidence <= 0.2); // Signal weights should be very conservative assert!(config.signal_weights.momentum_weight <= 0.1); assert!(config.signal_weights.volume_weight <= 0.1); assert!(config.signal_weights.spread_weight <= 0.1); assert!(config.signal_weights.volatility_weight <= 0.1); } /// Test: Fallback config - default trait #[test] fn test_fallback_config_default_trait() { let config = FallbackPredictionConfig::default(); // Default should match emergency safe defaults assert_eq!(config.base_prediction, 0.5); assert_eq!(config.neutral_prediction, 0.5); assert_eq!(config.default_confidence, 0.1); } /// Test: Signal weights - all positive #[test] fn test_signal_weights_positive() { let config = FallbackPredictionConfig::default(); let weights = &config.signal_weights; // All weights should be positive assert!(weights.momentum_weight >= 0.0); assert!(weights.volume_weight >= 0.0); assert!(weights.spread_weight >= 0.0); assert!(weights.volatility_weight >= 0.0); // Weights should be reasonable (< 1.0 for safety) assert!(weights.momentum_weight <= 1.0); assert!(weights.volume_weight <= 1.0); assert!(weights.spread_weight <= 1.0); assert!(weights.volatility_weight <= 1.0); } /// Test: Signal scaling - conservative defaults #[test] fn test_signal_scaling_conservative() { let config = FallbackPredictionConfig::default(); let scaling = &config.signal_scaling; // All scaling factors should be small for safety assert!(scaling.momentum_scale <= 0.1); assert!(scaling.volume_scale <= 0.1); assert!(scaling.spread_scale <= 0.1); assert!(scaling.volatility_scale <= 0.1); // All scaling factors should be positive assert!(scaling.momentum_scale > 0.0); assert!(scaling.volume_scale > 0.0); assert!(scaling.spread_scale > 0.0); assert!(scaling.volatility_scale > 0.0); } /// Test: Feature bounds - valid ranges #[test] fn test_feature_bounds_valid_ranges() { let config = FallbackPredictionConfig::default(); let bounds = &config.feature_bounds; // Min should be less than max for all features assert!(bounds.momentum_min < bounds.momentum_max); assert!(bounds.volume_min < bounds.volume_max); assert!(bounds.spread_min < bounds.spread_max); assert!(bounds.volatility_min < bounds.volatility_max); // Bounds should be reasonable assert!(bounds.momentum_min >= -1.0); assert!(bounds.momentum_max <= 1.0); assert!(bounds.volume_min >= 0.0); assert!(bounds.spread_min >= 0.0); assert!(bounds.volatility_min >= 0.0); } /// Test: Feature bounds - momentum bounds #[test] fn test_feature_bounds_momentum() { let config = FallbackPredictionConfig::default(); let bounds = &config.feature_bounds; // Momentum bounds should be symmetric around zero assert_eq!(bounds.momentum_min, -0.1); assert_eq!(bounds.momentum_max, 0.1); assert!(bounds.momentum_min.abs() == bounds.momentum_max); } /// Test: Feature bounds - volume bounds #[test] fn test_feature_bounds_volume() { let config = FallbackPredictionConfig::default(); let bounds = &config.feature_bounds; // Volume should be non-negative assert_eq!(bounds.volume_min, 0.0); assert!(bounds.volume_max > 0.0); assert!(bounds.volume_max <= 10.0); // Reasonable upper limit } /// Test: Feature bounds - spread bounds #[test] fn test_feature_bounds_spread() { let config = FallbackPredictionConfig::default(); let bounds = &config.feature_bounds; // Spread should be non-negative and small assert_eq!(bounds.spread_min, 0.0); assert!(bounds.spread_max > 0.0); assert!(bounds.spread_max <= 1.0); } /// Test: Feature bounds - volatility bounds #[test] fn test_feature_bounds_volatility() { let config = FallbackPredictionConfig::default(); let bounds = &config.feature_bounds; // Volatility should be non-negative assert_eq!(bounds.volatility_min, 0.0); assert!(bounds.volatility_max > 0.0); assert!(bounds.volatility_max <= 2.0); } /// Test: Feature defaults - neutral values #[test] fn test_feature_defaults_neutral() { let config = FallbackPredictionConfig::default(); let defaults = &config.feature_defaults; // Momentum default should be neutral (zero) assert_eq!(defaults.momentum_default, 0.0); // Volume default should be average assert_eq!(defaults.volume_default, 1.0); // Spread default should be small assert!(defaults.spread_default > 0.0); assert!(defaults.spread_default <= 0.1); // Volatility default should be low assert!(defaults.volatility_default > 0.0); assert!(defaults.volatility_default <= 0.5); } /// Test: Feature defaults - within bounds #[test] fn test_feature_defaults_within_bounds() { let config = FallbackPredictionConfig::default(); let defaults = &config.feature_defaults; let bounds = &config.feature_bounds; // All defaults should be within bounds assert!(defaults.momentum_default >= bounds.momentum_min); assert!(defaults.momentum_default <= bounds.momentum_max); assert!(defaults.volume_default >= bounds.volume_min); assert!(defaults.volume_default <= bounds.volume_max); assert!(defaults.spread_default >= bounds.spread_min); assert!(defaults.spread_default <= bounds.spread_max); assert!(defaults.volatility_default >= bounds.volatility_min); assert!(defaults.volatility_default <= bounds.volatility_max); } /// Test: Prediction bounds - around neutral #[test] fn test_prediction_bounds_neutral() { let config = FallbackPredictionConfig::default(); let bounds = &config.prediction_bounds; // Bounds should be very narrow around 0.5 (neutral) assert_eq!(bounds.min, 0.45); assert_eq!(bounds.max, 0.55); // Range should be tight for safety let range = bounds.max - bounds.min; assert_eq!(range, 0.1); // 10% range } /// Test: Prediction bounds - valid range #[test] fn test_prediction_bounds_valid() { let config = FallbackPredictionConfig::default(); let bounds = &config.prediction_bounds; // Min should be less than max assert!(bounds.min < bounds.max); // Bounds should be in [0, 1] assert!(bounds.min >= 0.0); assert!(bounds.max <= 1.0); // Base prediction should be within bounds assert!(config.base_prediction >= bounds.min); assert!(config.base_prediction <= bounds.max); } /// Test: Inference engine config - default values #[test] fn test_inference_engine_config_defaults() { let config = InferenceEngineConfig::default(); // Max concurrent requests should be positive assert!(config.max_concurrent_requests > 0); assert!(config.max_concurrent_requests <= 1000); // Reasonable limit // Default timeout should be positive assert!(config.default_timeout_us > 0); assert!(config.default_timeout_us <= 1_000_000); // At most 1 second // Max batch size should be positive and reasonable assert!(config.max_batch_size > 0); assert!(config.max_batch_size <= 1000); } /// Test: Inference engine config - ONNX flag #[test] fn test_inference_engine_config_onnx() { let config = InferenceEngineConfig::default(); // ONNX can be enabled or disabled // Just verify the field exists and is boolean let _onnx_enabled = config.enable_onnx; } /// Test: Signal weights - custom values #[test] fn test_signal_weights_custom() { let weights = SignalWeights { momentum_weight: 0.4, volume_weight: 0.3, spread_weight: 0.2, volatility_weight: 0.1, }; assert_eq!(weights.momentum_weight, 0.4); assert_eq!(weights.volume_weight, 0.3); assert_eq!(weights.spread_weight, 0.2); assert_eq!(weights.volatility_weight, 0.1); // Weights sum to 1.0 (typical normalization) let sum = weights.momentum_weight + weights.volume_weight + weights.spread_weight + weights.volatility_weight; assert!((sum - 1.0).abs() < 0.001); } /// Test: Signal scaling - custom values #[test] fn test_signal_scaling_custom() { let scaling = SignalScaling { momentum_scale: 0.05, volume_scale: 0.03, spread_scale: 0.02, volatility_scale: 0.01, }; assert_eq!(scaling.momentum_scale, 0.05); assert_eq!(scaling.volume_scale, 0.03); assert_eq!(scaling.spread_scale, 0.02); assert_eq!(scaling.volatility_scale, 0.01); } /// Test: Feature bounds - custom ranges #[test] fn test_feature_bounds_custom() { let bounds = FeatureBounds { momentum_min: -0.5, momentum_max: 0.5, volume_min: 0.0, volume_max: 5.0, spread_min: 0.0, spread_max: 0.2, volatility_min: 0.0, volatility_max: 1.0, }; // Verify custom values assert_eq!(bounds.momentum_min, -0.5); assert_eq!(bounds.momentum_max, 0.5); assert_eq!(bounds.volume_max, 5.0); assert_eq!(bounds.spread_max, 0.2); assert_eq!(bounds.volatility_max, 1.0); // Verify invariants still hold assert!(bounds.momentum_min < bounds.momentum_max); assert!(bounds.volume_min < bounds.volume_max); } /// Test: Feature defaults - custom values #[test] fn test_feature_defaults_custom() { let defaults = FeatureDefaults { momentum_default: 0.05, volume_default: 1.5, spread_default: 0.02, volatility_default: 0.2, }; assert_eq!(defaults.momentum_default, 0.05); assert_eq!(defaults.volume_default, 1.5); assert_eq!(defaults.spread_default, 0.02); assert_eq!(defaults.volatility_default, 0.2); } /// Test: Prediction bounds - custom range #[test] fn test_prediction_bounds_custom() { let bounds = PredictionBounds { min: 0.3, max: 0.7 }; assert_eq!(bounds.min, 0.3); assert_eq!(bounds.max, 0.7); assert!(bounds.min < bounds.max); let range = bounds.max - bounds.min; assert_eq!(range, 0.4); } /// Test: Prediction bounds - edge cases #[test] fn test_prediction_bounds_edge_cases() { // Very narrow range let narrow = PredictionBounds { min: 0.49, max: 0.51, }; assert!(narrow.max - narrow.min == 0.02); // Wide range let wide = PredictionBounds { min: 0.0, max: 1.0 }; assert!(wide.max - wide.min == 1.0); } /// Test: Fallback config - serialization #[test] fn test_fallback_config_serialization() { let config = FallbackPredictionConfig::default(); // Serialize to JSON let json = serde_json::to_string(&config).expect("Should serialize"); // Deserialize back let deserialized: FallbackPredictionConfig = serde_json::from_str(&json).expect("Should deserialize"); // Verify key fields match assert_eq!(config.base_prediction, deserialized.base_prediction); assert_eq!(config.neutral_prediction, deserialized.neutral_prediction); assert_eq!(config.default_confidence, deserialized.default_confidence); } /// Test: Inference engine config - serialization #[test] fn test_inference_config_serialization() { let config = InferenceEngineConfig::default(); // Serialize to JSON let json = serde_json::to_string(&config).expect("Should serialize"); // Deserialize back let deserialized: InferenceEngineConfig = serde_json::from_str(&json).expect("Should deserialize"); // Verify key fields match assert_eq!( config.max_concurrent_requests, deserialized.max_concurrent_requests ); assert_eq!(config.default_timeout_us, deserialized.default_timeout_us); assert_eq!(config.max_batch_size, deserialized.max_batch_size); } /// Test: Fallback config - clone trait #[test] fn test_fallback_config_clone() { let config1 = FallbackPredictionConfig::default(); let config2 = config1.clone(); assert_eq!(config1.base_prediction, config2.base_prediction); assert_eq!(config1.neutral_prediction, config2.neutral_prediction); assert_eq!(config1.default_confidence, config2.default_confidence); } /// Test: Inference engine config - clone trait #[test] fn test_inference_config_clone() { let config1 = InferenceEngineConfig::default(); let config2 = config1.clone(); assert_eq!( config1.max_concurrent_requests, config2.max_concurrent_requests ); assert_eq!(config1.default_timeout_us, config2.default_timeout_us); assert_eq!(config1.max_batch_size, config2.max_batch_size); }