mockforge_core/reality_continuum/
engine.rs1use crate::reality_continuum::{
7 ContinuumConfig, ContinuumRule, ResponseBlender, TimeSchedule, TransitionMode,
8};
9use chrono::{DateTime, Utc};
10use std::collections::HashMap;
11use std::sync::Arc;
12use tokio::sync::RwLock;
13use tracing::{debug, info, warn};
14
15#[derive(Debug, Clone)]
20pub struct RealityContinuumEngine {
21 config: Arc<RwLock<ContinuumConfig>>,
23 blender: ResponseBlender,
25 virtual_clock: Option<Arc<crate::time_travel::VirtualClock>>,
27 manual_overrides: Arc<RwLock<HashMap<String, f64>>>,
29}
30
31impl RealityContinuumEngine {
32 pub fn new(config: ContinuumConfig) -> Self {
34 let blender = ResponseBlender::new(config.merge_strategy);
35 Self {
36 config: Arc::new(RwLock::new(config)),
37 blender,
38 virtual_clock: None,
39 manual_overrides: Arc::new(RwLock::new(HashMap::new())),
40 }
41 }
42
43 pub fn with_virtual_clock(
45 config: ContinuumConfig,
46 virtual_clock: Arc<crate::time_travel::VirtualClock>,
47 ) -> Self {
48 let blender = ResponseBlender::new(config.merge_strategy);
49 Self {
50 config: Arc::new(RwLock::new(config)),
51 blender,
52 virtual_clock: Some(virtual_clock),
53 manual_overrides: Arc::new(RwLock::new(HashMap::new())),
54 }
55 }
56
57 pub fn set_virtual_clock(&mut self, virtual_clock: Arc<crate::time_travel::VirtualClock>) {
59 self.virtual_clock = Some(virtual_clock);
60 }
61
62 pub async fn get_blend_ratio(&self, path: &str) -> f64 {
71 {
73 let overrides = self.manual_overrides.read().await;
74 if let Some(&ratio) = overrides.get(path) {
75 debug!("Using manual override for {}: {}", path, ratio);
76 return ratio;
77 }
78 }
79
80 let config = self.config.read().await;
81
82 for rule in &config.routes {
84 if rule.matches_path(path) {
85 debug!("Using route rule for {}: {}", path, rule.ratio);
86 return rule.ratio;
87 }
88 }
89
90 for rule in &config.routes {
94 if let Some(ref group) = rule.group {
95 if rule.matches_path(path) {
96 if let Some(&group_ratio) = config.groups.get(group) {
97 debug!(
98 "Using group override for {} (group {}): {}",
99 path, group, group_ratio
100 );
101 return group_ratio;
102 }
103 }
104 }
105 }
106
107 if config.transition_mode == TransitionMode::TimeBased
109 || config.transition_mode == TransitionMode::Scheduled
110 {
111 if let Some(ref schedule) = config.time_schedule {
112 let current_time = self.get_current_time().await;
113 let ratio = schedule.calculate_ratio(current_time);
114 debug!("Using time-based ratio for {}: {} (time: {})", path, ratio, current_time);
115 return ratio;
116 }
117 }
118
119 debug!("Using default ratio for {}: {}", path, config.default_ratio);
121 config.default_ratio
122 }
123
124 pub async fn set_blend_ratio(&self, path: &str, ratio: f64) {
126 let ratio = ratio.clamp(0.0, 1.0);
127 let mut overrides = self.manual_overrides.write().await;
128 overrides.insert(path.to_string(), ratio);
129 info!("Set manual blend ratio for {}: {}", path, ratio);
130 }
131
132 pub async fn remove_blend_ratio(&self, path: &str) {
134 let mut overrides = self.manual_overrides.write().await;
135 if overrides.remove(path).is_some() {
136 info!("Removed manual blend ratio override for {}", path);
137 }
138 }
139
140 pub async fn set_group_ratio(&self, group: &str, ratio: f64) {
142 let ratio = ratio.clamp(0.0, 1.0);
143 let mut config = self.config.write().await;
144 config.groups.insert(group.to_string(), ratio);
145 info!("Set group blend ratio for {}: {}", group, ratio);
146 }
147
148 pub async fn update_from_time(&self, _time: DateTime<Utc>) {
152 debug!("Continuum engine updated from time: {}", _time);
155 }
156
157 pub fn blender(&self) -> &ResponseBlender {
159 &self.blender
160 }
161
162 pub async fn get_config(&self) -> ContinuumConfig {
164 self.config.read().await.clone()
165 }
166
167 pub async fn update_config(&self, config: ContinuumConfig) {
169 let mut current_config = self.config.write().await;
170 *current_config = config;
171 info!("Continuum configuration updated");
172 }
173
174 pub async fn is_enabled(&self) -> bool {
176 self.config.read().await.enabled
177 }
178
179 pub async fn set_enabled(&self, enabled: bool) {
181 let mut config = self.config.write().await;
182 config.enabled = enabled;
183 if enabled {
184 info!("Reality Continuum enabled");
185 } else {
186 info!("Reality Continuum disabled");
187 }
188 }
189
190 pub async fn get_time_schedule(&self) -> Option<TimeSchedule> {
192 self.config.read().await.time_schedule.clone()
193 }
194
195 pub async fn set_time_schedule(&self, schedule: TimeSchedule) {
197 let mut config = self.config.write().await;
198 config.time_schedule = Some(schedule);
199 config.transition_mode = TransitionMode::TimeBased;
200 info!("Time schedule updated");
201 }
202
203 async fn get_current_time(&self) -> DateTime<Utc> {
205 if let Some(ref clock) = self.virtual_clock {
206 clock.now()
207 } else {
208 Utc::now()
209 }
210 }
211
212 pub async fn advance_ratio(&self, increment: f64) {
216 let mut config = self.config.write().await;
217 let new_ratio = (config.default_ratio + increment).clamp(0.0, 1.0);
218 config.default_ratio = new_ratio;
219 info!("Advanced default blend ratio to {}", new_ratio);
220 }
221
222 pub async fn get_manual_overrides(&self) -> HashMap<String, f64> {
224 self.manual_overrides.read().await.clone()
225 }
226
227 pub async fn clear_manual_overrides(&self) {
229 let mut overrides = self.manual_overrides.write().await;
230 overrides.clear();
231 info!("Cleared all manual blend ratio overrides");
232 }
233}
234
235impl Default for RealityContinuumEngine {
236 fn default() -> Self {
237 Self::new(ContinuumConfig::default())
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[tokio::test]
246 async fn test_get_blend_ratio_default() {
247 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
248 let ratio = engine.get_blend_ratio("/api/test").await;
249 assert_eq!(ratio, 0.0); }
251
252 #[tokio::test]
253 async fn test_set_get_blend_ratio() {
254 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
255 engine.set_blend_ratio("/api/test", 0.75).await;
256 let ratio = engine.get_blend_ratio("/api/test").await;
257 assert_eq!(ratio, 0.75);
258 }
259
260 #[tokio::test]
261 async fn test_route_rule_matching() {
262 let mut config = ContinuumConfig::default();
263 config.routes.push(ContinuumRule::new("/api/users/*".to_string(), 0.5));
264 let engine = RealityContinuumEngine::new(config);
265
266 let ratio = engine.get_blend_ratio("/api/users/123").await;
267 assert_eq!(ratio, 0.5);
268 }
269
270 #[tokio::test]
271 async fn test_group_ratio() {
272 let mut config = ContinuumConfig::default();
273 config.groups.insert("api-v1".to_string(), 0.3);
274 config.routes.push(
275 ContinuumRule::new("/api/users/*".to_string(), 0.5).with_group("api-v1".to_string()),
276 );
277 let engine = RealityContinuumEngine::new(config);
278
279 let ratio = engine.get_blend_ratio("/api/users/123").await;
281 assert_eq!(ratio, 0.3);
282 }
283
284 #[tokio::test]
285 async fn test_time_based_ratio() {
286 let start = Utc::now();
287 let end = start + chrono::Duration::days(30);
288 let schedule = TimeSchedule::new(start, end, 0.0, 1.0);
289
290 let mut config = ContinuumConfig::default();
291 config.transition_mode = TransitionMode::TimeBased;
292 config.time_schedule = Some(schedule);
293 let engine = RealityContinuumEngine::new(config);
294
295 let ratio = engine.get_blend_ratio("/api/test").await;
297 assert!(ratio < 0.1);
299 }
300
301 #[tokio::test]
302 async fn test_remove_blend_ratio() {
303 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
304 engine.set_blend_ratio("/api/test", 0.75).await;
305 assert_eq!(engine.get_blend_ratio("/api/test").await, 0.75);
306
307 engine.remove_blend_ratio("/api/test").await;
308 assert_eq!(engine.get_blend_ratio("/api/test").await, 0.0); }
310
311 #[tokio::test]
312 async fn test_group_ratio_override() {
313 let mut config = ContinuumConfig::default();
314 config.groups.insert("api-v1".to_string(), 0.8);
315 config.routes.push(
316 ContinuumRule::new("/api/users/*".to_string(), 0.5).with_group("api-v1".to_string()),
317 );
318 let engine = RealityContinuumEngine::new(config);
319
320 let ratio = engine.get_blend_ratio("/api/users/123").await;
322 assert_eq!(ratio, 0.8);
323 }
324
325 #[tokio::test]
326 async fn test_enable_disable() {
327 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
328 assert!(!engine.is_enabled().await);
329
330 engine.set_enabled(true).await;
331 assert!(engine.is_enabled().await);
332
333 engine.set_enabled(false).await;
334 assert!(!engine.is_enabled().await);
335 }
336
337 #[tokio::test]
338 async fn test_advance_ratio() {
339 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
340 assert_eq!(engine.get_config().await.default_ratio, 0.0);
341
342 engine.advance_ratio(0.2).await;
343 assert_eq!(engine.get_config().await.default_ratio, 0.2);
344
345 engine.advance_ratio(0.5).await;
346 assert_eq!(engine.get_config().await.default_ratio, 0.7);
347
348 engine.advance_ratio(0.5).await;
350 assert_eq!(engine.get_config().await.default_ratio, 1.0);
351 }
352
353 #[tokio::test]
354 async fn test_clear_manual_overrides() {
355 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
356 engine.set_blend_ratio("/api/test1", 0.5).await;
357 engine.set_blend_ratio("/api/test2", 0.7).await;
358
359 let overrides = engine.get_manual_overrides().await;
360 assert_eq!(overrides.len(), 2);
361
362 engine.clear_manual_overrides().await;
363 let overrides = engine.get_manual_overrides().await;
364 assert_eq!(overrides.len(), 0);
365 }
366}