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