Skip to main content

mockforge_data/
drift_learning.rs

1//! Drift Learning System
2//!
3//! This module extends the DataDriftEngine with learning capabilities that allow
4//! mocks to gradually learn from recorded traffic and adapt their behavior.
5//!
6//! Features:
7//! - Traffic pattern learning from recorded requests
8//! - Persona behavior adaptation based on request patterns
9//! - Configurable learning rate and sensitivity
10//! - Opt-in per persona/endpoint learning
11
12use crate::drift::{DataDriftConfig, DataDriftEngine};
13use crate::Result;
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::collections::HashMap;
17use std::sync::Arc;
18use std::time::Duration;
19use tokio::sync::RwLock;
20
21/// Learning configuration
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct LearningConfig {
24    /// Enable drift learning
25    #[serde(default)]
26    pub enabled: bool,
27
28    /// Learning mode
29    #[serde(default)]
30    pub mode: LearningMode,
31
32    /// Learning rate (0.0 to 1.0) - how quickly mocks learn from patterns
33    #[serde(default = "default_learning_rate")]
34    pub sensitivity: f64,
35
36    /// Decay rate (0.0 to 1.0) - drift resets if upstream patterns reverse
37    #[serde(default = "default_decay_rate")]
38    pub decay: f64,
39
40    /// Minimum number of samples before learning starts
41    #[serde(default = "default_min_samples")]
42    pub min_samples: usize,
43
44    /// Update interval for learning
45    #[serde(default = "default_update_interval")]
46    pub update_interval: Duration,
47
48    /// Enable persona adaptation
49    #[serde(default = "default_true")]
50    pub persona_adaptation: bool,
51
52    /// Enable traffic pattern mirroring
53    #[serde(default = "default_true")]
54    pub traffic_mirroring: bool,
55
56    /// Per-endpoint opt-in learning (endpoint pattern -> enabled)
57    #[serde(default)]
58    pub endpoint_learning: HashMap<String, bool>,
59
60    /// Per-persona opt-in learning (persona_id -> enabled)
61    #[serde(default)]
62    pub persona_learning: HashMap<String, bool>,
63}
64
65fn default_learning_rate() -> f64 {
66    0.2 // 20% learning rate - conservative default
67}
68
69fn default_decay_rate() -> f64 {
70    0.05 // 5% decay rate
71}
72
73fn default_min_samples() -> usize {
74    10 // Need at least 10 samples before learning
75}
76
77fn default_update_interval() -> Duration {
78    Duration::from_secs(60) // Update every minute
79}
80
81fn default_true() -> bool {
82    true
83}
84
85impl Default for LearningConfig {
86    fn default() -> Self {
87        Self {
88            enabled: false, // Opt-in by default
89            mode: LearningMode::Behavioral,
90            sensitivity: default_learning_rate(),
91            decay: default_decay_rate(),
92            min_samples: default_min_samples(),
93            update_interval: default_update_interval(),
94            persona_adaptation: true,
95            traffic_mirroring: true,
96            endpoint_learning: HashMap::new(),
97            persona_learning: HashMap::new(),
98        }
99    }
100}
101
102/// Learning mode
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
104#[serde(rename_all = "snake_case")]
105pub enum LearningMode {
106    /// Behavioral learning - adapts to behavior patterns
107    #[default]
108    Behavioral,
109    /// Statistical learning - adapts to statistical patterns
110    Statistical,
111    /// Hybrid - combines behavioral and statistical
112    Hybrid,
113}
114
115/// Drift Learning Engine
116///
117/// Extends DataDriftEngine with learning capabilities from recorded traffic.
118pub struct DriftLearningEngine {
119    /// Base drift engine
120    drift_engine: DataDriftEngine,
121    /// Learning configuration
122    learning_config: LearningConfig,
123    /// Traffic pattern learner (constructed for config validation; wiring pending)
124    _traffic_learner: Option<Arc<RwLock<TrafficPatternLearner>>>,
125    /// Persona behavior learner (constructed for config validation; wiring pending)
126    _persona_learner: Option<Arc<RwLock<PersonaBehaviorLearner>>>,
127    /// Learned patterns cache
128    learned_patterns: Arc<RwLock<HashMap<String, LearnedPattern>>>,
129}
130
131/// Learned pattern from traffic analysis
132#[derive(Debug, Clone)]
133pub struct LearnedPattern {
134    /// Pattern identifier
135    pub pattern_id: String,
136    /// Pattern type
137    pub pattern_type: PatternType,
138    /// Learned parameters
139    pub parameters: HashMap<String, Value>,
140    /// Confidence score (0.0 to 1.0)
141    pub confidence: f64,
142    /// Sample count used for learning
143    pub sample_count: usize,
144    /// Last updated timestamp
145    pub last_updated: chrono::DateTime<chrono::Utc>,
146}
147
148/// Pattern type
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub enum PatternType {
151    /// Latency pattern
152    Latency,
153    /// Error rate pattern
154    ErrorRate,
155    /// Request sequence pattern
156    RequestSequence,
157    /// Persona behavior pattern
158    PersonaBehavior,
159}
160
161impl DriftLearningEngine {
162    /// Create a new drift learning engine
163    pub fn new(drift_config: DataDriftConfig, learning_config: LearningConfig) -> Result<Self> {
164        let drift_engine = DataDriftEngine::new(drift_config)?;
165
166        let traffic_learner = if learning_config.traffic_mirroring {
167            Some(Arc::new(RwLock::new(TrafficPatternLearner::new(learning_config.clone())?)))
168        } else {
169            None
170        };
171
172        let persona_learner = if learning_config.persona_adaptation {
173            Some(Arc::new(RwLock::new(PersonaBehaviorLearner::new(learning_config.clone())?)))
174        } else {
175            None
176        };
177
178        Ok(Self {
179            drift_engine,
180            learning_config,
181            _traffic_learner: traffic_learner,
182            _persona_learner: persona_learner,
183            learned_patterns: Arc::new(RwLock::new(HashMap::new())),
184        })
185    }
186
187    /// Get the base drift engine
188    pub fn drift_engine(&self) -> &DataDriftEngine {
189        &self.drift_engine
190    }
191
192    /// Get learning configuration
193    pub fn learning_config(&self) -> &LearningConfig {
194        &self.learning_config
195    }
196
197    /// Update learning configuration
198    pub fn update_learning_config(&mut self, config: LearningConfig) -> Result<()> {
199        self.learning_config = config;
200        Ok(())
201    }
202
203    /// Get learned patterns
204    pub async fn get_learned_patterns(&self) -> HashMap<String, LearnedPattern> {
205        self.learned_patterns.read().await.clone()
206    }
207
208    /// Apply drift with learning
209    pub async fn apply_drift_with_learning(&self, data: Value) -> Result<Value> {
210        // First apply base drift
211        let mut data = self.drift_engine.apply_drift(data).await?;
212
213        // Then apply learned patterns if learning is enabled
214        if !self.learning_config.enabled {
215            return Ok(data);
216        }
217
218        // Apply learned patterns
219        let patterns = self.learned_patterns.read().await;
220        for (_pattern_id, pattern) in patterns.iter() {
221            // Check if pattern should be applied based on confidence and decay
222            if pattern.confidence < 0.5 {
223                continue; // Low confidence, skip
224            }
225
226            // Apply pattern based on type
227            match pattern.pattern_type {
228                PatternType::Latency => {
229                    // Latency patterns are handled separately
230                }
231                PatternType::ErrorRate => {
232                    // Error rate patterns are handled separately
233                }
234                PatternType::RequestSequence => {
235                    // Request sequence patterns affect persona behavior
236                }
237                PatternType::PersonaBehavior => {
238                    // Persona behavior patterns affect data generation
239                    if let Some(obj) = data.as_object_mut() {
240                        for (key, value) in &pattern.parameters {
241                            if let Some(existing) = obj.get(key) {
242                                // Blend learned value with existing value
243                                let blended =
244                                    self.blend_values(existing, value, pattern.confidence)?;
245                                obj.insert(key.clone(), blended);
246                            }
247                        }
248                    }
249                }
250            }
251        }
252
253        Ok(data)
254    }
255
256    /// Blend two values based on confidence
257    fn blend_values(&self, existing: &Value, learned: &Value, confidence: f64) -> Result<Value> {
258        // Simple blending: existing * (1 - confidence * sensitivity) + learned * (confidence * sensitivity)
259        let weight = confidence * self.learning_config.sensitivity;
260
261        match (existing, learned) {
262            (Value::Number(existing_num), Value::Number(learned_num)) => {
263                if let (Some(existing_f64), Some(learned_f64)) =
264                    (existing_num.as_f64(), learned_num.as_f64())
265                {
266                    let blended = existing_f64 * (1.0 - weight) + learned_f64 * weight;
267                    Ok(Value::from(blended))
268                } else {
269                    Ok(existing.clone())
270                }
271            }
272            _ => Ok(existing.clone()), // For non-numeric, keep existing
273        }
274    }
275}
276
277/// Traffic Pattern Learner
278///
279/// Analyzes recorded traffic to detect patterns and trends.
280pub struct TrafficPatternLearner {
281    /// Learning configuration (reserved for future per-learner config checks)
282    _config: LearningConfig,
283}
284
285impl TrafficPatternLearner {
286    /// Create a new traffic pattern learner
287    pub fn new(config: LearningConfig) -> Result<Self> {
288        Ok(Self { _config: config })
289    }
290
291    /// Analyze traffic patterns from recorded requests.
292    ///
293    /// Accepts JSON values with fields: `method`, `path`, `duration_ms`, `status_code`, `trace_id`.
294    /// Returns detected latency, error rate, and sequence patterns.
295    pub async fn analyze_traffic_patterns(
296        &mut self,
297        requests: &[Value],
298    ) -> Result<Vec<LearnedPattern>> {
299        let mut patterns = Vec::new();
300        patterns.extend(self.detect_latency_patterns_from_requests(requests).await?);
301        patterns.extend(self.detect_error_patterns_from_requests(requests).await?);
302        patterns.extend(self.detect_sequence_patterns_from_requests(requests).await?);
303        Ok(patterns)
304    }
305
306    /// Detect latency patterns from recorded request data.
307    ///
308    /// Each request value should contain `method` (string), `path` (string),
309    /// and `duration_ms` (integer) fields.
310    pub async fn detect_latency_patterns_from_requests(
311        &self,
312        requests: &[Value],
313    ) -> Result<Vec<LearnedPattern>> {
314        use chrono::Utc;
315
316        let mut endpoint_latencies: HashMap<String, Vec<i64>> = HashMap::new();
317
318        for request in requests {
319            let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("UNKNOWN");
320            let path = request.get("path").and_then(|v| v.as_str()).unwrap_or("/");
321            let duration = request.get("duration_ms").and_then(|v| v.as_i64());
322
323            if let Some(duration_ms) = duration {
324                let key = format!("{} {}", method, path);
325                endpoint_latencies.entry(key).or_default().push(duration_ms);
326            }
327        }
328
329        let mut patterns = Vec::new();
330
331        for (endpoint_key, latencies) in endpoint_latencies {
332            if latencies.len() < 10 {
333                continue;
334            }
335
336            let sum: i64 = latencies.iter().sum();
337            let count = latencies.len();
338            let avg_latency = sum as f64 / count as f64;
339
340            let mut sorted = latencies.clone();
341            sorted.sort();
342            let p50 = sorted[count / 2];
343            let p95 = sorted[(count * 95) / 100];
344            let p99 = sorted[(count * 99) / 100];
345
346            let recent_avg = if latencies.len() >= 20 {
347                let recent: Vec<i64> = latencies.iter().rev().take(10).copied().collect();
348                let recent_sum: i64 = recent.iter().sum();
349                recent_sum as f64 / recent.len() as f64
350            } else {
351                avg_latency
352            };
353
354            let latency_trend = if recent_avg > avg_latency * 1.2 {
355                "increasing"
356            } else if recent_avg < avg_latency * 0.8 {
357                "decreasing"
358            } else {
359                "stable"
360            };
361
362            if p99 > p50 * 2 || latency_trend != "stable" {
363                let mut parameters = HashMap::new();
364                parameters.insert("endpoint".to_string(), serde_json::json!(endpoint_key));
365                parameters.insert("avg_latency_ms".to_string(), serde_json::json!(avg_latency));
366                parameters.insert("p50_ms".to_string(), serde_json::json!(p50));
367                parameters.insert("p95_ms".to_string(), serde_json::json!(p95));
368                parameters.insert("p99_ms".to_string(), serde_json::json!(p99));
369                parameters.insert("sample_count".to_string(), serde_json::json!(count));
370                parameters.insert("trend".to_string(), serde_json::json!(latency_trend));
371
372                let confidence = (count as f64 / 100.0).min(1.0);
373
374                patterns.push(LearnedPattern {
375                    pattern_id: format!("latency_{}", endpoint_key.replace(['/', ' '], "_")),
376                    pattern_type: PatternType::Latency,
377                    parameters,
378                    confidence,
379                    sample_count: count,
380                    last_updated: Utc::now(),
381                });
382            }
383        }
384
385        Ok(patterns)
386    }
387
388    /// Detect error rate patterns from recorded request data.
389    ///
390    /// Each request value should contain `method` (string), `path` (string),
391    /// and `status_code` (integer) fields.
392    async fn detect_error_patterns_from_requests(
393        &self,
394        requests: &[Value],
395    ) -> Result<Vec<LearnedPattern>> {
396        use chrono::Utc;
397
398        let mut endpoint_errors: HashMap<String, (usize, usize)> = HashMap::new();
399
400        for request in requests {
401            let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("UNKNOWN");
402            let path = request.get("path").and_then(|v| v.as_str()).unwrap_or("/");
403            let status_code = request.get("status_code").and_then(|v| v.as_u64());
404
405            let key = format!("{} {}", method, path);
406            let entry = endpoint_errors.entry(key).or_insert((0, 0));
407            entry.0 += 1;
408
409            if let Some(status) = status_code {
410                if status >= 400 {
411                    entry.1 += 1;
412                }
413            }
414        }
415
416        let mut patterns = Vec::new();
417
418        for (endpoint_key, (total, errors)) in endpoint_errors {
419            if total < 20 {
420                continue;
421            }
422
423            let error_rate = errors as f64 / total as f64;
424
425            if error_rate > 0.05 {
426                let mut parameters = HashMap::new();
427                parameters.insert("endpoint".to_string(), serde_json::json!(endpoint_key));
428                parameters.insert("error_rate".to_string(), serde_json::json!(error_rate));
429                parameters.insert("total_requests".to_string(), serde_json::json!(total));
430                parameters.insert("error_count".to_string(), serde_json::json!(errors));
431
432                let confidence = ((total as f64 / 100.0).min(1.0) * error_rate * 10.0).min(1.0);
433
434                patterns.push(LearnedPattern {
435                    pattern_id: format!("error_rate_{}", endpoint_key.replace(['/', ' '], "_")),
436                    pattern_type: PatternType::ErrorRate,
437                    parameters,
438                    confidence,
439                    sample_count: total,
440                    last_updated: Utc::now(),
441                });
442            }
443        }
444
445        Ok(patterns)
446    }
447
448    /// Detect request sequence patterns from recorded request data.
449    ///
450    /// Each request value should contain `method` (string), `path` (string),
451    /// and optionally `trace_id` (string) fields.
452    async fn detect_sequence_patterns_from_requests(
453        &self,
454        requests: &[Value],
455    ) -> Result<Vec<LearnedPattern>> {
456        use chrono::Utc;
457
458        if requests.len() < 50 {
459            return Ok(Vec::new());
460        }
461
462        let mut trace_sequences: HashMap<Option<String>, Vec<String>> = HashMap::new();
463
464        for request in requests {
465            let trace_id = request.get("trace_id").and_then(|v| v.as_str()).map(String::from);
466            let method = request.get("method").and_then(|v| v.as_str()).unwrap_or("UNKNOWN");
467            let path = request.get("path").and_then(|v| v.as_str()).unwrap_or("/");
468            let endpoint_key = format!("{} {}", method, path);
469            trace_sequences.entry(trace_id).or_default().push(endpoint_key);
470        }
471
472        let mut sequence_counts: HashMap<String, usize> = HashMap::new();
473
474        for sequence in trace_sequences.values() {
475            if sequence.len() >= 2 {
476                let signature: Vec<String> = sequence.iter().take(3).cloned().collect();
477                let signature_str = signature.join(" -> ");
478                *sequence_counts.entry(signature_str).or_insert(0) += 1;
479            }
480        }
481
482        let mut patterns = Vec::new();
483
484        for (sequence_str, count) in sequence_counts {
485            if count >= 5 {
486                let mut parameters = HashMap::new();
487                parameters.insert("sequence".to_string(), serde_json::json!(sequence_str));
488                parameters.insert("occurrence_count".to_string(), serde_json::json!(count));
489
490                let confidence = (count as f64 / 20.0).min(1.0);
491
492                patterns.push(LearnedPattern {
493                    pattern_id: format!(
494                        "sequence_{}",
495                        sequence_str.replace(['/', ' '], "_").replace("->", "_")
496                    ),
497                    pattern_type: PatternType::RequestSequence,
498                    parameters,
499                    confidence,
500                    sample_count: count,
501                    last_updated: Utc::now(),
502                });
503            }
504        }
505
506        Ok(patterns)
507    }
508
509    /// Detect latency patterns from recorded requests.
510    ///
511    /// Convenience wrapper — pass request data as JSON values.
512    pub async fn detect_latency_patterns(
513        &mut self,
514        requests: &[Value],
515    ) -> Result<Vec<LearnedPattern>> {
516        self.detect_latency_patterns_from_requests(requests).await
517    }
518
519    /// Detect error rate patterns from recorded requests.
520    ///
521    /// Convenience wrapper — pass request data as JSON values.
522    pub async fn detect_error_patterns(
523        &mut self,
524        requests: &[Value],
525    ) -> Result<Vec<LearnedPattern>> {
526        self.detect_error_patterns_from_requests(requests).await
527    }
528}
529
530/// Persona Behavior Learner
531///
532/// Adapts persona profiles based on request patterns.
533pub struct PersonaBehaviorLearner {
534    /// Learning configuration
535    config: LearningConfig,
536    /// Behavior history (persona_id -> behavior events)
537    behavior_history: HashMap<String, Vec<BehaviorEvent>>,
538}
539
540/// Behavior event for a persona
541#[derive(Debug, Clone)]
542pub struct BehaviorEvent {
543    /// Event timestamp
544    pub timestamp: chrono::DateTime<chrono::Utc>,
545    /// Event type
546    pub event_type: BehaviorEventType,
547    /// Event data
548    pub data: HashMap<String, Value>,
549}
550
551/// Behavior event type
552#[derive(Debug, Clone, PartialEq, Eq)]
553pub enum BehaviorEventType {
554    /// Request made to an endpoint
555    Request {
556        /// Endpoint path
557        endpoint: String,
558        /// HTTP method
559        method: String,
560    },
561    /// Request failed
562    RequestFailed {
563        /// Endpoint path
564        endpoint: String,
565        /// HTTP status code
566        status_code: u16,
567    },
568    /// Request succeeded after failure
569    RequestSucceededAfterFailure {
570        /// Endpoint path
571        endpoint: String,
572    },
573    /// Pattern detected
574    PatternDetected {
575        /// Pattern identifier
576        pattern: String,
577    },
578}
579
580impl PersonaBehaviorLearner {
581    /// Create a new persona behavior learner
582    pub fn new(config: LearningConfig) -> Result<Self> {
583        Ok(Self {
584            config,
585            behavior_history: HashMap::new(),
586        })
587    }
588
589    /// Record a behavior event for a persona
590    pub fn record_event(&mut self, persona_id: String, event: BehaviorEvent) {
591        if !self.config.enabled {
592            return;
593        }
594
595        // Check if persona learning is enabled for this persona
596        if let Some(&enabled) = self.config.persona_learning.get(&persona_id) {
597            if !enabled {
598                return; // Learning disabled for this persona
599            }
600        }
601
602        let events = self.behavior_history.entry(persona_id).or_default();
603        events.push(event);
604
605        // Keep only recent events (last 1000)
606        if events.len() > 1000 {
607            events.remove(0);
608        }
609    }
610
611    /// Analyze behavior patterns for a persona
612    pub async fn analyze_persona_behavior(
613        &self,
614        persona_id: &str,
615    ) -> Result<Option<LearnedPattern>> {
616        if !self.config.enabled {
617            return Ok(None);
618        }
619
620        let events = match self.behavior_history.get(persona_id) {
621            Some(events) => events,
622            None => return Ok(None),
623        };
624
625        if events.len() < self.config.min_samples {
626            return Ok(None); // Not enough samples
627        }
628
629        // Analyze patterns
630        // Example: If persona repeatedly requests /checkout after failure, learn this pattern
631        let mut checkout_after_failure_count = 0;
632        let mut total_failures = 0;
633
634        for i in 1..events.len() {
635            if let BehaviorEventType::RequestFailed { .. } = events[i - 1].event_type {
636                total_failures += 1;
637                if let BehaviorEventType::Request { endpoint, .. } = &events[i].event_type {
638                    if endpoint.contains("/checkout") {
639                        checkout_after_failure_count += 1;
640                    }
641                }
642            }
643        }
644
645        if total_failures > 0 && checkout_after_failure_count as f64 / total_failures as f64 > 0.5 {
646            // Pattern detected: persona requests /checkout after failure > 50% of the time
647            let mut parameters = HashMap::new();
648            parameters.insert("retry_checkout_after_failure".to_string(), Value::from(true));
649            parameters.insert(
650                "retry_probability".to_string(),
651                Value::from(checkout_after_failure_count as f64 / total_failures as f64),
652            );
653
654            return Ok(Some(LearnedPattern {
655                pattern_id: format!("persona_{}_checkout_retry", persona_id),
656                pattern_type: PatternType::PersonaBehavior,
657                parameters,
658                confidence: (checkout_after_failure_count as f64 / total_failures as f64).min(1.0),
659                sample_count: total_failures,
660                last_updated: chrono::Utc::now(),
661            }));
662        }
663
664        Ok(None)
665    }
666
667    /// Get behavior history for a persona
668    pub fn get_behavior_history(&self, persona_id: &str) -> Option<&Vec<BehaviorEvent>> {
669        self.behavior_history.get(persona_id)
670    }
671
672    /// Apply learned patterns to a persona in PersonaRegistry
673    ///
674    /// This method should be called periodically to update persona profiles
675    /// based on learned behavior patterns.
676    pub async fn apply_learned_patterns_to_persona(
677        &self,
678        persona_id: &str,
679        persona_registry: &crate::PersonaRegistry,
680    ) -> Result<()> {
681        if !self.config.enabled {
682            return Ok(());
683        }
684
685        // Analyze behavior for this persona
686        if let Some(pattern) = self.analyze_persona_behavior(persona_id).await? {
687            // Convert learned pattern parameters to traits
688            let mut learned_traits = HashMap::new();
689            for (key, value) in &pattern.parameters {
690                let trait_key = format!("learned_{}", key);
691                let trait_value = if let Some(s) = value.as_str() {
692                    s.to_string()
693                } else if let Some(n) = value.as_f64() {
694                    n.to_string()
695                } else if let Some(b) = value.as_bool() {
696                    b.to_string()
697                } else {
698                    value.to_string()
699                };
700                learned_traits.insert(trait_key, trait_value);
701            }
702
703            // Update persona traits in registry
704            if !learned_traits.is_empty() {
705                persona_registry.update_persona(persona_id, learned_traits)?;
706            }
707        }
708
709        Ok(())
710    }
711}
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716
717    // =========================================================================
718    // LearningConfig tests
719    // =========================================================================
720
721    #[test]
722    fn test_learning_config_default() {
723        let config = LearningConfig::default();
724        assert!(!config.enabled); // Opt-in by default
725        assert_eq!(config.sensitivity, 0.2);
726        assert_eq!(config.min_samples, 10);
727        assert_eq!(config.decay, 0.05);
728        assert!(config.persona_adaptation);
729        assert!(config.traffic_mirroring);
730    }
731
732    #[test]
733    fn test_learning_config_serialize() {
734        let config = LearningConfig {
735            enabled: true,
736            mode: LearningMode::Statistical,
737            ..Default::default()
738        };
739        let json = serde_json::to_string(&config).unwrap();
740        assert!(json.contains("true"));
741        assert!(json.contains("statistical"));
742    }
743
744    #[test]
745    fn test_learning_config_deserialize() {
746        let json = r#"{"enabled": true, "mode": "hybrid", "sensitivity": 0.5}"#;
747        let config: LearningConfig = serde_json::from_str(json).unwrap();
748        assert!(config.enabled);
749        assert_eq!(config.mode, LearningMode::Hybrid);
750        assert!((config.sensitivity - 0.5).abs() < f64::EPSILON);
751    }
752
753    #[test]
754    fn test_learning_config_clone() {
755        let config = LearningConfig {
756            enabled: true,
757            sensitivity: 0.3,
758            ..Default::default()
759        };
760        let cloned = config.clone();
761        assert!(cloned.enabled);
762        assert!((cloned.sensitivity - 0.3).abs() < f64::EPSILON);
763    }
764
765    #[test]
766    fn test_learning_config_debug() {
767        let config = LearningConfig::default();
768        let debug_str = format!("{:?}", config);
769        assert!(debug_str.contains("sensitivity"));
770        assert!(debug_str.contains("enabled"));
771    }
772
773    #[test]
774    fn test_learning_config_endpoint_learning() {
775        let mut config = LearningConfig::default();
776        config.endpoint_learning.insert("/api/users".to_string(), true);
777        config.endpoint_learning.insert("/api/orders".to_string(), false);
778
779        assert_eq!(config.endpoint_learning.get("/api/users"), Some(&true));
780        assert_eq!(config.endpoint_learning.get("/api/orders"), Some(&false));
781    }
782
783    #[test]
784    fn test_learning_config_persona_learning() {
785        let mut config = LearningConfig::default();
786        config.persona_learning.insert("persona-1".to_string(), true);
787        config.persona_learning.insert("persona-2".to_string(), false);
788
789        assert_eq!(config.persona_learning.get("persona-1"), Some(&true));
790        assert_eq!(config.persona_learning.get("persona-2"), Some(&false));
791    }
792
793    // =========================================================================
794    // LearningMode tests
795    // =========================================================================
796
797    #[test]
798    fn test_learning_mode_default() {
799        let mode = LearningMode::default();
800        assert_eq!(mode, LearningMode::Behavioral);
801    }
802
803    #[test]
804    fn test_learning_mode_eq() {
805        assert_eq!(LearningMode::Statistical, LearningMode::Statistical);
806        assert_ne!(LearningMode::Behavioral, LearningMode::Hybrid);
807    }
808
809    #[test]
810    fn test_learning_mode_serialize() {
811        let mode = LearningMode::Hybrid;
812        let json = serde_json::to_string(&mode).unwrap();
813        assert_eq!(json, "\"hybrid\"");
814    }
815
816    #[test]
817    fn test_learning_mode_deserialize() {
818        let json = "\"statistical\"";
819        let mode: LearningMode = serde_json::from_str(json).unwrap();
820        assert_eq!(mode, LearningMode::Statistical);
821    }
822
823    #[test]
824    fn test_learning_mode_clone() {
825        let mode = LearningMode::Hybrid;
826        let cloned = mode.clone();
827        assert_eq!(cloned, LearningMode::Hybrid);
828    }
829
830    #[test]
831    fn test_learning_mode_debug() {
832        let debug_str = format!("{:?}", LearningMode::Behavioral);
833        assert!(debug_str.contains("Behavioral"));
834    }
835
836    // =========================================================================
837    // DriftLearningEngine tests
838    // =========================================================================
839
840    #[test]
841    fn test_drift_learning_engine_creation() {
842        let drift_config = DataDriftConfig::new();
843        let learning_config = LearningConfig::default();
844        let engine = DriftLearningEngine::new(drift_config, learning_config);
845        assert!(engine.is_ok());
846    }
847
848    #[test]
849    fn test_drift_learning_engine_with_traffic_mirroring_disabled() {
850        let drift_config = DataDriftConfig::new();
851        let learning_config = LearningConfig {
852            traffic_mirroring: false,
853            ..Default::default()
854        };
855        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
856        assert!(engine._traffic_learner.is_none());
857    }
858
859    #[test]
860    fn test_drift_learning_engine_with_persona_adaptation_disabled() {
861        let drift_config = DataDriftConfig::new();
862        let learning_config = LearningConfig {
863            persona_adaptation: false,
864            ..Default::default()
865        };
866        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
867        assert!(engine._persona_learner.is_none());
868    }
869
870    #[test]
871    fn test_drift_learning_engine_get_drift_engine() {
872        let drift_config = DataDriftConfig::new();
873        let learning_config = LearningConfig::default();
874        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
875        let _ = engine.drift_engine();
876    }
877
878    #[test]
879    fn test_drift_learning_engine_get_learning_config() {
880        let drift_config = DataDriftConfig::new();
881        let learning_config = LearningConfig {
882            enabled: true,
883            sensitivity: 0.5,
884            ..Default::default()
885        };
886        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
887        assert!(engine.learning_config().enabled);
888        assert!((engine.learning_config().sensitivity - 0.5).abs() < f64::EPSILON);
889    }
890
891    #[test]
892    fn test_drift_learning_engine_update_learning_config() {
893        let drift_config = DataDriftConfig::new();
894        let learning_config = LearningConfig::default();
895        let mut engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
896
897        let new_config = LearningConfig {
898            enabled: true,
899            sensitivity: 0.8,
900            ..Default::default()
901        };
902        engine.update_learning_config(new_config).unwrap();
903
904        assert!(engine.learning_config().enabled);
905        assert!((engine.learning_config().sensitivity - 0.8).abs() < f64::EPSILON);
906    }
907
908    #[tokio::test]
909    async fn test_drift_learning_engine_get_learned_patterns_empty() {
910        let drift_config = DataDriftConfig::new();
911        let learning_config = LearningConfig::default();
912        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
913
914        let patterns = engine.get_learned_patterns().await;
915        assert!(patterns.is_empty());
916    }
917
918    #[tokio::test]
919    async fn test_drift_learning_engine_apply_drift_with_learning_disabled() {
920        let drift_config = DataDriftConfig::new();
921        let learning_config = LearningConfig {
922            enabled: false,
923            ..Default::default()
924        };
925        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
926
927        let data = serde_json::json!({"value": 100});
928        let result = engine.apply_drift_with_learning(data.clone()).await.unwrap();
929        // Should return data without learning modifications
930        assert!(result.is_object());
931    }
932
933    #[tokio::test]
934    async fn test_drift_learning_engine_apply_drift_with_learning_enabled() {
935        let drift_config = DataDriftConfig::new();
936        let learning_config = LearningConfig {
937            enabled: true,
938            ..Default::default()
939        };
940        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
941
942        let data = serde_json::json!({"value": 100});
943        let result = engine.apply_drift_with_learning(data).await.unwrap();
944        assert!(result.is_object());
945    }
946
947    #[test]
948    fn test_drift_learning_engine_blend_values_numeric() {
949        let drift_config = DataDriftConfig::new();
950        let learning_config = LearningConfig {
951            sensitivity: 0.5,
952            ..Default::default()
953        };
954        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
955
956        let existing = serde_json::json!(100.0);
957        let learned = serde_json::json!(200.0);
958        let result = engine.blend_values(&existing, &learned, 0.5).unwrap();
959
960        // With sensitivity 0.5 and confidence 0.5, weight = 0.25
961        // blended = 100 * 0.75 + 200 * 0.25 = 75 + 50 = 125
962        if let Some(n) = result.as_f64() {
963            assert!((n - 125.0).abs() < 1.0);
964        }
965    }
966
967    #[test]
968    fn test_drift_learning_engine_blend_values_non_numeric() {
969        let drift_config = DataDriftConfig::new();
970        let learning_config = LearningConfig::default();
971        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
972
973        let existing = serde_json::json!("original");
974        let learned = serde_json::json!("learned");
975        let result = engine.blend_values(&existing, &learned, 0.5).unwrap();
976
977        // Non-numeric values keep existing
978        assert_eq!(result, serde_json::json!("original"));
979    }
980
981    // =========================================================================
982    // LearnedPattern tests
983    // =========================================================================
984
985    #[test]
986    fn test_learned_pattern_creation() {
987        let pattern = LearnedPattern {
988            pattern_id: "test-pattern".to_string(),
989            pattern_type: PatternType::Latency,
990            parameters: HashMap::new(),
991            confidence: 0.9,
992            sample_count: 100,
993            last_updated: chrono::Utc::now(),
994        };
995        assert_eq!(pattern.pattern_id, "test-pattern");
996        assert!((pattern.confidence - 0.9).abs() < f64::EPSILON);
997    }
998
999    #[test]
1000    fn test_learned_pattern_clone() {
1001        let pattern = LearnedPattern {
1002            pattern_id: "cloneable".to_string(),
1003            pattern_type: PatternType::ErrorRate,
1004            parameters: HashMap::new(),
1005            confidence: 0.75,
1006            sample_count: 50,
1007            last_updated: chrono::Utc::now(),
1008        };
1009        let cloned = pattern.clone();
1010        assert_eq!(cloned.pattern_id, "cloneable");
1011    }
1012
1013    #[test]
1014    fn test_learned_pattern_debug() {
1015        let pattern = LearnedPattern {
1016            pattern_id: "debug-pattern".to_string(),
1017            pattern_type: PatternType::PersonaBehavior,
1018            parameters: HashMap::new(),
1019            confidence: 0.5,
1020            sample_count: 25,
1021            last_updated: chrono::Utc::now(),
1022        };
1023        let debug_str = format!("{:?}", pattern);
1024        assert!(debug_str.contains("debug-pattern"));
1025    }
1026
1027    // =========================================================================
1028    // PatternType tests
1029    // =========================================================================
1030
1031    #[test]
1032    fn test_pattern_type_eq() {
1033        assert_eq!(PatternType::Latency, PatternType::Latency);
1034        assert_ne!(PatternType::Latency, PatternType::ErrorRate);
1035    }
1036
1037    #[test]
1038    fn test_pattern_type_clone() {
1039        let pt = PatternType::RequestSequence;
1040        let cloned = pt.clone();
1041        assert_eq!(cloned, PatternType::RequestSequence);
1042    }
1043
1044    #[test]
1045    fn test_pattern_type_debug() {
1046        let debug_str = format!("{:?}", PatternType::PersonaBehavior);
1047        assert!(debug_str.contains("PersonaBehavior"));
1048    }
1049
1050    // =========================================================================
1051    // TrafficPatternLearner tests
1052    // =========================================================================
1053
1054    #[test]
1055    fn test_traffic_pattern_learner_new() {
1056        let config = LearningConfig::default();
1057        let learner = TrafficPatternLearner::new(config);
1058        assert!(learner.is_ok());
1059    }
1060
1061    #[tokio::test]
1062    async fn test_traffic_pattern_learner_analyze_empty() {
1063        let config = LearningConfig::default();
1064        let mut learner = TrafficPatternLearner::new(config).unwrap();
1065        let patterns = learner.analyze_traffic_patterns(&[]).await.unwrap();
1066        assert!(patterns.is_empty());
1067    }
1068
1069    #[tokio::test]
1070    async fn test_traffic_pattern_learner_detect_latency_patterns() {
1071        let config = LearningConfig::default();
1072        let mut learner = TrafficPatternLearner::new(config).unwrap();
1073        // Not enough samples (< 10) should return empty
1074        let requests: Vec<Value> = (0..5)
1075            .map(|i| {
1076                serde_json::json!({
1077                    "method": "GET",
1078                    "path": "/api/test",
1079                    "duration_ms": 100 + i * 10,
1080                })
1081            })
1082            .collect();
1083        let patterns = learner.detect_latency_patterns(&requests).await.unwrap();
1084        assert!(patterns.is_empty());
1085    }
1086
1087    #[tokio::test]
1088    async fn test_traffic_pattern_learner_detect_error_patterns() {
1089        let config = LearningConfig::default();
1090        let mut learner = TrafficPatternLearner::new(config).unwrap();
1091        // Not enough samples (< 20) should return empty
1092        let requests: Vec<Value> = (0..10)
1093            .map(|_| {
1094                serde_json::json!({
1095                    "method": "GET",
1096                    "path": "/api/test",
1097                    "status_code": 500,
1098                })
1099            })
1100            .collect();
1101        let patterns = learner.detect_error_patterns(&requests).await.unwrap();
1102        assert!(patterns.is_empty());
1103    }
1104
1105    // =========================================================================
1106    // PersonaBehaviorLearner tests
1107    // =========================================================================
1108
1109    #[test]
1110    fn test_persona_behavior_learner_new() {
1111        let config = LearningConfig::default();
1112        let learner = PersonaBehaviorLearner::new(config);
1113        assert!(learner.is_ok());
1114    }
1115
1116    #[test]
1117    fn test_persona_behavior_learner_record_event_disabled() {
1118        let config = LearningConfig {
1119            enabled: false,
1120            ..Default::default()
1121        };
1122        let mut learner = PersonaBehaviorLearner::new(config).unwrap();
1123
1124        learner.record_event(
1125            "persona-1".to_string(),
1126            BehaviorEvent {
1127                timestamp: chrono::Utc::now(),
1128                event_type: BehaviorEventType::Request {
1129                    endpoint: "/api/test".to_string(),
1130                    method: "GET".to_string(),
1131                },
1132                data: HashMap::new(),
1133            },
1134        );
1135
1136        // Should not record when disabled
1137        assert!(learner.get_behavior_history("persona-1").is_none());
1138    }
1139
1140    #[test]
1141    fn test_persona_behavior_learner_record_event_persona_disabled() {
1142        let mut config = LearningConfig {
1143            enabled: true,
1144            ..Default::default()
1145        };
1146        config.persona_learning.insert("persona-1".to_string(), false);
1147        let mut learner = PersonaBehaviorLearner::new(config).unwrap();
1148
1149        learner.record_event(
1150            "persona-1".to_string(),
1151            BehaviorEvent {
1152                timestamp: chrono::Utc::now(),
1153                event_type: BehaviorEventType::Request {
1154                    endpoint: "/api/test".to_string(),
1155                    method: "GET".to_string(),
1156                },
1157                data: HashMap::new(),
1158            },
1159        );
1160
1161        // Should not record when persona learning is disabled
1162        assert!(learner.get_behavior_history("persona-1").is_none());
1163    }
1164
1165    #[tokio::test]
1166    async fn test_persona_behavior_learner() {
1167        let config = LearningConfig {
1168            enabled: true,
1169            persona_adaptation: true,
1170            ..Default::default()
1171        };
1172        let mut learner = PersonaBehaviorLearner::new(config).unwrap();
1173
1174        // Record failure
1175        learner.record_event(
1176            "persona-1".to_string(),
1177            BehaviorEvent {
1178                timestamp: chrono::Utc::now(),
1179                event_type: BehaviorEventType::RequestFailed {
1180                    endpoint: "/api/checkout".to_string(),
1181                    status_code: 500,
1182                },
1183                data: HashMap::new(),
1184            },
1185        );
1186
1187        // Record checkout request after failure
1188        learner.record_event(
1189            "persona-1".to_string(),
1190            BehaviorEvent {
1191                timestamp: chrono::Utc::now(),
1192                event_type: BehaviorEventType::Request {
1193                    endpoint: "/api/checkout".to_string(),
1194                    method: "POST".to_string(),
1195                },
1196                data: HashMap::new(),
1197            },
1198        );
1199
1200        // Analyze (won't find pattern with only 2 samples, need min_samples)
1201        let pattern = learner.analyze_persona_behavior("persona-1").await.unwrap();
1202        assert!(pattern.is_none()); // Not enough samples
1203    }
1204
1205    #[tokio::test]
1206    async fn test_persona_behavior_learner_get_behavior_history() {
1207        let config = LearningConfig {
1208            enabled: true,
1209            ..Default::default()
1210        };
1211        let mut learner = PersonaBehaviorLearner::new(config).unwrap();
1212
1213        learner.record_event(
1214            "persona-test".to_string(),
1215            BehaviorEvent {
1216                timestamp: chrono::Utc::now(),
1217                event_type: BehaviorEventType::Request {
1218                    endpoint: "/api/users".to_string(),
1219                    method: "GET".to_string(),
1220                },
1221                data: HashMap::new(),
1222            },
1223        );
1224
1225        let history = learner.get_behavior_history("persona-test");
1226        assert!(history.is_some());
1227        assert_eq!(history.unwrap().len(), 1);
1228    }
1229
1230    #[tokio::test]
1231    async fn test_persona_behavior_learner_analyze_nonexistent_persona() {
1232        let config = LearningConfig {
1233            enabled: true,
1234            ..Default::default()
1235        };
1236        let learner = PersonaBehaviorLearner::new(config).unwrap();
1237
1238        let pattern = learner.analyze_persona_behavior("nonexistent").await.unwrap();
1239        assert!(pattern.is_none());
1240    }
1241
1242    #[tokio::test]
1243    async fn test_persona_behavior_learner_analyze_disabled() {
1244        let config = LearningConfig {
1245            enabled: false,
1246            ..Default::default()
1247        };
1248        let learner = PersonaBehaviorLearner::new(config).unwrap();
1249
1250        let pattern = learner.analyze_persona_behavior("any").await.unwrap();
1251        assert!(pattern.is_none());
1252    }
1253
1254    #[test]
1255    fn test_persona_behavior_learner_event_limit() {
1256        let config = LearningConfig {
1257            enabled: true,
1258            ..Default::default()
1259        };
1260        let mut learner = PersonaBehaviorLearner::new(config).unwrap();
1261
1262        // Record more than 1000 events
1263        for i in 0..1050 {
1264            learner.record_event(
1265                "persona-limit".to_string(),
1266                BehaviorEvent {
1267                    timestamp: chrono::Utc::now(),
1268                    event_type: BehaviorEventType::Request {
1269                        endpoint: format!("/api/test/{}", i),
1270                        method: "GET".to_string(),
1271                    },
1272                    data: HashMap::new(),
1273                },
1274            );
1275        }
1276
1277        let history = learner.get_behavior_history("persona-limit").unwrap();
1278        assert_eq!(history.len(), 1000); // Should be capped at 1000
1279    }
1280
1281    // =========================================================================
1282    // BehaviorEvent tests
1283    // =========================================================================
1284
1285    #[test]
1286    fn test_behavior_event_creation() {
1287        let event = BehaviorEvent {
1288            timestamp: chrono::Utc::now(),
1289            event_type: BehaviorEventType::Request {
1290                endpoint: "/api/test".to_string(),
1291                method: "POST".to_string(),
1292            },
1293            data: HashMap::new(),
1294        };
1295        assert!(event.data.is_empty());
1296    }
1297
1298    #[test]
1299    fn test_behavior_event_clone() {
1300        let event = BehaviorEvent {
1301            timestamp: chrono::Utc::now(),
1302            event_type: BehaviorEventType::PatternDetected {
1303                pattern: "test-pattern".to_string(),
1304            },
1305            data: HashMap::new(),
1306        };
1307        let cloned = event.clone();
1308        if let BehaviorEventType::PatternDetected { pattern } = cloned.event_type {
1309            assert_eq!(pattern, "test-pattern");
1310        } else {
1311            panic!("Wrong event type after clone");
1312        }
1313    }
1314
1315    #[test]
1316    fn test_behavior_event_debug() {
1317        let event = BehaviorEvent {
1318            timestamp: chrono::Utc::now(),
1319            event_type: BehaviorEventType::RequestSucceededAfterFailure {
1320                endpoint: "/api/retry".to_string(),
1321            },
1322            data: HashMap::new(),
1323        };
1324        let debug_str = format!("{:?}", event);
1325        assert!(debug_str.contains("RequestSucceededAfterFailure"));
1326    }
1327
1328    // =========================================================================
1329    // BehaviorEventType tests
1330    // =========================================================================
1331
1332    #[test]
1333    fn test_behavior_event_type_request() {
1334        let event_type = BehaviorEventType::Request {
1335            endpoint: "/api/users".to_string(),
1336            method: "GET".to_string(),
1337        };
1338        if let BehaviorEventType::Request { endpoint, method } = event_type {
1339            assert_eq!(endpoint, "/api/users");
1340            assert_eq!(method, "GET");
1341        }
1342    }
1343
1344    #[test]
1345    fn test_behavior_event_type_request_failed() {
1346        let event_type = BehaviorEventType::RequestFailed {
1347            endpoint: "/api/orders".to_string(),
1348            status_code: 500,
1349        };
1350        if let BehaviorEventType::RequestFailed {
1351            endpoint,
1352            status_code,
1353        } = event_type
1354        {
1355            assert_eq!(endpoint, "/api/orders");
1356            assert_eq!(status_code, 500);
1357        }
1358    }
1359
1360    #[test]
1361    fn test_behavior_event_type_eq() {
1362        let a = BehaviorEventType::PatternDetected {
1363            pattern: "a".to_string(),
1364        };
1365        let b = BehaviorEventType::PatternDetected {
1366            pattern: "a".to_string(),
1367        };
1368        assert_eq!(a, b);
1369    }
1370
1371    #[test]
1372    fn test_behavior_event_type_clone() {
1373        let event_type = BehaviorEventType::RequestFailed {
1374            endpoint: "/test".to_string(),
1375            status_code: 404,
1376        };
1377        let cloned = event_type.clone();
1378        assert_eq!(cloned, event_type);
1379    }
1380
1381    #[test]
1382    fn test_behavior_event_type_debug() {
1383        let event_type = BehaviorEventType::Request {
1384            endpoint: "/debug".to_string(),
1385            method: "DELETE".to_string(),
1386        };
1387        let debug_str = format!("{:?}", event_type);
1388        assert!(debug_str.contains("Request"));
1389        assert!(debug_str.contains("/debug"));
1390    }
1391
1392    // =========================================================================
1393    // Integration tests
1394    // =========================================================================
1395
1396    #[tokio::test]
1397    async fn test_full_learning_workflow() {
1398        // Create learning engine
1399        let drift_config = DataDriftConfig::new();
1400        let learning_config = LearningConfig {
1401            enabled: true,
1402            min_samples: 2, // Lower threshold for testing
1403            ..Default::default()
1404        };
1405        let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
1406
1407        // Apply drift with learning
1408        let data = serde_json::json!({
1409            "user_id": "123",
1410            "amount": 100.0,
1411            "status": "pending"
1412        });
1413        let result = engine.apply_drift_with_learning(data).await.unwrap();
1414        assert!(result.is_object());
1415    }
1416
1417    #[tokio::test]
1418    async fn test_persona_behavior_pattern_detection() {
1419        let config = LearningConfig {
1420            enabled: true,
1421            min_samples: 5,
1422            ..Default::default()
1423        };
1424        let mut learner = PersonaBehaviorLearner::new(config).unwrap();
1425
1426        // Simulate pattern: failure followed by checkout retry
1427        for _ in 0..10 {
1428            learner.record_event(
1429                "retry-persona".to_string(),
1430                BehaviorEvent {
1431                    timestamp: chrono::Utc::now(),
1432                    event_type: BehaviorEventType::RequestFailed {
1433                        endpoint: "/api/payment".to_string(),
1434                        status_code: 503,
1435                    },
1436                    data: HashMap::new(),
1437                },
1438            );
1439            learner.record_event(
1440                "retry-persona".to_string(),
1441                BehaviorEvent {
1442                    timestamp: chrono::Utc::now(),
1443                    event_type: BehaviorEventType::Request {
1444                        endpoint: "/api/checkout".to_string(),
1445                        method: "POST".to_string(),
1446                    },
1447                    data: HashMap::new(),
1448                },
1449            );
1450        }
1451
1452        // Now analyze - should detect pattern
1453        let pattern = learner.analyze_persona_behavior("retry-persona").await.unwrap();
1454        assert!(pattern.is_some());
1455        let p = pattern.unwrap();
1456        assert_eq!(p.pattern_type, PatternType::PersonaBehavior);
1457        assert!(p.confidence > 0.0);
1458    }
1459}