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#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct TimeTravelConfig {
218 #[serde(default)]
220 pub enabled: bool,
221 #[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
223 pub initial_time: Option<DateTime<Utc>>,
224 #[serde(default = "default_scale")]
226 pub scale_factor: f64,
227 #[serde(default = "default_true")]
229 pub enable_scheduling: bool,
230}
231
232fn default_scale() -> f64 {
233 1.0
234}
235
236fn default_true() -> bool {
237 true
238}
239
240impl Default for TimeTravelConfig {
241 fn default() -> Self {
242 Self {
243 enabled: false,
244 initial_time: None,
245 scale_factor: 1.0,
246 enable_scheduling: true,
247 }
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct ResponseScheduler {
254 clock: Arc<VirtualClock>,
256 scheduled: Arc<RwLock<BTreeMap<DateTime<Utc>, Vec<ScheduledResponse>>>>,
258 named_schedules: Arc<RwLock<HashMap<String, String>>>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct ScheduledResponse {
265 pub id: String,
267 pub trigger_time: DateTime<Utc>,
269 pub body: serde_json::Value,
271 #[serde(default = "default_status")]
273 pub status: u16,
274 #[serde(default)]
276 pub headers: HashMap<String, String>,
277 pub name: Option<String>,
279 #[serde(default)]
281 pub repeat: Option<RepeatConfig>,
282}
283
284fn default_status() -> u16 {
285 200
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct RepeatConfig {
291 pub interval: Duration,
293 pub max_count: Option<usize>,
295}
296
297impl ResponseScheduler {
298 pub fn new(clock: Arc<VirtualClock>) -> Self {
300 Self {
301 clock,
302 scheduled: Arc::new(RwLock::new(BTreeMap::new())),
303 named_schedules: Arc::new(RwLock::new(HashMap::new())),
304 }
305 }
306
307 pub fn schedule(&self, response: ScheduledResponse) -> Result<String, String> {
309 let id = if response.id.is_empty() {
310 uuid::Uuid::new_v4().to_string()
311 } else {
312 response.id.clone()
313 };
314
315 let mut scheduled = self.scheduled.write().unwrap();
316 scheduled.entry(response.trigger_time).or_default().push(response.clone());
317
318 if let Some(name) = &response.name {
319 let mut named = self.named_schedules.write().unwrap();
320 named.insert(name.clone(), id.clone());
321 }
322
323 info!("Scheduled response {} for {}", id, response.trigger_time);
324 Ok(id)
325 }
326
327 pub fn get_due_responses(&self) -> Vec<ScheduledResponse> {
329 let now = self.clock.now();
330 let mut scheduled = self.scheduled.write().unwrap();
331 let mut due = Vec::new();
332
333 let times_to_process: Vec<DateTime<Utc>> =
335 scheduled.range(..=now).map(|(time, _)| *time).collect();
336
337 for time in times_to_process {
338 if let Some(responses) = scheduled.remove(&time) {
339 for response in responses {
340 due.push(response.clone());
341
342 if let Some(repeat_config) = &response.repeat {
344 let next_time = time + repeat_config.interval;
345
346 let should_repeat = if let Some(max) = repeat_config.max_count {
348 max > 1
350 } else {
351 true
352 };
353
354 if should_repeat {
355 let mut next_response = response.clone();
356 next_response.trigger_time = next_time;
357 if let Some(ref mut repeat) = next_response.repeat {
358 if let Some(ref mut count) = repeat.max_count {
359 *count -= 1;
360 }
361 }
362
363 scheduled.entry(next_time).or_default().push(next_response);
364 }
365 }
366 }
367 }
368 }
369
370 debug!("Found {} due responses at {}", due.len(), now);
371 due
372 }
373
374 pub fn cancel(&self, id: &str) -> bool {
376 let mut scheduled = self.scheduled.write().unwrap();
377
378 for responses in scheduled.values_mut() {
379 if let Some(pos) = responses.iter().position(|r| r.id == id) {
380 responses.remove(pos);
381 info!("Cancelled scheduled response {}", id);
382 return true;
383 }
384 }
385
386 false
387 }
388
389 pub fn clear_all(&self) {
391 let mut scheduled = self.scheduled.write().unwrap();
392 scheduled.clear();
393
394 let mut named = self.named_schedules.write().unwrap();
395 named.clear();
396
397 info!("Cleared all scheduled responses");
398 }
399
400 pub fn list_scheduled(&self) -> Vec<ScheduledResponse> {
402 let scheduled = self.scheduled.read().unwrap();
403 scheduled.values().flat_map(|v| v.iter().cloned()).collect()
404 }
405
406 pub fn count(&self) -> usize {
408 let scheduled = self.scheduled.read().unwrap();
409 scheduled.values().map(|v| v.len()).sum()
410 }
411}
412
413#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct TimeScenario {
416 pub name: String,
418 pub enabled: bool,
420 pub current_time: Option<DateTime<Utc>>,
422 pub scale_factor: f64,
424 #[serde(default)]
426 pub scheduled_responses: Vec<ScheduledResponse>,
427 pub created_at: DateTime<Utc>,
429 #[serde(default)]
431 pub description: Option<String>,
432}
433
434impl TimeScenario {
435 pub fn from_manager(manager: &TimeTravelManager, name: String) -> Self {
437 let status = manager.clock().status();
438 let scheduled = manager.scheduler().list_scheduled();
439
440 Self {
441 name,
442 enabled: status.enabled,
443 current_time: status.current_time,
444 scale_factor: status.scale_factor,
445 scheduled_responses: scheduled,
446 created_at: Utc::now(),
447 description: None,
448 }
449 }
450
451 pub fn apply_to_manager(&self, manager: &TimeTravelManager) {
453 if self.enabled {
454 if let Some(time) = self.current_time {
455 manager.enable_and_set(time);
456 } else {
457 manager.enable_and_set(Utc::now());
458 }
459 manager.set_scale(self.scale_factor);
460 } else {
461 manager.disable();
462 }
463
464 manager.scheduler().clear_all();
466 for response in &self.scheduled_responses {
467 let _ = manager.scheduler().schedule(response.clone());
468 }
469 }
470}
471
472static GLOBAL_CLOCK_REGISTRY: Lazy<Arc<RwLock<Option<Arc<VirtualClock>>>>> =
479 Lazy::new(|| Arc::new(RwLock::new(None)));
480
481pub fn register_global_clock(clock: Arc<VirtualClock>) {
486 let mut registry = GLOBAL_CLOCK_REGISTRY.write().unwrap();
487 *registry = Some(clock);
488 info!("Virtual clock registered globally");
489}
490
491pub fn unregister_global_clock() {
495 let mut registry = GLOBAL_CLOCK_REGISTRY.write().unwrap();
496 *registry = None;
497 info!("Virtual clock unregistered globally");
498}
499
500pub fn get_global_clock() -> Option<Arc<VirtualClock>> {
504 let registry = GLOBAL_CLOCK_REGISTRY.read().unwrap();
505 registry.clone()
506}
507
508pub fn now() -> DateTime<Utc> {
515 if let Some(clock) = get_global_clock() {
516 clock.now()
517 } else {
518 Utc::now()
519 }
520}
521
522pub fn is_time_travel_enabled() -> bool {
526 if let Some(clock) = get_global_clock() {
527 clock.is_enabled()
528 } else {
529 false
530 }
531}
532
533pub struct TimeTravelManager {
535 clock: Arc<VirtualClock>,
537 scheduler: Arc<ResponseScheduler>,
539 cron_scheduler: Arc<cron::CronScheduler>,
541}
542
543impl TimeTravelManager {
544 pub fn new(config: TimeTravelConfig) -> Self {
549 let clock = Arc::new(VirtualClock::new());
550
551 if config.enabled {
552 if let Some(initial_time) = config.initial_time {
553 clock.enable_and_set(initial_time);
554 } else {
555 clock.enable_and_set(Utc::now());
556 }
557 clock.set_scale(config.scale_factor);
558 register_global_clock(clock.clone());
560 }
561
562 let scheduler = Arc::new(ResponseScheduler::new(clock.clone()));
563 let cron_scheduler = Arc::new(cron::CronScheduler::new(clock.clone()));
564
565 Self {
566 clock,
567 scheduler,
568 cron_scheduler,
569 }
570 }
571
572 pub fn clock(&self) -> Arc<VirtualClock> {
574 self.clock.clone()
575 }
576
577 pub fn scheduler(&self) -> Arc<ResponseScheduler> {
579 self.scheduler.clone()
580 }
581
582 pub fn cron_scheduler(&self) -> Arc<cron::CronScheduler> {
584 self.cron_scheduler.clone()
585 }
586
587 pub fn now(&self) -> DateTime<Utc> {
589 self.clock.now()
590 }
591
592 pub fn save_scenario(&self, name: String) -> TimeScenario {
594 TimeScenario::from_manager(self, name)
595 }
596
597 pub fn load_scenario(&self, scenario: &TimeScenario) {
599 scenario.apply_to_manager(self);
600 }
601
602 pub fn enable_and_set(&self, time: DateTime<Utc>) {
606 self.clock.enable_and_set(time);
607 register_global_clock(self.clock.clone());
608 }
609
610 pub fn disable(&self) {
614 self.clock.disable();
615 unregister_global_clock();
616 }
617
618 pub fn advance(&self, duration: Duration) {
622 self.clock.advance(duration);
623 }
624
625 pub fn set_time(&self, time: DateTime<Utc>) {
629 self.clock.set_time(time);
630 if self.clock.is_enabled() {
631 register_global_clock(self.clock.clone());
632 }
633 }
634
635 pub fn set_scale(&self, factor: f64) {
639 self.clock.set_scale(factor);
640 }
641}
642
643impl Drop for TimeTravelManager {
644 fn drop(&mut self) {
645 unregister_global_clock();
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653
654 #[test]
655 fn test_virtual_clock_creation() {
656 let clock = VirtualClock::new();
657 assert!(!clock.is_enabled());
658 }
659
660 #[test]
661 fn test_virtual_clock_enable() {
662 let clock = VirtualClock::new();
663 let test_time = Utc::now();
664 clock.enable_and_set(test_time);
665
666 assert!(clock.is_enabled());
667 let now = clock.now();
668 assert!((now - test_time).num_seconds().abs() < 1);
669 }
670
671 #[test]
672 fn test_virtual_clock_advance() {
673 let clock = VirtualClock::new();
674 let test_time = Utc::now();
675 clock.enable_and_set(test_time);
676
677 clock.advance(Duration::hours(2));
678 let now = clock.now();
679
680 assert!((now - test_time - Duration::hours(2)).num_seconds().abs() < 1);
681 }
682
683 #[test]
684 fn test_virtual_clock_scale() {
685 let clock = VirtualClock::new();
686 clock.set_scale(2.0);
687 assert_eq!(clock.get_scale(), 2.0);
688 }
689
690 #[test]
691 fn test_response_scheduler() {
692 let clock = Arc::new(VirtualClock::new());
693 let test_time = Utc::now();
694 clock.enable_and_set(test_time);
695
696 let scheduler = ResponseScheduler::new(clock.clone());
697
698 let response = ScheduledResponse {
699 id: "test-1".to_string(),
700 trigger_time: test_time + Duration::seconds(10),
701 body: serde_json::json!({"message": "Hello"}),
702 status: 200,
703 headers: HashMap::new(),
704 name: Some("test".to_string()),
705 repeat: None,
706 };
707
708 let id = scheduler.schedule(response).unwrap();
709 assert_eq!(id, "test-1");
710 assert_eq!(scheduler.count(), 1);
711 }
712
713 #[test]
714 fn test_scheduled_response_triggering() {
715 let clock = Arc::new(VirtualClock::new());
716 let test_time = Utc::now();
717 clock.enable_and_set(test_time);
718
719 let scheduler = ResponseScheduler::new(clock.clone());
720
721 let response = ScheduledResponse {
722 id: "test-1".to_string(),
723 trigger_time: test_time + Duration::seconds(10),
724 body: serde_json::json!({"message": "Hello"}),
725 status: 200,
726 headers: HashMap::new(),
727 name: None,
728 repeat: None,
729 };
730
731 scheduler.schedule(response).unwrap();
732
733 let due = scheduler.get_due_responses();
735 assert_eq!(due.len(), 0);
736
737 clock.advance(Duration::seconds(15));
739
740 let due = scheduler.get_due_responses();
742 assert_eq!(due.len(), 1);
743 }
744
745 #[test]
746 fn test_time_travel_config() {
747 let config = TimeTravelConfig::default();
748 assert!(!config.enabled);
749 assert_eq!(config.scale_factor, 1.0);
750 assert!(config.enable_scheduling);
751 }
752
753 #[test]
754 fn test_time_travel_manager() {
755 let config = TimeTravelConfig {
756 enabled: true,
757 initial_time: Some(Utc::now()),
758 scale_factor: 1.0,
759 enable_scheduling: true,
760 };
761
762 let manager = TimeTravelManager::new(config);
763 assert!(manager.clock().is_enabled());
764 }
765
766 #[test]
767 fn test_one_month_later_scenario() {
768 let clock = Arc::new(VirtualClock::new());
769 let initial_time = Utc::now();
770 clock.enable_and_set(initial_time);
771
772 clock.advance(Duration::days(30));
774
775 let final_time = clock.now();
776 let elapsed = final_time - initial_time;
777
778 assert!(elapsed.num_days() >= 29 && elapsed.num_days() <= 31);
780 }
781
782 #[test]
783 fn test_scenario_save_and_load() {
784 let config = TimeTravelConfig {
785 enabled: true,
786 initial_time: Some(Utc::now()),
787 scale_factor: 2.0,
788 enable_scheduling: true,
789 };
790
791 let manager = TimeTravelManager::new(config);
792
793 manager.clock().advance(Duration::hours(24));
795
796 let scenario = manager.save_scenario("test-scenario".to_string());
798 assert_eq!(scenario.name, "test-scenario");
799 assert!(scenario.enabled);
800 assert_eq!(scenario.scale_factor, 2.0);
801 assert!(scenario.current_time.is_some());
802
803 let new_config = TimeTravelConfig::default();
805 let new_manager = TimeTravelManager::new(new_config);
806
807 new_manager.load_scenario(&scenario);
809
810 assert!(new_manager.clock().is_enabled());
812 assert_eq!(new_manager.clock().get_scale(), 2.0);
813 if let Some(saved_time) = scenario.current_time {
814 let loaded_time = new_manager.clock().now();
815 assert!((loaded_time - saved_time).num_seconds().abs() < 1);
817 }
818 }
819
820 #[test]
821 fn test_duration_parsing_month_year() {
822 let clock = Arc::new(VirtualClock::new());
824 let initial_time = Utc::now();
825 clock.enable_and_set(initial_time);
826
827 clock.advance(Duration::days(30));
829 let after_month = clock.now();
830 let month_elapsed = after_month - initial_time;
831 assert!(month_elapsed.num_days() >= 29 && month_elapsed.num_days() <= 31);
832
833 clock.set_time(initial_time);
835 clock.advance(Duration::days(365));
836 let after_year = clock.now();
837 let year_elapsed = after_year - initial_time;
838 assert!(year_elapsed.num_days() >= 364 && year_elapsed.num_days() <= 366);
839 }
840}
841
842pub mod cron;
844
845pub use cron::{CronJob, CronJobAction, CronScheduler};