Skip to main content

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/// Callback function type for time change events
18pub type TimeChangeCallback = Arc<dyn Fn(DateTime<Utc>, DateTime<Utc>) + Send + Sync>;
19
20/// Virtual clock that can be manipulated for testing time-dependent behavior
21#[derive(Clone)]
22pub struct VirtualClock {
23    /// The current virtual time (None means use real time)
24    current_time: Arc<RwLock<Option<DateTime<Utc>>>>,
25    /// Whether time travel is enabled
26    enabled: Arc<RwLock<bool>>,
27    /// Time scale factor (1.0 = real time, 2.0 = 2x speed, 0.5 = half speed)
28    scale_factor: Arc<RwLock<f64>>,
29    /// Baseline real time when virtual time was set (for scaled time)
30    baseline_real_time: Arc<RwLock<Option<DateTime<Utc>>>>,
31    /// Callbacks to invoke when time changes
32    #[cfg_attr(not(test), allow(dead_code))]
33    time_change_callbacks: Arc<RwLock<Vec<TimeChangeCallback>>>,
34}
35
36impl std::fmt::Debug for VirtualClock {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("VirtualClock")
39            .field(
40                "current_time",
41                &*self.current_time.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
42            )
43            .field(
44                "enabled",
45                &*self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
46            )
47            .field(
48                "scale_factor",
49                &*self.scale_factor.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
50            )
51            .field(
52                "baseline_real_time",
53                &*self.baseline_real_time.read().unwrap_or_else(|poisoned| poisoned.into_inner()),
54            )
55            .field(
56                "callback_count",
57                &self
58                    .time_change_callbacks
59                    .read()
60                    .unwrap_or_else(|poisoned| poisoned.into_inner())
61                    .len(),
62            )
63            .finish()
64    }
65}
66
67impl Default for VirtualClock {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73impl VirtualClock {
74    /// Create a new virtual clock (disabled by default, uses real time)
75    pub fn new() -> Self {
76        Self {
77            current_time: Arc::new(RwLock::new(None)),
78            enabled: Arc::new(RwLock::new(false)),
79            scale_factor: Arc::new(RwLock::new(1.0)),
80            baseline_real_time: Arc::new(RwLock::new(None)),
81            time_change_callbacks: Arc::new(RwLock::new(Vec::new())),
82        }
83    }
84
85    /// Register a callback to be invoked when time changes
86    ///
87    /// The callback receives (old_time, new_time) as arguments.
88    pub fn on_time_change<F>(&self, callback: F)
89    where
90        F: Fn(DateTime<Utc>, DateTime<Utc>) + Send + Sync + 'static,
91    {
92        let mut callbacks = self
93            .time_change_callbacks
94            .write()
95            .unwrap_or_else(|poisoned| poisoned.into_inner());
96        callbacks.push(Arc::new(callback));
97    }
98
99    /// Invoke all registered time change callbacks
100    fn invoke_time_change_callbacks(&self, old_time: DateTime<Utc>, new_time: DateTime<Utc>) {
101        let callbacks = self
102            .time_change_callbacks
103            .read()
104            .unwrap_or_else(|poisoned| poisoned.into_inner());
105        for callback in callbacks.iter() {
106            callback(old_time, new_time);
107        }
108    }
109
110    /// Create a new virtual clock with time travel enabled at a specific time
111    pub fn new_at(time: DateTime<Utc>) -> Self {
112        let clock = Self::new();
113        clock.enable_and_set(time);
114        clock
115    }
116
117    /// Enable time travel and set the current virtual time
118    pub fn enable_and_set(&self, time: DateTime<Utc>) {
119        let mut current =
120            self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
121        *current = Some(time);
122
123        let mut enabled = self.enabled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
124        *enabled = true;
125
126        let mut baseline =
127            self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
128        *baseline = Some(Utc::now());
129
130        info!("Time travel enabled at {}", time);
131    }
132
133    /// Disable time travel and return to using real time
134    pub fn disable(&self) {
135        let mut enabled = self.enabled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
136        *enabled = false;
137
138        let mut current =
139            self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
140        *current = None;
141
142        let mut baseline =
143            self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
144        *baseline = None;
145
146        info!("Time travel disabled, using real time");
147    }
148
149    /// Check if time travel is enabled
150    pub fn is_enabled(&self) -> bool {
151        *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner())
152    }
153
154    /// Get the current time (virtual or real)
155    pub fn now(&self) -> DateTime<Utc> {
156        let enabled = *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
157
158        if !enabled {
159            return Utc::now();
160        }
161
162        let current = self.current_time.read().unwrap_or_else(|poisoned| poisoned.into_inner());
163        let scale = *self.scale_factor.read().unwrap_or_else(|poisoned| poisoned.into_inner());
164
165        if let Some(virtual_time) = *current {
166            // If scale factor is 1.0, just return the virtual time
167            if (scale - 1.0).abs() < f64::EPSILON {
168                return virtual_time;
169            }
170
171            // If scale factor is different, calculate scaled time
172            let baseline =
173                self.baseline_real_time.read().unwrap_or_else(|poisoned| poisoned.into_inner());
174            if let Some(baseline_real) = *baseline {
175                let elapsed_real = Utc::now() - baseline_real;
176                let elapsed_scaled =
177                    Duration::milliseconds((elapsed_real.num_milliseconds() as f64 * scale) as i64);
178                return virtual_time + elapsed_scaled;
179            }
180
181            virtual_time
182        } else {
183            Utc::now()
184        }
185    }
186
187    /// Advance time by a duration
188    pub fn advance(&self, duration: Duration) {
189        let enabled = *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
190        if !enabled {
191            warn!("Cannot advance time: time travel is not enabled");
192            return;
193        }
194
195        let mut current =
196            self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
197        if let Some(time) = *current {
198            let old_time = time;
199            let new_time = time + duration;
200            *current = Some(new_time);
201
202            // Update baseline to current real time
203            let mut baseline =
204                self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
205            *baseline = Some(Utc::now());
206
207            // Invoke callbacks
208            drop(current);
209            drop(baseline);
210            self.invoke_time_change_callbacks(old_time, new_time);
211
212            info!("Time advanced by {} to {}", duration, new_time);
213        }
214    }
215
216    /// Set the time scale factor (1.0 = real time, 2.0 = 2x speed, etc.)
217    pub fn set_scale(&self, factor: f64) {
218        if factor <= 0.0 {
219            warn!("Invalid scale factor: {}, must be positive", factor);
220            return;
221        }
222
223        let mut scale = self.scale_factor.write().unwrap_or_else(|poisoned| poisoned.into_inner());
224        *scale = factor;
225
226        // Update baseline to current real time
227        let mut baseline =
228            self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
229        *baseline = Some(Utc::now());
230
231        info!("Time scale set to {}x", factor);
232    }
233
234    /// Get the current time scale factor
235    pub fn get_scale(&self) -> f64 {
236        *self.scale_factor.read().unwrap_or_else(|poisoned| poisoned.into_inner())
237    }
238
239    /// Reset time travel to real time
240    pub fn reset(&self) {
241        self.disable();
242        info!("Time travel reset to real time");
243    }
244
245    /// Set the virtual time to a specific point
246    pub fn set_time(&self, time: DateTime<Utc>) {
247        let enabled = *self.enabled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
248        if !enabled {
249            self.enable_and_set(time);
250            return;
251        }
252
253        let mut current =
254            self.current_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
255        let old_time = *current;
256        *current = Some(time);
257
258        // Update baseline to current real time
259        let mut baseline =
260            self.baseline_real_time.write().unwrap_or_else(|poisoned| poisoned.into_inner());
261        *baseline = Some(Utc::now());
262
263        // Invoke callbacks if time actually changed
264        if let Some(old) = old_time {
265            drop(current);
266            drop(baseline);
267            self.invoke_time_change_callbacks(old, time);
268        }
269
270        info!("Virtual time set to {}", time);
271    }
272
273    /// Get time travel status
274    pub fn status(&self) -> TimeTravelStatus {
275        TimeTravelStatus {
276            enabled: self.is_enabled(),
277            current_time: if self.is_enabled() {
278                Some(self.now())
279            } else {
280                None
281            },
282            scale_factor: self.get_scale(),
283            real_time: Utc::now(),
284        }
285    }
286}
287
288/// Status information for time travel
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct TimeTravelStatus {
291    /// Whether time travel is enabled
292    pub enabled: bool,
293    /// Current virtual time (None if using real time)
294    pub current_time: Option<DateTime<Utc>>,
295    /// Time scale factor
296    pub scale_factor: f64,
297    /// Current real time
298    pub real_time: DateTime<Utc>,
299}
300
301/// Configuration for time travel features
302#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct TimeTravelConfig {
305    /// Whether time travel is enabled by default
306    #[serde(default)]
307    pub enabled: bool,
308    /// Initial virtual time (if enabled)
309    #[cfg_attr(feature = "schema", schemars(with = "Option<String>"))]
310    pub initial_time: Option<DateTime<Utc>>,
311    /// Initial time scale factor
312    #[serde(default = "default_scale")]
313    pub scale_factor: f64,
314    /// Whether to enable scheduled responses
315    #[serde(default = "default_true")]
316    pub enable_scheduling: bool,
317}
318
319fn default_scale() -> f64 {
320    1.0
321}
322
323fn default_true() -> bool {
324    true
325}
326
327impl Default for TimeTravelConfig {
328    fn default() -> Self {
329        Self {
330            enabled: false,
331            initial_time: None,
332            scale_factor: 1.0,
333            enable_scheduling: true,
334        }
335    }
336}
337
338/// Schedule manager for time-based response scheduling
339#[derive(Debug, Clone)]
340pub struct ResponseScheduler {
341    /// Virtual clock reference
342    clock: Arc<VirtualClock>,
343    /// Scheduled responses (sorted by trigger time)
344    scheduled: Arc<RwLock<BTreeMap<DateTime<Utc>, Vec<ScheduledResponse>>>>,
345    /// Named schedules for easy reference
346    named_schedules: Arc<RwLock<HashMap<String, String>>>,
347}
348
349/// A scheduled response that will be returned at a specific time
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct ScheduledResponse {
352    /// Unique identifier for this scheduled response
353    pub id: String,
354    /// When this response should be returned
355    pub trigger_time: DateTime<Utc>,
356    /// The response body
357    pub body: serde_json::Value,
358    /// HTTP status code
359    #[serde(default = "default_status")]
360    pub status: u16,
361    /// Response headers
362    #[serde(default)]
363    pub headers: HashMap<String, String>,
364    /// Optional name/label
365    pub name: Option<String>,
366    /// Whether this should repeat
367    #[serde(default)]
368    pub repeat: Option<RepeatConfig>,
369}
370
371fn default_status() -> u16 {
372    200
373}
374
375/// Configuration for repeating scheduled responses
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct RepeatConfig {
378    /// Interval between repeats
379    pub interval: Duration,
380    /// Maximum number of repeats (None = infinite)
381    pub max_count: Option<usize>,
382}
383
384impl ResponseScheduler {
385    /// Create a new response scheduler
386    pub fn new(clock: Arc<VirtualClock>) -> Self {
387        Self {
388            clock,
389            scheduled: Arc::new(RwLock::new(BTreeMap::new())),
390            named_schedules: Arc::new(RwLock::new(HashMap::new())),
391        }
392    }
393
394    /// Schedule a response to be returned at a specific time
395    pub fn schedule(&self, response: ScheduledResponse) -> Result<String, String> {
396        let id = if response.id.is_empty() {
397            uuid::Uuid::new_v4().to_string()
398        } else {
399            response.id.clone()
400        };
401
402        let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
403        scheduled.entry(response.trigger_time).or_default().push(response.clone());
404
405        if let Some(name) = &response.name {
406            let mut named =
407                self.named_schedules.write().unwrap_or_else(|poisoned| poisoned.into_inner());
408            named.insert(name.clone(), id.clone());
409        }
410
411        info!("Scheduled response {} for {}", id, response.trigger_time);
412        Ok(id)
413    }
414
415    /// Get responses that should be triggered at the current time
416    pub fn get_due_responses(&self) -> Vec<ScheduledResponse> {
417        let now = self.clock.now();
418        let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
419        let mut due = Vec::new();
420
421        // Get all times up to now
422        let times_to_process: Vec<DateTime<Utc>> =
423            scheduled.range(..=now).map(|(time, _)| *time).collect();
424
425        for time in times_to_process {
426            if let Some(responses) = scheduled.remove(&time) {
427                for response in responses {
428                    due.push(response.clone());
429
430                    // Handle repeating responses
431                    if let Some(repeat_config) = &response.repeat {
432                        let next_time = time + repeat_config.interval;
433
434                        // Check if we should schedule another repeat
435                        let should_repeat = if let Some(max) = repeat_config.max_count {
436                            // Track repeat count (simplified - in production use a counter)
437                            max > 1
438                        } else {
439                            true
440                        };
441
442                        if should_repeat {
443                            let mut next_response = response.clone();
444                            next_response.trigger_time = next_time;
445                            if let Some(ref mut repeat) = next_response.repeat {
446                                if let Some(ref mut count) = repeat.max_count {
447                                    *count -= 1;
448                                }
449                            }
450
451                            scheduled.entry(next_time).or_default().push(next_response);
452                        }
453                    }
454                }
455            }
456        }
457
458        debug!("Found {} due responses at {}", due.len(), now);
459        due
460    }
461
462    /// Remove a scheduled response by ID
463    pub fn cancel(&self, id: &str) -> bool {
464        let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
465
466        for responses in scheduled.values_mut() {
467            if let Some(pos) = responses.iter().position(|r| r.id == id) {
468                responses.remove(pos);
469                info!("Cancelled scheduled response {}", id);
470                return true;
471            }
472        }
473
474        false
475    }
476
477    /// Clear all scheduled responses
478    pub fn clear_all(&self) {
479        let mut scheduled = self.scheduled.write().unwrap_or_else(|poisoned| poisoned.into_inner());
480        scheduled.clear();
481
482        let mut named =
483            self.named_schedules.write().unwrap_or_else(|poisoned| poisoned.into_inner());
484        named.clear();
485
486        info!("Cleared all scheduled responses");
487    }
488
489    /// Get all scheduled responses
490    pub fn list_scheduled(&self) -> Vec<ScheduledResponse> {
491        let scheduled = self.scheduled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
492        scheduled.values().flat_map(|v| v.iter().cloned()).collect()
493    }
494
495    /// Get count of scheduled responses
496    pub fn count(&self) -> usize {
497        let scheduled = self.scheduled.read().unwrap_or_else(|poisoned| poisoned.into_inner());
498        scheduled.values().map(|v| v.len()).sum()
499    }
500}
501
502/// Time travel scenario snapshot
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct TimeScenario {
505    /// Scenario name
506    pub name: String,
507    /// Whether time travel is enabled
508    pub enabled: bool,
509    /// Current virtual time (if enabled)
510    pub current_time: Option<DateTime<Utc>>,
511    /// Time scale factor
512    pub scale_factor: f64,
513    /// Scheduled responses (if any)
514    #[serde(default)]
515    pub scheduled_responses: Vec<ScheduledResponse>,
516    /// Created timestamp
517    pub created_at: DateTime<Utc>,
518    /// Description (optional)
519    #[serde(default)]
520    pub description: Option<String>,
521}
522
523impl TimeScenario {
524    /// Create a new scenario from current time travel state
525    pub fn from_manager(manager: &TimeTravelManager, name: String) -> Self {
526        let status = manager.clock().status();
527        let scheduled = manager.scheduler().list_scheduled();
528
529        Self {
530            name,
531            enabled: status.enabled,
532            current_time: status.current_time,
533            scale_factor: status.scale_factor,
534            scheduled_responses: scheduled,
535            created_at: Utc::now(),
536            description: None,
537        }
538    }
539
540    /// Apply this scenario to a time travel manager
541    pub fn apply_to_manager(&self, manager: &TimeTravelManager) {
542        if self.enabled {
543            if let Some(time) = self.current_time {
544                manager.enable_and_set(time);
545            } else {
546                manager.enable_and_set(Utc::now());
547            }
548            manager.set_scale(self.scale_factor);
549        } else {
550            manager.disable();
551        }
552
553        // Clear existing scheduled responses and add scenario ones
554        manager.scheduler().clear_all();
555        for response in &self.scheduled_responses {
556            let _ = manager.scheduler().schedule(response.clone());
557        }
558    }
559}
560
561/// Global virtual clock registry for automatic time travel detection
562///
563/// This registry allows modules throughout MockForge to automatically detect
564/// if time travel is enabled and use the virtual clock when available.
565/// Modules can call `get_global_clock()` or `now()` to get the current time,
566/// which will automatically use virtual time if enabled, or real time otherwise.
567static GLOBAL_CLOCK_REGISTRY: Lazy<Arc<RwLock<Option<Arc<VirtualClock>>>>> =
568    Lazy::new(|| Arc::new(RwLock::new(None)));
569
570/// Register a virtual clock with the global registry
571///
572/// This should be called when a `TimeTravelManager` is created to make
573/// the virtual clock available throughout the application.
574pub fn register_global_clock(clock: Arc<VirtualClock>) {
575    let mut registry =
576        GLOBAL_CLOCK_REGISTRY.write().unwrap_or_else(|poisoned| poisoned.into_inner());
577    *registry = Some(clock);
578    info!("Virtual clock registered globally");
579    // Also register with mockforge-foundation so types there (e.g. SessionState)
580    // honor the virtual clock. Ignored if already set (OnceLock semantics).
581    let _ = mockforge_foundation::clock::set_clock(now);
582}
583
584/// Unregister the global virtual clock
585///
586/// This should be called when time travel is disabled or the manager is dropped.
587pub fn unregister_global_clock() {
588    let mut registry =
589        GLOBAL_CLOCK_REGISTRY.write().unwrap_or_else(|poisoned| poisoned.into_inner());
590    *registry = None;
591    info!("Virtual clock unregistered globally");
592}
593
594/// Get the global virtual clock if one is registered
595///
596/// Returns `None` if time travel is not enabled or no clock is registered.
597pub fn get_global_clock() -> Option<Arc<VirtualClock>> {
598    let registry = GLOBAL_CLOCK_REGISTRY.read().unwrap_or_else(|poisoned| poisoned.into_inner());
599    registry.clone()
600}
601
602/// Get the current time, automatically using virtual clock if available
603///
604/// This is a convenience function that checks the global registry and returns
605/// virtual time if time travel is enabled, or real time otherwise.
606/// This allows modules to automatically respect time travel without needing
607/// to explicitly pass a clock reference.
608pub fn now() -> DateTime<Utc> {
609    if let Some(clock) = get_global_clock() {
610        clock.now()
611    } else {
612        Utc::now()
613    }
614}
615
616/// Check if time travel is currently enabled globally
617///
618/// Returns `true` if a virtual clock is registered and enabled.
619pub fn is_time_travel_enabled() -> bool {
620    if let Some(clock) = get_global_clock() {
621        clock.is_enabled()
622    } else {
623        false
624    }
625}
626
627/// Global time travel manager
628pub struct TimeTravelManager {
629    /// Virtual clock
630    clock: Arc<VirtualClock>,
631    /// Response scheduler
632    scheduler: Arc<ResponseScheduler>,
633    /// Cron scheduler for recurring events
634    cron_scheduler: Arc<CronScheduler>,
635}
636
637impl TimeTravelManager {
638    /// Create a new time travel manager
639    ///
640    /// The virtual clock is automatically registered with the global registry
641    /// so it can be detected by other modules (e.g., auth, session expiration).
642    pub fn new(config: TimeTravelConfig) -> Self {
643        let clock = Arc::new(VirtualClock::new());
644
645        if config.enabled {
646            if let Some(initial_time) = config.initial_time {
647                clock.enable_and_set(initial_time);
648            } else {
649                clock.enable_and_set(Utc::now());
650            }
651            clock.set_scale(config.scale_factor);
652            // Register with global registry for automatic detection
653            register_global_clock(clock.clone());
654        }
655
656        let scheduler = Arc::new(ResponseScheduler::new(clock.clone()));
657        let cron_scheduler =
658            Arc::new(CronScheduler::new(clock.clone()).with_response_scheduler(scheduler.clone()));
659
660        Self {
661            clock,
662            scheduler,
663            cron_scheduler,
664        }
665    }
666
667    /// Get the virtual clock
668    pub fn clock(&self) -> Arc<VirtualClock> {
669        self.clock.clone()
670    }
671
672    /// Get the response scheduler
673    pub fn scheduler(&self) -> Arc<ResponseScheduler> {
674        self.scheduler.clone()
675    }
676
677    /// Get the cron scheduler
678    pub fn cron_scheduler(&self) -> Arc<CronScheduler> {
679        self.cron_scheduler.clone()
680    }
681
682    /// Get the current time (respects virtual clock if enabled)
683    pub fn now(&self) -> DateTime<Utc> {
684        self.clock.now()
685    }
686
687    /// Save current state as a scenario
688    pub fn save_scenario(&self, name: String) -> TimeScenario {
689        TimeScenario::from_manager(self, name)
690    }
691
692    /// Load and apply a scenario
693    pub fn load_scenario(&self, scenario: &TimeScenario) {
694        scenario.apply_to_manager(self);
695    }
696
697    /// Enable time travel and set the current virtual time
698    ///
699    /// This method wraps the clock's enable_and_set and updates the global registry.
700    pub fn enable_and_set(&self, time: DateTime<Utc>) {
701        self.clock.enable_and_set(time);
702        register_global_clock(self.clock.clone());
703    }
704
705    /// Disable time travel and return to using real time
706    ///
707    /// This method wraps the clock's disable and updates the global registry.
708    pub fn disable(&self) {
709        self.clock.disable();
710        unregister_global_clock();
711    }
712
713    /// Advance time by a duration
714    ///
715    /// This method wraps the clock's advance for convenience.
716    pub fn advance(&self, duration: Duration) {
717        self.clock.advance(duration);
718    }
719
720    /// Set the virtual time to a specific point
721    ///
722    /// This method wraps the clock's set_time for convenience.
723    pub fn set_time(&self, time: DateTime<Utc>) {
724        self.clock.set_time(time);
725        if self.clock.is_enabled() {
726            register_global_clock(self.clock.clone());
727        }
728    }
729
730    /// Set the time scale factor
731    ///
732    /// This method wraps the clock's set_scale for convenience.
733    pub fn set_scale(&self, factor: f64) {
734        self.clock.set_scale(factor);
735    }
736}
737
738impl Drop for TimeTravelManager {
739    fn drop(&mut self) {
740        // Unregister when manager is dropped
741        unregister_global_clock();
742    }
743}
744
745#[cfg(test)]
746mod tests {
747    use super::*;
748
749    #[test]
750    fn test_virtual_clock_creation() {
751        let clock = VirtualClock::new();
752        assert!(!clock.is_enabled());
753    }
754
755    #[test]
756    fn test_virtual_clock_enable() {
757        let clock = VirtualClock::new();
758        let test_time = Utc::now();
759        clock.enable_and_set(test_time);
760
761        assert!(clock.is_enabled());
762        let now = clock.now();
763        assert!((now - test_time).num_seconds().abs() < 1);
764    }
765
766    #[test]
767    fn test_virtual_clock_advance() {
768        let clock = VirtualClock::new();
769        let test_time = Utc::now();
770        clock.enable_and_set(test_time);
771
772        clock.advance(Duration::hours(2));
773        let now = clock.now();
774
775        assert!((now - test_time - Duration::hours(2)).num_seconds().abs() < 1);
776    }
777
778    #[test]
779    fn test_virtual_clock_scale() {
780        let clock = VirtualClock::new();
781        clock.set_scale(2.0);
782        assert_eq!(clock.get_scale(), 2.0);
783    }
784
785    #[test]
786    fn test_response_scheduler() {
787        let clock = Arc::new(VirtualClock::new());
788        let test_time = Utc::now();
789        clock.enable_and_set(test_time);
790
791        let scheduler = ResponseScheduler::new(clock.clone());
792
793        let response = ScheduledResponse {
794            id: "test-1".to_string(),
795            trigger_time: test_time + Duration::seconds(10),
796            body: serde_json::json!({"message": "Hello"}),
797            status: 200,
798            headers: HashMap::new(),
799            name: Some("test".to_string()),
800            repeat: None,
801        };
802
803        let id = scheduler.schedule(response).unwrap();
804        assert_eq!(id, "test-1");
805        assert_eq!(scheduler.count(), 1);
806    }
807
808    #[test]
809    fn test_scheduled_response_triggering() {
810        let clock = Arc::new(VirtualClock::new());
811        let test_time = Utc::now();
812        clock.enable_and_set(test_time);
813
814        let scheduler = ResponseScheduler::new(clock.clone());
815
816        let response = ScheduledResponse {
817            id: "test-1".to_string(),
818            trigger_time: test_time + Duration::seconds(10),
819            body: serde_json::json!({"message": "Hello"}),
820            status: 200,
821            headers: HashMap::new(),
822            name: None,
823            repeat: None,
824        };
825
826        scheduler.schedule(response).unwrap();
827
828        // Should not be due yet
829        let due = scheduler.get_due_responses();
830        assert_eq!(due.len(), 0);
831
832        // Advance time
833        clock.advance(Duration::seconds(15));
834
835        // Should be due now
836        let due = scheduler.get_due_responses();
837        assert_eq!(due.len(), 1);
838    }
839
840    #[test]
841    fn test_time_travel_config() {
842        let config = TimeTravelConfig::default();
843        assert!(!config.enabled);
844        assert_eq!(config.scale_factor, 1.0);
845        assert!(config.enable_scheduling);
846    }
847
848    #[test]
849    fn test_time_travel_manager() {
850        let config = TimeTravelConfig {
851            enabled: true,
852            initial_time: Some(Utc::now()),
853            scale_factor: 1.0,
854            enable_scheduling: true,
855        };
856
857        let manager = TimeTravelManager::new(config);
858        assert!(manager.clock().is_enabled());
859    }
860
861    #[test]
862    fn test_one_month_later_scenario() {
863        let clock = Arc::new(VirtualClock::new());
864        let initial_time = Utc::now();
865        clock.enable_and_set(initial_time);
866
867        // Advance by 1 month (30 days)
868        clock.advance(Duration::days(30));
869
870        let final_time = clock.now();
871        let elapsed = final_time - initial_time;
872
873        // Should be approximately 30 days
874        assert!(elapsed.num_days() >= 29 && elapsed.num_days() <= 31);
875    }
876
877    #[test]
878    fn test_scenario_save_and_load() {
879        let config = TimeTravelConfig {
880            enabled: true,
881            initial_time: Some(Utc::now()),
882            scale_factor: 2.0,
883            enable_scheduling: true,
884        };
885
886        let manager = TimeTravelManager::new(config);
887
888        // Advance time
889        manager.clock().advance(Duration::hours(24));
890
891        // Save scenario
892        let scenario = manager.save_scenario("test-scenario".to_string());
893        assert_eq!(scenario.name, "test-scenario");
894        assert!(scenario.enabled);
895        assert_eq!(scenario.scale_factor, 2.0);
896        assert!(scenario.current_time.is_some());
897
898        // Create new manager and load scenario
899        let new_config = TimeTravelConfig::default();
900        let new_manager = TimeTravelManager::new(new_config);
901
902        // Load scenario
903        new_manager.load_scenario(&scenario);
904
905        // Verify state was restored
906        assert!(new_manager.clock().is_enabled());
907        assert_eq!(new_manager.clock().get_scale(), 2.0);
908        if let Some(saved_time) = scenario.current_time {
909            let loaded_time = new_manager.clock().now();
910            // Times should be very close (within 1 second)
911            assert!((loaded_time - saved_time).num_seconds().abs() < 1);
912        }
913    }
914
915    #[test]
916    fn test_duration_parsing_month_year() {
917        // Test that month and year durations work
918        let clock = Arc::new(VirtualClock::new());
919        let initial_time = Utc::now();
920        clock.enable_and_set(initial_time);
921
922        // Advance by 1 month (should be ~30 days)
923        clock.advance(Duration::days(30));
924        let after_month = clock.now();
925        let month_elapsed = after_month - initial_time;
926        assert!(month_elapsed.num_days() >= 29 && month_elapsed.num_days() <= 31);
927
928        // Reset and advance by 1 year (should be ~365 days)
929        clock.set_time(initial_time);
930        clock.advance(Duration::days(365));
931        let after_year = clock.now();
932        let year_elapsed = after_year - initial_time;
933        assert!(year_elapsed.num_days() >= 364 && year_elapsed.num_days() <= 366);
934    }
935}
936
937// Cron scheduler module
938pub mod cron;
939
940// Re-export cron types
941pub use cron::{CronJob, CronJobAction, CronScheduler};