mockforge_core/time_travel/
mod.rs

1//! # Time Travel / Temporal Testing Module
2//!
3//! This module provides time travel capabilities for testing time-dependent behavior.
4//! It allows you to:
5//! - Simulate time progression without waiting
6//! - Schedule responses to be returned at specific virtual times
7//! - Test time-based state transitions (e.g., token expiry, session timeouts)
8//! - Control time flow for deterministic testing
9
10use 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/// Virtual clock that can be manipulated for testing time-dependent behavior
18#[derive(Debug, Clone)]
19pub struct VirtualClock {
20    /// The current virtual time (None means use real time)
21    current_time: Arc<RwLock<Option<DateTime<Utc>>>>,
22    /// Whether time travel is enabled
23    enabled: Arc<RwLock<bool>>,
24    /// Time scale factor (1.0 = real time, 2.0 = 2x speed, 0.5 = half speed)
25    scale_factor: Arc<RwLock<f64>>,
26    /// Baseline real time when virtual time was set (for scaled time)
27    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    /// Create a new virtual clock (disabled by default, uses real time)
38    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    /// Create a new virtual clock with time travel enabled at a specific time
48    pub fn new_at(time: DateTime<Utc>) -> Self {
49        let clock = Self::new();
50        clock.enable_and_set(time);
51        clock
52    }
53
54    /// Enable time travel and set the current virtual time
55    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    /// Disable time travel and return to using real time
69    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    /// Check if time travel is enabled
83    pub fn is_enabled(&self) -> bool {
84        *self.enabled.read().unwrap()
85    }
86
87    /// Get the current time (virtual or real)
88    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 factor is 1.0, just return the virtual time
100            if (scale - 1.0).abs() < f64::EPSILON {
101                return virtual_time;
102            }
103
104            // If scale factor is different, calculate scaled time
105            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    /// Advance time by a duration
120    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            // Update baseline to current real time
133            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    /// Set the time scale factor (1.0 = real time, 2.0 = 2x speed, etc.)
141    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        // Update baseline to current real time
151        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    /// Get the current time scale factor
158    pub fn get_scale(&self) -> f64 {
159        *self.scale_factor.read().unwrap()
160    }
161
162    /// Reset time travel to real time
163    pub fn reset(&self) {
164        self.disable();
165        info!("Time travel reset to real time");
166    }
167
168    /// Set the virtual time to a specific point
169    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        // Update baseline to current real time
180        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    /// Get time travel status
187    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/// Status information for time travel
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct TimeTravelStatus {
204    /// Whether time travel is enabled
205    pub enabled: bool,
206    /// Current virtual time (None if using real time)
207    pub current_time: Option<DateTime<Utc>>,
208    /// Time scale factor
209    pub scale_factor: f64,
210    /// Current real time
211    pub real_time: DateTime<Utc>,
212}
213
214/// Configuration for time travel features
215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct TimeTravelConfig {
218    /// Whether time travel is enabled by default
219    #[serde(default)]
220    pub enabled: bool,
221    /// Initial virtual time (if enabled)
222    #[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
223    pub initial_time: Option<DateTime<Utc>>,
224    /// Initial time scale factor
225    #[serde(default = "default_scale")]
226    pub scale_factor: f64,
227    /// Whether to enable scheduled responses
228    #[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/// Schedule manager for time-based response scheduling
252#[derive(Debug, Clone)]
253pub struct ResponseScheduler {
254    /// Virtual clock reference
255    clock: Arc<VirtualClock>,
256    /// Scheduled responses (sorted by trigger time)
257    scheduled: Arc<RwLock<BTreeMap<DateTime<Utc>, Vec<ScheduledResponse>>>>,
258    /// Named schedules for easy reference
259    named_schedules: Arc<RwLock<HashMap<String, String>>>,
260}
261
262/// A scheduled response that will be returned at a specific time
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct ScheduledResponse {
265    /// Unique identifier for this scheduled response
266    pub id: String,
267    /// When this response should be returned
268    pub trigger_time: DateTime<Utc>,
269    /// The response body
270    pub body: serde_json::Value,
271    /// HTTP status code
272    #[serde(default = "default_status")]
273    pub status: u16,
274    /// Response headers
275    #[serde(default)]
276    pub headers: HashMap<String, String>,
277    /// Optional name/label
278    pub name: Option<String>,
279    /// Whether this should repeat
280    #[serde(default)]
281    pub repeat: Option<RepeatConfig>,
282}
283
284fn default_status() -> u16 {
285    200
286}
287
288/// Configuration for repeating scheduled responses
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct RepeatConfig {
291    /// Interval between repeats
292    pub interval: Duration,
293    /// Maximum number of repeats (None = infinite)
294    pub max_count: Option<usize>,
295}
296
297impl ResponseScheduler {
298    /// Create a new response scheduler
299    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    /// Schedule a response to be returned at a specific time
308    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    /// Get responses that should be triggered at the current time
328    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        // Get all times up to now
334        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                    // Handle repeating responses
343                    if let Some(repeat_config) = &response.repeat {
344                        let next_time = time + repeat_config.interval;
345
346                        // Check if we should schedule another repeat
347                        let should_repeat = if let Some(max) = repeat_config.max_count {
348                            // Track repeat count (simplified - in production use a counter)
349                            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    /// Remove a scheduled response by ID
375    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    /// Clear all scheduled responses
390    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    /// Get all scheduled responses
401    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    /// Get count of scheduled responses
407    pub fn count(&self) -> usize {
408        let scheduled = self.scheduled.read().unwrap();
409        scheduled.values().map(|v| v.len()).sum()
410    }
411}
412
413/// Time travel scenario snapshot
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct TimeScenario {
416    /// Scenario name
417    pub name: String,
418    /// Whether time travel is enabled
419    pub enabled: bool,
420    /// Current virtual time (if enabled)
421    pub current_time: Option<DateTime<Utc>>,
422    /// Time scale factor
423    pub scale_factor: f64,
424    /// Scheduled responses (if any)
425    #[serde(default)]
426    pub scheduled_responses: Vec<ScheduledResponse>,
427    /// Created timestamp
428    pub created_at: DateTime<Utc>,
429    /// Description (optional)
430    #[serde(default)]
431    pub description: Option<String>,
432}
433
434impl TimeScenario {
435    /// Create a new scenario from current time travel state
436    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    /// Apply this scenario to a time travel manager
452    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        // Clear existing scheduled responses and add scenario ones
465        manager.scheduler().clear_all();
466        for response in &self.scheduled_responses {
467            let _ = manager.scheduler().schedule(response.clone());
468        }
469    }
470}
471
472/// Global virtual clock registry for automatic time travel detection
473///
474/// This registry allows modules throughout MockForge to automatically detect
475/// if time travel is enabled and use the virtual clock when available.
476/// Modules can call `get_global_clock()` or `now()` to get the current time,
477/// which will automatically use virtual time if enabled, or real time otherwise.
478static GLOBAL_CLOCK_REGISTRY: Lazy<Arc<RwLock<Option<Arc<VirtualClock>>>>> =
479    Lazy::new(|| Arc::new(RwLock::new(None)));
480
481/// Register a virtual clock with the global registry
482///
483/// This should be called when a `TimeTravelManager` is created to make
484/// the virtual clock available throughout the application.
485pub 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
491/// Unregister the global virtual clock
492///
493/// This should be called when time travel is disabled or the manager is dropped.
494pub fn unregister_global_clock() {
495    let mut registry = GLOBAL_CLOCK_REGISTRY.write().unwrap();
496    *registry = None;
497    info!("Virtual clock unregistered globally");
498}
499
500/// Get the global virtual clock if one is registered
501///
502/// Returns `None` if time travel is not enabled or no clock is registered.
503pub fn get_global_clock() -> Option<Arc<VirtualClock>> {
504    let registry = GLOBAL_CLOCK_REGISTRY.read().unwrap();
505    registry.clone()
506}
507
508/// Get the current time, automatically using virtual clock if available
509///
510/// This is a convenience function that checks the global registry and returns
511/// virtual time if time travel is enabled, or real time otherwise.
512/// This allows modules to automatically respect time travel without needing
513/// to explicitly pass a clock reference.
514pub fn now() -> DateTime<Utc> {
515    if let Some(clock) = get_global_clock() {
516        clock.now()
517    } else {
518        Utc::now()
519    }
520}
521
522/// Check if time travel is currently enabled globally
523///
524/// Returns `true` if a virtual clock is registered and enabled.
525pub 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
533/// Global time travel manager
534pub struct TimeTravelManager {
535    /// Virtual clock
536    clock: Arc<VirtualClock>,
537    /// Response scheduler
538    scheduler: Arc<ResponseScheduler>,
539    /// Cron scheduler for recurring events
540    cron_scheduler: Arc<cron::CronScheduler>,
541}
542
543impl TimeTravelManager {
544    /// Create a new time travel manager
545    ///
546    /// The virtual clock is automatically registered with the global registry
547    /// so it can be detected by other modules (e.g., auth, session expiration).
548    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 with global registry for automatic detection
559            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    /// Get the virtual clock
573    pub fn clock(&self) -> Arc<VirtualClock> {
574        self.clock.clone()
575    }
576
577    /// Get the response scheduler
578    pub fn scheduler(&self) -> Arc<ResponseScheduler> {
579        self.scheduler.clone()
580    }
581
582    /// Get the cron scheduler
583    pub fn cron_scheduler(&self) -> Arc<cron::CronScheduler> {
584        self.cron_scheduler.clone()
585    }
586
587    /// Get the current time (respects virtual clock if enabled)
588    pub fn now(&self) -> DateTime<Utc> {
589        self.clock.now()
590    }
591
592    /// Save current state as a scenario
593    pub fn save_scenario(&self, name: String) -> TimeScenario {
594        TimeScenario::from_manager(self, name)
595    }
596
597    /// Load and apply a scenario
598    pub fn load_scenario(&self, scenario: &TimeScenario) {
599        scenario.apply_to_manager(self);
600    }
601
602    /// Enable time travel and set the current virtual time
603    ///
604    /// This method wraps the clock's enable_and_set and updates the global registry.
605    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    /// Disable time travel and return to using real time
611    ///
612    /// This method wraps the clock's disable and updates the global registry.
613    pub fn disable(&self) {
614        self.clock.disable();
615        unregister_global_clock();
616    }
617
618    /// Advance time by a duration
619    ///
620    /// This method wraps the clock's advance for convenience.
621    pub fn advance(&self, duration: Duration) {
622        self.clock.advance(duration);
623    }
624
625    /// Set the virtual time to a specific point
626    ///
627    /// This method wraps the clock's set_time for convenience.
628    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    /// Set the time scale factor
636    ///
637    /// This method wraps the clock's set_scale for convenience.
638    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 when manager is dropped
646        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        // Should not be due yet
734        let due = scheduler.get_due_responses();
735        assert_eq!(due.len(), 0);
736
737        // Advance time
738        clock.advance(Duration::seconds(15));
739
740        // Should be due now
741        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        // Advance by 1 month (30 days)
773        clock.advance(Duration::days(30));
774
775        let final_time = clock.now();
776        let elapsed = final_time - initial_time;
777
778        // Should be approximately 30 days
779        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        // Advance time
794        manager.clock().advance(Duration::hours(24));
795
796        // Save scenario
797        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        // Create new manager and load scenario
804        let new_config = TimeTravelConfig::default();
805        let new_manager = TimeTravelManager::new(new_config);
806
807        // Load scenario
808        new_manager.load_scenario(&scenario);
809
810        // Verify state was restored
811        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            // Times should be very close (within 1 second)
816            assert!((loaded_time - saved_time).num_seconds().abs() < 1);
817        }
818    }
819
820    #[test]
821    fn test_duration_parsing_month_year() {
822        // Test that month and year durations work
823        let clock = Arc::new(VirtualClock::new());
824        let initial_time = Utc::now();
825        clock.enable_and_set(initial_time);
826
827        // Advance by 1 month (should be ~30 days)
828        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        // Reset and advance by 1 year (should be ~365 days)
834        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
842// Cron scheduler module
843pub mod cron;
844
845// Re-export cron types
846pub use cron::{CronJob, CronJobAction, CronScheduler};