1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct LearningConfig {
24 #[serde(default)]
26 pub enabled: bool,
27
28 #[serde(default)]
30 pub mode: LearningMode,
31
32 #[serde(default = "default_learning_rate")]
34 pub sensitivity: f64,
35
36 #[serde(default = "default_decay_rate")]
38 pub decay: f64,
39
40 #[serde(default = "default_min_samples")]
42 pub min_samples: usize,
43
44 #[serde(default = "default_update_interval")]
46 pub update_interval: Duration,
47
48 #[serde(default = "default_true")]
50 pub persona_adaptation: bool,
51
52 #[serde(default = "default_true")]
54 pub traffic_mirroring: bool,
55
56 #[serde(default)]
58 pub endpoint_learning: HashMap<String, bool>,
59
60 #[serde(default)]
62 pub persona_learning: HashMap<String, bool>,
63}
64
65fn default_learning_rate() -> f64 {
66 0.2 }
68
69fn default_decay_rate() -> f64 {
70 0.05 }
72
73fn default_min_samples() -> usize {
74 10 }
76
77fn default_update_interval() -> Duration {
78 Duration::from_secs(60) }
80
81fn default_true() -> bool {
82 true
83}
84
85impl Default for LearningConfig {
86 fn default() -> Self {
87 Self {
88 enabled: false, 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
104#[serde(rename_all = "snake_case")]
105pub enum LearningMode {
106 #[default]
108 Behavioral,
109 Statistical,
111 Hybrid,
113}
114
115pub struct DriftLearningEngine {
119 drift_engine: DataDriftEngine,
121 learning_config: LearningConfig,
123 _traffic_learner: Option<Arc<RwLock<TrafficPatternLearner>>>,
125 _persona_learner: Option<Arc<RwLock<PersonaBehaviorLearner>>>,
127 learned_patterns: Arc<RwLock<HashMap<String, LearnedPattern>>>,
129}
130
131#[derive(Debug, Clone)]
133pub struct LearnedPattern {
134 pub pattern_id: String,
136 pub pattern_type: PatternType,
138 pub parameters: HashMap<String, Value>,
140 pub confidence: f64,
142 pub sample_count: usize,
144 pub last_updated: chrono::DateTime<chrono::Utc>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
150pub enum PatternType {
151 Latency,
153 ErrorRate,
155 RequestSequence,
157 PersonaBehavior,
159}
160
161impl DriftLearningEngine {
162 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 pub fn drift_engine(&self) -> &DataDriftEngine {
189 &self.drift_engine
190 }
191
192 pub fn learning_config(&self) -> &LearningConfig {
194 &self.learning_config
195 }
196
197 pub fn update_learning_config(&mut self, config: LearningConfig) -> Result<()> {
199 self.learning_config = config;
200 Ok(())
201 }
202
203 pub async fn get_learned_patterns(&self) -> HashMap<String, LearnedPattern> {
205 self.learned_patterns.read().await.clone()
206 }
207
208 pub async fn apply_drift_with_learning(&self, data: Value) -> Result<Value> {
210 let mut data = self.drift_engine.apply_drift(data).await?;
212
213 if !self.learning_config.enabled {
215 return Ok(data);
216 }
217
218 let patterns = self.learned_patterns.read().await;
220 for (_pattern_id, pattern) in patterns.iter() {
221 if pattern.confidence < 0.5 {
223 continue; }
225
226 match pattern.pattern_type {
228 PatternType::Latency => {
229 }
231 PatternType::ErrorRate => {
232 }
234 PatternType::RequestSequence => {
235 }
237 PatternType::PersonaBehavior => {
238 if let Some(obj) = data.as_object_mut() {
240 for (key, value) in &pattern.parameters {
241 if let Some(existing) = obj.get(key) {
242 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 fn blend_values(&self, existing: &Value, learned: &Value, confidence: f64) -> Result<Value> {
258 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()), }
274 }
275}
276
277pub struct TrafficPatternLearner {
281 _config: LearningConfig,
283}
284
285impl TrafficPatternLearner {
286 pub fn new(config: LearningConfig) -> Result<Self> {
288 Ok(Self { _config: config })
289 }
290
291 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 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 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 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 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 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
530pub struct PersonaBehaviorLearner {
534 config: LearningConfig,
536 behavior_history: HashMap<String, Vec<BehaviorEvent>>,
538}
539
540#[derive(Debug, Clone)]
542pub struct BehaviorEvent {
543 pub timestamp: chrono::DateTime<chrono::Utc>,
545 pub event_type: BehaviorEventType,
547 pub data: HashMap<String, Value>,
549}
550
551#[derive(Debug, Clone, PartialEq, Eq)]
553pub enum BehaviorEventType {
554 Request {
556 endpoint: String,
558 method: String,
560 },
561 RequestFailed {
563 endpoint: String,
565 status_code: u16,
567 },
568 RequestSucceededAfterFailure {
570 endpoint: String,
572 },
573 PatternDetected {
575 pattern: String,
577 },
578}
579
580impl PersonaBehaviorLearner {
581 pub fn new(config: LearningConfig) -> Result<Self> {
583 Ok(Self {
584 config,
585 behavior_history: HashMap::new(),
586 })
587 }
588
589 pub fn record_event(&mut self, persona_id: String, event: BehaviorEvent) {
591 if !self.config.enabled {
592 return;
593 }
594
595 if let Some(&enabled) = self.config.persona_learning.get(&persona_id) {
597 if !enabled {
598 return; }
600 }
601
602 let events = self.behavior_history.entry(persona_id).or_default();
603 events.push(event);
604
605 if events.len() > 1000 {
607 events.remove(0);
608 }
609 }
610
611 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); }
628
629 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 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 pub fn get_behavior_history(&self, persona_id: &str) -> Option<&Vec<BehaviorEvent>> {
669 self.behavior_history.get(persona_id)
670 }
671
672 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 if let Some(pattern) = self.analyze_persona_behavior(persona_id).await? {
687 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 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 #[test]
722 fn test_learning_config_default() {
723 let config = LearningConfig::default();
724 assert!(!config.enabled); 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 #[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 #[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 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 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 assert_eq!(result, serde_json::json!("original"));
979 }
980
981 #[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 #[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 #[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 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 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 #[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 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 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 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 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 let pattern = learner.analyze_persona_behavior("persona-1").await.unwrap();
1202 assert!(pattern.is_none()); }
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 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); }
1280
1281 #[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 #[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 #[tokio::test]
1397 async fn test_full_learning_workflow() {
1398 let drift_config = DataDriftConfig::new();
1400 let learning_config = LearningConfig {
1401 enabled: true,
1402 min_samples: 2, ..Default::default()
1404 };
1405 let engine = DriftLearningEngine::new(drift_config, learning_config).unwrap();
1406
1407 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 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 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}