mockforge_core/reality_continuum/
engine.rs1use super::blender::ResponseBlender;
7use super::config::{ContinuumConfig, ContinuumRule, TransitionMode};
8use super::schedule::TimeSchedule;
9use chrono::{DateTime, Utc};
10use std::collections::HashMap;
11use std::sync::Arc;
12use tokio::sync::RwLock;
13use tracing::{debug, info};
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 if let Some(ref group) = rule.group {
87 if let Some(&group_ratio) = config.groups.get(group) {
88 debug!(
89 "Using group override for {} (group {}): {}",
90 path, group, group_ratio
91 );
92 return group_ratio;
93 }
94 }
95 debug!("Using route rule for {}: {}", path, rule.ratio);
97 return rule.ratio;
98 }
99 }
100
101 if config.transition_mode == TransitionMode::TimeBased
103 || config.transition_mode == TransitionMode::Scheduled
104 {
105 if let Some(ref schedule) = config.time_schedule {
106 let current_time = self.get_current_time().await;
107 let ratio = schedule.calculate_ratio(current_time);
108 debug!("Using time-based ratio for {}: {} (time: {})", path, ratio, current_time);
109 return ratio;
110 }
111 }
112
113 debug!("Using default ratio for {}: {}", path, config.default_ratio);
115 config.default_ratio
116 }
117
118 pub async fn set_blend_ratio(&self, path: &str, ratio: f64) {
120 let ratio = ratio.clamp(0.0, 1.0);
121 let mut overrides = self.manual_overrides.write().await;
122 overrides.insert(path.to_string(), ratio);
123 info!("Set manual blend ratio for {}: {}", path, ratio);
124 }
125
126 pub async fn remove_blend_ratio(&self, path: &str) {
128 let mut overrides = self.manual_overrides.write().await;
129 if overrides.remove(path).is_some() {
130 info!("Removed manual blend ratio override for {}", path);
131 }
132 }
133
134 pub async fn set_group_ratio(&self, group: &str, ratio: f64) {
136 let ratio = ratio.clamp(0.0, 1.0);
137 let mut config = self.config.write().await;
138 config.groups.insert(group.to_string(), ratio);
139 info!("Set group blend ratio for {}: {}", group, ratio);
140 }
141
142 pub async fn update_from_time(&self, _time: DateTime<Utc>) {
146 debug!("Continuum engine updated from time: {}", _time);
149 }
150
151 pub fn blender(&self) -> &ResponseBlender {
153 &self.blender
154 }
155
156 pub async fn get_config(&self) -> ContinuumConfig {
158 self.config.read().await.clone()
159 }
160
161 pub async fn update_config(&self, config: ContinuumConfig) {
163 let mut current_config = self.config.write().await;
164 *current_config = config;
165 info!("Continuum configuration updated");
166 }
167
168 pub async fn is_enabled(&self) -> bool {
170 self.config.read().await.enabled
171 }
172
173 pub async fn set_enabled(&self, enabled: bool) {
175 let mut config = self.config.write().await;
176 config.enabled = enabled;
177 if enabled {
178 info!("Reality Continuum enabled");
179 } else {
180 info!("Reality Continuum disabled");
181 }
182 }
183
184 pub async fn get_time_schedule(&self) -> Option<TimeSchedule> {
186 self.config.read().await.time_schedule.clone()
187 }
188
189 pub async fn set_time_schedule(&self, schedule: TimeSchedule) {
191 let mut config = self.config.write().await;
192 config.time_schedule = Some(schedule);
193 config.transition_mode = TransitionMode::TimeBased;
194 info!("Time schedule updated");
195 }
196
197 async fn get_current_time(&self) -> DateTime<Utc> {
199 if let Some(ref clock) = self.virtual_clock {
200 clock.now()
201 } else {
202 Utc::now()
203 }
204 }
205
206 pub async fn advance_ratio(&self, increment: f64) {
210 let mut config = self.config.write().await;
211 let new_ratio = (config.default_ratio + increment).clamp(0.0, 1.0);
212 config.default_ratio = new_ratio;
213 info!("Advanced default blend ratio to {}", new_ratio);
214 }
215
216 pub async fn get_manual_overrides(&self) -> HashMap<String, f64> {
218 self.manual_overrides.read().await.clone()
219 }
220
221 pub async fn clear_manual_overrides(&self) {
223 let mut overrides = self.manual_overrides.write().await;
224 overrides.clear();
225 info!("Cleared all manual blend ratio overrides");
226 }
227}
228
229impl Default for RealityContinuumEngine {
230 fn default() -> Self {
231 Self::new(ContinuumConfig::default())
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[tokio::test]
240 async fn test_get_blend_ratio_default() {
241 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
242 let ratio = engine.get_blend_ratio("/api/test").await;
243 assert_eq!(ratio, 0.0); }
245
246 #[tokio::test]
247 async fn test_set_get_blend_ratio() {
248 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
249 engine.set_blend_ratio("/api/test", 0.75).await;
250 let ratio = engine.get_blend_ratio("/api/test").await;
251 assert_eq!(ratio, 0.75);
252 }
253
254 #[tokio::test]
255 async fn test_route_rule_matching() {
256 let mut config = ContinuumConfig::default();
257 config.routes.push(ContinuumRule::new("/api/users/*".to_string(), 0.5));
258 let engine = RealityContinuumEngine::new(config);
259
260 let ratio = engine.get_blend_ratio("/api/users/123").await;
261 assert_eq!(ratio, 0.5);
262 }
263
264 #[tokio::test]
265 async fn test_group_ratio() {
266 let mut config = ContinuumConfig::default();
267 config.groups.insert("api-v1".to_string(), 0.3);
268 config.routes.push(
269 ContinuumRule::new("/api/users/*".to_string(), 0.5).with_group("api-v1".to_string()),
270 );
271 let engine = RealityContinuumEngine::new(config);
272
273 let ratio = engine.get_blend_ratio("/api/users/123").await;
275 assert_eq!(ratio, 0.3);
276 }
277
278 #[tokio::test]
279 async fn test_time_based_ratio() {
280 let start = Utc::now();
281 let end = start + chrono::Duration::days(30);
282 let schedule = TimeSchedule::new(start, end, 0.0, 1.0);
283
284 let mut config = ContinuumConfig::default();
285 config.transition_mode = TransitionMode::TimeBased;
286 config.time_schedule = Some(schedule);
287 let engine = RealityContinuumEngine::new(config);
288
289 let ratio = engine.get_blend_ratio("/api/test").await;
291 assert!(ratio < 0.1);
293 }
294
295 #[tokio::test]
296 async fn test_remove_blend_ratio() {
297 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
298 engine.set_blend_ratio("/api/test", 0.75).await;
299 assert_eq!(engine.get_blend_ratio("/api/test").await, 0.75);
300
301 engine.remove_blend_ratio("/api/test").await;
302 assert_eq!(engine.get_blend_ratio("/api/test").await, 0.0); }
304
305 #[tokio::test]
306 async fn test_group_ratio_override() {
307 let mut config = ContinuumConfig::default();
308 config.groups.insert("api-v1".to_string(), 0.8);
309 config.routes.push(
310 ContinuumRule::new("/api/users/*".to_string(), 0.5).with_group("api-v1".to_string()),
311 );
312 let engine = RealityContinuumEngine::new(config);
313
314 let ratio = engine.get_blend_ratio("/api/users/123").await;
316 assert_eq!(ratio, 0.8);
317 }
318
319 #[tokio::test]
320 async fn test_enable_disable() {
321 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
322 assert!(!engine.is_enabled().await);
323
324 engine.set_enabled(true).await;
325 assert!(engine.is_enabled().await);
326
327 engine.set_enabled(false).await;
328 assert!(!engine.is_enabled().await);
329 }
330
331 #[tokio::test]
332 async fn test_advance_ratio() {
333 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
334 assert_eq!(engine.get_config().await.default_ratio, 0.0);
335
336 engine.advance_ratio(0.2).await;
337 assert_eq!(engine.get_config().await.default_ratio, 0.2);
338
339 engine.advance_ratio(0.5).await;
340 assert_eq!(engine.get_config().await.default_ratio, 0.7);
341
342 engine.advance_ratio(0.5).await;
344 assert_eq!(engine.get_config().await.default_ratio, 1.0);
345 }
346
347 #[tokio::test]
348 async fn test_clear_manual_overrides() {
349 let engine = RealityContinuumEngine::new(ContinuumConfig::default());
350 engine.set_blend_ratio("/api/test1", 0.5).await;
351 engine.set_blend_ratio("/api/test2", 0.7).await;
352
353 let overrides = engine.get_manual_overrides().await;
354 assert_eq!(overrides.len(), 2);
355
356 engine.clear_manual_overrides().await;
357 let overrides = engine.get_manual_overrides().await;
358 assert_eq!(overrides.len(), 0);
359 }
360}