mockforge_core/time_travel/
mod.rs1use chrono::{DateTime, Duration, Utc};
11use once_cell::sync::Lazy;
12use serde::{Deserialize, Serialize};
13use std::collections::{BTreeMap, HashMap};
14use std::sync::{Arc, RwLock};
15use tracing::{debug, info, warn};
16
17#[derive(Debug, Clone)]
19pub struct VirtualClock {
20 current_time: Arc<RwLock<Option<DateTime<Utc>>>>,
22 enabled: Arc<RwLock<bool>>,
24 scale_factor: Arc<RwLock<f64>>,
26 baseline_real_time: Arc<RwLock<Option<DateTime<Utc>>>>,
28}
29
30impl Default for VirtualClock {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl VirtualClock {
37 pub fn new() -> Self {
39 Self {
40 current_time: Arc::new(RwLock::new(None)),
41 enabled: Arc::new(RwLock::new(false)),
42 scale_factor: Arc::new(RwLock::new(1.0)),
43 baseline_real_time: Arc::new(RwLock::new(None)),
44 }
45 }
46
47 pub fn new_at(time: DateTime<Utc>) -> Self {
49 let clock = Self::new();
50 clock.enable_and_set(time);
51 clock
52 }
53
54 pub fn enable_and_set(&self, time: DateTime<Utc>) {
56 let mut current = self.current_time.write().unwrap();
57 *current = Some(time);
58
59 let mut enabled = self.enabled.write().unwrap();
60 *enabled = true;
61
62 let mut baseline = self.baseline_real_time.write().unwrap();
63 *baseline = Some(Utc::now());
64
65 info!("Time travel enabled at {}", time);
66 }
67
68 pub fn disable(&self) {
70 let mut enabled = self.enabled.write().unwrap();
71 *enabled = false;
72
73 let mut current = self.current_time.write().unwrap();
74 *current = None;
75
76 let mut baseline = self.baseline_real_time.write().unwrap();
77 *baseline = None;
78
79 info!("Time travel disabled, using real time");
80 }
81
82 pub fn is_enabled(&self) -> bool {
84 *self.enabled.read().unwrap()
85 }
86
87 pub fn now(&self) -> DateTime<Utc> {
89 let enabled = *self.enabled.read().unwrap();
90
91 if !enabled {
92 return Utc::now();
93 }
94
95 let current = self.current_time.read().unwrap();
96 let scale = *self.scale_factor.read().unwrap();
97
98 if let Some(virtual_time) = *current {
99 if (scale - 1.0).abs() < f64::EPSILON {
101 return virtual_time;
102 }
103
104 let baseline = self.baseline_real_time.read().unwrap();
106 if let Some(baseline_real) = *baseline {
107 let elapsed_real = Utc::now() - baseline_real;
108 let elapsed_scaled =
109 Duration::milliseconds((elapsed_real.num_milliseconds() as f64 * scale) as i64);
110 return virtual_time + elapsed_scaled;
111 }
112
113 virtual_time
114 } else {
115 Utc::now()
116 }
117 }
118
119 pub fn advance(&self, duration: Duration) {
121 let enabled = *self.enabled.read().unwrap();
122 if !enabled {
123 warn!("Cannot advance time: time travel is not enabled");
124 return;
125 }
126
127 let mut current = self.current_time.write().unwrap();
128 if let Some(time) = *current {
129 let new_time = time + duration;
130 *current = Some(new_time);
131
132 let mut baseline = self.baseline_real_time.write().unwrap();
134 *baseline = Some(Utc::now());
135
136 info!("Time advanced by {} to {}", duration, new_time);
137 }
138 }
139
140 pub fn set_scale(&self, factor: f64) {
142 if factor <= 0.0 {
143 warn!("Invalid scale factor: {}, must be positive", factor);
144 return;
145 }
146
147 let mut scale = self.scale_factor.write().unwrap();
148 *scale = factor;
149
150 let mut baseline = self.baseline_real_time.write().unwrap();
152 *baseline = Some(Utc::now());
153
154 info!("Time scale set to {}x", factor);
155 }
156
157 pub fn get_scale(&self) -> f64 {
159 *self.scale_factor.read().unwrap()
160 }
161
162 pub fn reset(&self) {
164 self.disable();
165 info!("Time travel reset to real time");
166 }
167
168 pub fn set_time(&self, time: DateTime<Utc>) {
170 let enabled = *self.enabled.read().unwrap();
171 if !enabled {
172 self.enable_and_set(time);
173 return;
174 }
175
176 let mut current = self.current_time.write().unwrap();
177 *current = Some(time);
178
179 let mut baseline = self.baseline_real_time.write().unwrap();
181 *baseline = Some(Utc::now());
182
183 info!("Virtual time set to {}", time);
184 }
185
186 pub fn status(&self) -> TimeTravelStatus {
188 TimeTravelStatus {
189 enabled: self.is_enabled(),
190 current_time: if self.is_enabled() {
191 Some(self.now())
192 } else {
193 None
194 },
195 scale_factor: self.get_scale(),
196 real_time: Utc::now(),
197 }
198 }
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct TimeTravelStatus {
204 pub enabled: bool,
206 pub current_time: Option<DateTime<Utc>>,
208 pub scale_factor: f64,
210 pub real_time: DateTime<Utc>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct TimeTravelConfig {
217 #[serde(default)]
219 pub enabled: bool,
220 pub initial_time: Option<DateTime<Utc>>,
222 #[serde(default = "default_scale")]
224 pub scale_factor: f64,
225 #[serde(default = "default_true")]
227 pub enable_scheduling: bool,
228}
229
230fn default_scale() -> f64 {
231 1.0
232}
233
234fn default_true() -> bool {
235 true
236}
237
238impl Default for TimeTravelConfig {
239 fn default() -> Self {
240 Self {
241 enabled: false,
242 initial_time: None,
243 scale_factor: 1.0,
244 enable_scheduling: true,
245 }
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct ResponseScheduler {
252 clock: Arc<VirtualClock>,
254 scheduled: Arc<RwLock<BTreeMap<DateTime<Utc>, Vec<ScheduledResponse>>>>,
256 named_schedules: Arc<RwLock<HashMap<String, String>>>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct ScheduledResponse {
263 pub id: String,
265 pub trigger_time: DateTime<Utc>,
267 pub body: serde_json::Value,
269 #[serde(default = "default_status")]
271 pub status: u16,
272 #[serde(default)]
274 pub headers: HashMap<String, String>,
275 pub name: Option<String>,
277 #[serde(default)]
279 pub repeat: Option<RepeatConfig>,
280}
281
282fn default_status() -> u16 {
283 200
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct RepeatConfig {
289 pub interval: Duration,
291 pub max_count: Option<usize>,
293}
294
295impl ResponseScheduler {
296 pub fn new(clock: Arc<VirtualClock>) -> Self {
298 Self {
299 clock,
300 scheduled: Arc::new(RwLock::new(BTreeMap::new())),
301 named_schedules: Arc::new(RwLock::new(HashMap::new())),
302 }
303 }
304
305 pub fn schedule(&self, response: ScheduledResponse) -> Result<String, String> {
307 let id = if response.id.is_empty() {
308 uuid::Uuid::new_v4().to_string()
309 } else {
310 response.id.clone()
311 };
312
313 let mut scheduled = self.scheduled.write().unwrap();
314 scheduled.entry(response.trigger_time).or_default().push(response.clone());
315
316 if let Some(name) = &response.name {
317 let mut named = self.named_schedules.write().unwrap();
318 named.insert(name.clone(), id.clone());
319 }
320
321 info!("Scheduled response {} for {}", id, response.trigger_time);
322 Ok(id)
323 }
324
325 pub fn get_due_responses(&self) -> Vec<ScheduledResponse> {
327 let now = self.clock.now();
328 let mut scheduled = self.scheduled.write().unwrap();
329 let mut due = Vec::new();
330
331 let times_to_process: Vec<DateTime<Utc>> =
333 scheduled.range(..=now).map(|(time, _)| *time).collect();
334
335 for time in times_to_process {
336 if let Some(responses) = scheduled.remove(&time) {
337 for response in responses {
338 due.push(response.clone());
339
340 if let Some(repeat_config) = &response.repeat {
342 let next_time = time + repeat_config.interval;
343
344 let should_repeat = if let Some(max) = repeat_config.max_count {
346 max > 1
348 } else {
349 true
350 };
351
352 if should_repeat {
353 let mut next_response = response.clone();
354 next_response.trigger_time = next_time;
355 if let Some(ref mut repeat) = next_response.repeat {
356 if let Some(ref mut count) = repeat.max_count {
357 *count -= 1;
358 }
359 }
360
361 scheduled.entry(next_time).or_default().push(next_response);
362 }
363 }
364 }
365 }
366 }
367
368 debug!("Found {} due responses at {}", due.len(), now);
369 due
370 }
371
372 pub fn cancel(&self, id: &str) -> bool {
374 let mut scheduled = self.scheduled.write().unwrap();
375
376 for responses in scheduled.values_mut() {
377 if let Some(pos) = responses.iter().position(|r| r.id == id) {
378 responses.remove(pos);
379 info!("Cancelled scheduled response {}", id);
380 return true;
381 }
382 }
383
384 false
385 }
386
387 pub fn clear_all(&self) {
389 let mut scheduled = self.scheduled.write().unwrap();
390 scheduled.clear();
391
392 let mut named = self.named_schedules.write().unwrap();
393 named.clear();
394
395 info!("Cleared all scheduled responses");
396 }
397
398 pub fn list_scheduled(&self) -> Vec<ScheduledResponse> {
400 let scheduled = self.scheduled.read().unwrap();
401 scheduled.values().flat_map(|v| v.iter().cloned()).collect()
402 }
403
404 pub fn count(&self) -> usize {
406 let scheduled = self.scheduled.read().unwrap();
407 scheduled.values().map(|v| v.len()).sum()
408 }
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct TimeScenario {
414 pub name: String,
416 pub enabled: bool,
418 pub current_time: Option<DateTime<Utc>>,
420 pub scale_factor: f64,
422 #[serde(default)]
424 pub scheduled_responses: Vec<ScheduledResponse>,
425 pub created_at: DateTime<Utc>,
427 #[serde(default)]
429 pub description: Option<String>,
430}
431
432impl TimeScenario {
433 pub fn from_manager(manager: &TimeTravelManager, name: String) -> Self {
435 let status = manager.clock().status();
436 let scheduled = manager.scheduler().list_scheduled();
437
438 Self {
439 name,
440 enabled: status.enabled,
441 current_time: status.current_time,
442 scale_factor: status.scale_factor,
443 scheduled_responses: scheduled,
444 created_at: Utc::now(),
445 description: None,
446 }
447 }
448
449 pub fn apply_to_manager(&self, manager: &TimeTravelManager) {
451 if self.enabled {
452 if let Some(time) = self.current_time {
453 manager.enable_and_set(time);
454 } else {
455 manager.enable_and_set(Utc::now());
456 }
457 manager.set_scale(self.scale_factor);
458 } else {
459 manager.disable();
460 }
461
462 manager.scheduler().clear_all();
464 for response in &self.scheduled_responses {
465 let _ = manager.scheduler().schedule(response.clone());
466 }
467 }
468}
469
470static GLOBAL_CLOCK_REGISTRY: Lazy<Arc<RwLock<Option<Arc<VirtualClock>>>>> =
477 Lazy::new(|| Arc::new(RwLock::new(None)));
478
479pub fn register_global_clock(clock: Arc<VirtualClock>) {
484 let mut registry = GLOBAL_CLOCK_REGISTRY.write().unwrap();
485 *registry = Some(clock);
486 info!("Virtual clock registered globally");
487}
488
489pub fn unregister_global_clock() {
493 let mut registry = GLOBAL_CLOCK_REGISTRY.write().unwrap();
494 *registry = None;
495 info!("Virtual clock unregistered globally");
496}
497
498pub fn get_global_clock() -> Option<Arc<VirtualClock>> {
502 let registry = GLOBAL_CLOCK_REGISTRY.read().unwrap();
503 registry.clone()
504}
505
506pub fn now() -> DateTime<Utc> {
513 if let Some(clock) = get_global_clock() {
514 clock.now()
515 } else {
516 Utc::now()
517 }
518}
519
520pub fn is_time_travel_enabled() -> bool {
524 if let Some(clock) = get_global_clock() {
525 clock.is_enabled()
526 } else {
527 false
528 }
529}
530
531pub struct TimeTravelManager {
533 clock: Arc<VirtualClock>,
535 scheduler: Arc<ResponseScheduler>,
537 cron_scheduler: Arc<cron::CronScheduler>,
539}
540
541impl TimeTravelManager {
542 pub fn new(config: TimeTravelConfig) -> Self {
547 let clock = Arc::new(VirtualClock::new());
548
549 if config.enabled {
550 if let Some(initial_time) = config.initial_time {
551 clock.enable_and_set(initial_time);
552 } else {
553 clock.enable_and_set(Utc::now());
554 }
555 clock.set_scale(config.scale_factor);
556 register_global_clock(clock.clone());
558 }
559
560 let scheduler = Arc::new(ResponseScheduler::new(clock.clone()));
561 let cron_scheduler = Arc::new(cron::CronScheduler::new(clock.clone()));
562
563 Self {
564 clock,
565 scheduler,
566 cron_scheduler,
567 }
568 }
569
570 pub fn clock(&self) -> Arc<VirtualClock> {
572 self.clock.clone()
573 }
574
575 pub fn scheduler(&self) -> Arc<ResponseScheduler> {
577 self.scheduler.clone()
578 }
579
580 pub fn cron_scheduler(&self) -> Arc<cron::CronScheduler> {
582 self.cron_scheduler.clone()
583 }
584
585 pub fn now(&self) -> DateTime<Utc> {
587 self.clock.now()
588 }
589
590 pub fn save_scenario(&self, name: String) -> TimeScenario {
592 TimeScenario::from_manager(self, name)
593 }
594
595 pub fn load_scenario(&self, scenario: &TimeScenario) {
597 scenario.apply_to_manager(self);
598 }
599
600 pub fn enable_and_set(&self, time: DateTime<Utc>) {
604 self.clock.enable_and_set(time);
605 register_global_clock(self.clock.clone());
606 }
607
608 pub fn disable(&self) {
612 self.clock.disable();
613 unregister_global_clock();
614 }
615
616 pub fn advance(&self, duration: Duration) {
620 self.clock.advance(duration);
621 }
622
623 pub fn set_time(&self, time: DateTime<Utc>) {
627 self.clock.set_time(time);
628 if self.clock.is_enabled() {
629 register_global_clock(self.clock.clone());
630 }
631 }
632
633 pub fn set_scale(&self, factor: f64) {
637 self.clock.set_scale(factor);
638 }
639}
640
641impl Drop for TimeTravelManager {
642 fn drop(&mut self) {
643 unregister_global_clock();
645 }
646}
647
648#[cfg(test)]
649mod tests {
650 use super::*;
651
652 #[test]
653 fn test_virtual_clock_creation() {
654 let clock = VirtualClock::new();
655 assert!(!clock.is_enabled());
656 }
657
658 #[test]
659 fn test_virtual_clock_enable() {
660 let clock = VirtualClock::new();
661 let test_time = Utc::now();
662 clock.enable_and_set(test_time);
663
664 assert!(clock.is_enabled());
665 let now = clock.now();
666 assert!((now - test_time).num_seconds().abs() < 1);
667 }
668
669 #[test]
670 fn test_virtual_clock_advance() {
671 let clock = VirtualClock::new();
672 let test_time = Utc::now();
673 clock.enable_and_set(test_time);
674
675 clock.advance(Duration::hours(2));
676 let now = clock.now();
677
678 assert!((now - test_time - Duration::hours(2)).num_seconds().abs() < 1);
679 }
680
681 #[test]
682 fn test_virtual_clock_scale() {
683 let clock = VirtualClock::new();
684 clock.set_scale(2.0);
685 assert_eq!(clock.get_scale(), 2.0);
686 }
687
688 #[test]
689 fn test_response_scheduler() {
690 let clock = Arc::new(VirtualClock::new());
691 let test_time = Utc::now();
692 clock.enable_and_set(test_time);
693
694 let scheduler = ResponseScheduler::new(clock.clone());
695
696 let response = ScheduledResponse {
697 id: "test-1".to_string(),
698 trigger_time: test_time + Duration::seconds(10),
699 body: serde_json::json!({"message": "Hello"}),
700 status: 200,
701 headers: HashMap::new(),
702 name: Some("test".to_string()),
703 repeat: None,
704 };
705
706 let id = scheduler.schedule(response).unwrap();
707 assert_eq!(id, "test-1");
708 assert_eq!(scheduler.count(), 1);
709 }
710
711 #[test]
712 fn test_scheduled_response_triggering() {
713 let clock = Arc::new(VirtualClock::new());
714 let test_time = Utc::now();
715 clock.enable_and_set(test_time);
716
717 let scheduler = ResponseScheduler::new(clock.clone());
718
719 let response = ScheduledResponse {
720 id: "test-1".to_string(),
721 trigger_time: test_time + Duration::seconds(10),
722 body: serde_json::json!({"message": "Hello"}),
723 status: 200,
724 headers: HashMap::new(),
725 name: None,
726 repeat: None,
727 };
728
729 scheduler.schedule(response).unwrap();
730
731 let due = scheduler.get_due_responses();
733 assert_eq!(due.len(), 0);
734
735 clock.advance(Duration::seconds(15));
737
738 let due = scheduler.get_due_responses();
740 assert_eq!(due.len(), 1);
741 }
742
743 #[test]
744 fn test_time_travel_config() {
745 let config = TimeTravelConfig::default();
746 assert!(!config.enabled);
747 assert_eq!(config.scale_factor, 1.0);
748 assert!(config.enable_scheduling);
749 }
750
751 #[test]
752 fn test_time_travel_manager() {
753 let config = TimeTravelConfig {
754 enabled: true,
755 initial_time: Some(Utc::now()),
756 scale_factor: 1.0,
757 enable_scheduling: true,
758 };
759
760 let manager = TimeTravelManager::new(config);
761 assert!(manager.clock().is_enabled());
762 }
763
764 #[test]
765 fn test_one_month_later_scenario() {
766 let clock = Arc::new(VirtualClock::new());
767 let initial_time = Utc::now();
768 clock.enable_and_set(initial_time);
769
770 clock.advance(Duration::days(30));
772
773 let final_time = clock.now();
774 let elapsed = final_time - initial_time;
775
776 assert!(elapsed.num_days() >= 29 && elapsed.num_days() <= 31);
778 }
779
780 #[test]
781 fn test_scenario_save_and_load() {
782 let config = TimeTravelConfig {
783 enabled: true,
784 initial_time: Some(Utc::now()),
785 scale_factor: 2.0,
786 enable_scheduling: true,
787 };
788
789 let manager = TimeTravelManager::new(config);
790
791 manager.clock().advance(Duration::hours(24));
793
794 let scenario = manager.save_scenario("test-scenario".to_string());
796 assert_eq!(scenario.name, "test-scenario");
797 assert!(scenario.enabled);
798 assert_eq!(scenario.scale_factor, 2.0);
799 assert!(scenario.current_time.is_some());
800
801 let new_config = TimeTravelConfig::default();
803 let new_manager = TimeTravelManager::new(new_config);
804
805 new_manager.load_scenario(&scenario);
807
808 assert!(new_manager.clock().is_enabled());
810 assert_eq!(new_manager.clock().get_scale(), 2.0);
811 if let Some(saved_time) = scenario.current_time {
812 let loaded_time = new_manager.clock().now();
813 assert!((loaded_time - saved_time).num_seconds().abs() < 1);
815 }
816 }
817
818 #[test]
819 fn test_duration_parsing_month_year() {
820 let clock = Arc::new(VirtualClock::new());
822 let initial_time = Utc::now();
823 clock.enable_and_set(initial_time);
824
825 clock.advance(Duration::days(30));
827 let after_month = clock.now();
828 let month_elapsed = after_month - initial_time;
829 assert!(month_elapsed.num_days() >= 29 && month_elapsed.num_days() <= 31);
830
831 clock.set_time(initial_time);
833 clock.advance(Duration::days(365));
834 let after_year = clock.now();
835 let year_elapsed = after_year - initial_time;
836 assert!(year_elapsed.num_days() >= 364 && year_elapsed.num_days() <= 366);
837 }
838}
839
840pub mod cron;
842
843pub use cron::{CronJob, CronJobAction, CronScheduler};