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#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct TimeTravelConfig {
217    /// Whether time travel is enabled by default
218    #[serde(default)]
219    pub enabled: bool,
220    /// Initial virtual time (if enabled)
221    pub initial_time: Option<DateTime<Utc>>,
222    /// Initial time scale factor
223    #[serde(default = "default_scale")]
224    pub scale_factor: f64,
225    /// Whether to enable scheduled responses
226    #[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/// Schedule manager for time-based response scheduling
250#[derive(Debug, Clone)]
251pub struct ResponseScheduler {
252    /// Virtual clock reference
253    clock: Arc<VirtualClock>,
254    /// Scheduled responses (sorted by trigger time)
255    scheduled: Arc<RwLock<BTreeMap<DateTime<Utc>, Vec<ScheduledResponse>>>>,
256    /// Named schedules for easy reference
257    named_schedules: Arc<RwLock<HashMap<String, String>>>,
258}
259
260/// A scheduled response that will be returned at a specific time
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct ScheduledResponse {
263    /// Unique identifier for this scheduled response
264    pub id: String,
265    /// When this response should be returned
266    pub trigger_time: DateTime<Utc>,
267    /// The response body
268    pub body: serde_json::Value,
269    /// HTTP status code
270    #[serde(default = "default_status")]
271    pub status: u16,
272    /// Response headers
273    #[serde(default)]
274    pub headers: HashMap<String, String>,
275    /// Optional name/label
276    pub name: Option<String>,
277    /// Whether this should repeat
278    #[serde(default)]
279    pub repeat: Option<RepeatConfig>,
280}
281
282fn default_status() -> u16 {
283    200
284}
285
286/// Configuration for repeating scheduled responses
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct RepeatConfig {
289    /// Interval between repeats
290    pub interval: Duration,
291    /// Maximum number of repeats (None = infinite)
292    pub max_count: Option<usize>,
293}
294
295impl ResponseScheduler {
296    /// Create a new response scheduler
297    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    /// Schedule a response to be returned at a specific time
306    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    /// Get responses that should be triggered at the current time
326    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        // Get all times up to now
332        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                    // Handle repeating responses
341                    if let Some(repeat_config) = &response.repeat {
342                        let next_time = time + repeat_config.interval;
343
344                        // Check if we should schedule another repeat
345                        let should_repeat = if let Some(max) = repeat_config.max_count {
346                            // Track repeat count (simplified - in production use a counter)
347                            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    /// Remove a scheduled response by ID
373    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    /// Clear all scheduled responses
388    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    /// Get all scheduled responses
399    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    /// Get count of scheduled responses
405    pub fn count(&self) -> usize {
406        let scheduled = self.scheduled.read().unwrap();
407        scheduled.values().map(|v| v.len()).sum()
408    }
409}
410
411/// Time travel scenario snapshot
412#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct TimeScenario {
414    /// Scenario name
415    pub name: String,
416    /// Whether time travel is enabled
417    pub enabled: bool,
418    /// Current virtual time (if enabled)
419    pub current_time: Option<DateTime<Utc>>,
420    /// Time scale factor
421    pub scale_factor: f64,
422    /// Scheduled responses (if any)
423    #[serde(default)]
424    pub scheduled_responses: Vec<ScheduledResponse>,
425    /// Created timestamp
426    pub created_at: DateTime<Utc>,
427    /// Description (optional)
428    #[serde(default)]
429    pub description: Option<String>,
430}
431
432impl TimeScenario {
433    /// Create a new scenario from current time travel state
434    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    /// Apply this scenario to a time travel manager
450    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        // Clear existing scheduled responses and add scenario ones
463        manager.scheduler().clear_all();
464        for response in &self.scheduled_responses {
465            let _ = manager.scheduler().schedule(response.clone());
466        }
467    }
468}
469
470/// Global virtual clock registry for automatic time travel detection
471///
472/// This registry allows modules throughout MockForge to automatically detect
473/// if time travel is enabled and use the virtual clock when available.
474/// Modules can call `get_global_clock()` or `now()` to get the current time,
475/// which will automatically use virtual time if enabled, or real time otherwise.
476static GLOBAL_CLOCK_REGISTRY: Lazy<Arc<RwLock<Option<Arc<VirtualClock>>>>> =
477    Lazy::new(|| Arc::new(RwLock::new(None)));
478
479/// Register a virtual clock with the global registry
480///
481/// This should be called when a `TimeTravelManager` is created to make
482/// the virtual clock available throughout the application.
483pub 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
489/// Unregister the global virtual clock
490///
491/// This should be called when time travel is disabled or the manager is dropped.
492pub fn unregister_global_clock() {
493    let mut registry = GLOBAL_CLOCK_REGISTRY.write().unwrap();
494    *registry = None;
495    info!("Virtual clock unregistered globally");
496}
497
498/// Get the global virtual clock if one is registered
499///
500/// Returns `None` if time travel is not enabled or no clock is registered.
501pub fn get_global_clock() -> Option<Arc<VirtualClock>> {
502    let registry = GLOBAL_CLOCK_REGISTRY.read().unwrap();
503    registry.clone()
504}
505
506/// Get the current time, automatically using virtual clock if available
507///
508/// This is a convenience function that checks the global registry and returns
509/// virtual time if time travel is enabled, or real time otherwise.
510/// This allows modules to automatically respect time travel without needing
511/// to explicitly pass a clock reference.
512pub fn now() -> DateTime<Utc> {
513    if let Some(clock) = get_global_clock() {
514        clock.now()
515    } else {
516        Utc::now()
517    }
518}
519
520/// Check if time travel is currently enabled globally
521///
522/// Returns `true` if a virtual clock is registered and enabled.
523pub 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
531/// Global time travel manager
532pub struct TimeTravelManager {
533    /// Virtual clock
534    clock: Arc<VirtualClock>,
535    /// Response scheduler
536    scheduler: Arc<ResponseScheduler>,
537    /// Cron scheduler for recurring events
538    cron_scheduler: Arc<cron::CronScheduler>,
539}
540
541impl TimeTravelManager {
542    /// Create a new time travel manager
543    ///
544    /// The virtual clock is automatically registered with the global registry
545    /// so it can be detected by other modules (e.g., auth, session expiration).
546    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 with global registry for automatic detection
557            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    /// Get the virtual clock
571    pub fn clock(&self) -> Arc<VirtualClock> {
572        self.clock.clone()
573    }
574
575    /// Get the response scheduler
576    pub fn scheduler(&self) -> Arc<ResponseScheduler> {
577        self.scheduler.clone()
578    }
579
580    /// Get the cron scheduler
581    pub fn cron_scheduler(&self) -> Arc<cron::CronScheduler> {
582        self.cron_scheduler.clone()
583    }
584
585    /// Get the current time (respects virtual clock if enabled)
586    pub fn now(&self) -> DateTime<Utc> {
587        self.clock.now()
588    }
589
590    /// Save current state as a scenario
591    pub fn save_scenario(&self, name: String) -> TimeScenario {
592        TimeScenario::from_manager(self, name)
593    }
594
595    /// Load and apply a scenario
596    pub fn load_scenario(&self, scenario: &TimeScenario) {
597        scenario.apply_to_manager(self);
598    }
599
600    /// Enable time travel and set the current virtual time
601    ///
602    /// This method wraps the clock's enable_and_set and updates the global registry.
603    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    /// Disable time travel and return to using real time
609    ///
610    /// This method wraps the clock's disable and updates the global registry.
611    pub fn disable(&self) {
612        self.clock.disable();
613        unregister_global_clock();
614    }
615
616    /// Advance time by a duration
617    ///
618    /// This method wraps the clock's advance for convenience.
619    pub fn advance(&self, duration: Duration) {
620        self.clock.advance(duration);
621    }
622
623    /// Set the virtual time to a specific point
624    ///
625    /// This method wraps the clock's set_time for convenience.
626    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    /// Set the time scale factor
634    ///
635    /// This method wraps the clock's set_scale for convenience.
636    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 when manager is dropped
644        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        // Should not be due yet
732        let due = scheduler.get_due_responses();
733        assert_eq!(due.len(), 0);
734
735        // Advance time
736        clock.advance(Duration::seconds(15));
737
738        // Should be due now
739        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        // Advance by 1 month (30 days)
771        clock.advance(Duration::days(30));
772
773        let final_time = clock.now();
774        let elapsed = final_time - initial_time;
775
776        // Should be approximately 30 days
777        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        // Advance time
792        manager.clock().advance(Duration::hours(24));
793
794        // Save scenario
795        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        // Create new manager and load scenario
802        let new_config = TimeTravelConfig::default();
803        let new_manager = TimeTravelManager::new(new_config);
804
805        // Load scenario
806        new_manager.load_scenario(&scenario);
807
808        // Verify state was restored
809        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            // Times should be very close (within 1 second)
814            assert!((loaded_time - saved_time).num_seconds().abs() < 1);
815        }
816    }
817
818    #[test]
819    fn test_duration_parsing_month_year() {
820        // Test that month and year durations work
821        let clock = Arc::new(VirtualClock::new());
822        let initial_time = Utc::now();
823        clock.enable_and_set(initial_time);
824
825        // Advance by 1 month (should be ~30 days)
826        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        // Reset and advance by 1 year (should be ~365 days)
832        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
840// Cron scheduler module
841pub mod cron;
842
843// Re-export cron types
844pub use cron::{CronJob, CronJobAction, CronScheduler};