Skip to main content

mcp_execution_server/
clock.rs

1//! Injectable clock abstraction for session expiry.
2//!
3//! Production code uses [`SystemClock`], a zero-cost wrapper around
4//! [`Utc::now`](chrono::Utc::now). Tests can inject a fake clock to
5//! deterministically exercise expiry boundaries instead of rewinding
6//! [`PendingGeneration::expires_at`](crate::types::PendingGeneration::expires_at)
7//! after construction.
8
9use chrono::{DateTime, Utc};
10use std::fmt::Debug;
11
12/// Provides the current time for session expiry calculations.
13///
14/// Implementors must be [`Send`] + [`Sync`] so they can be shared across
15/// async tasks via `Arc<dyn Clock>`.
16///
17/// # Examples
18///
19/// ```
20/// use mcp_execution_server::clock::{Clock, SystemClock};
21///
22/// let clock = SystemClock;
23/// assert!(clock.now().timestamp() > 0);
24/// ```
25pub trait Clock: Send + Sync + Debug {
26    /// Returns the current time.
27    fn now(&self) -> DateTime<Utc>;
28}
29
30/// Real-time [`Clock`] backed by [`Utc::now`].
31///
32/// This is the default clock used in production; it matches the previous
33/// unconditional `Utc::now()` calls exactly.
34///
35/// # Examples
36///
37/// ```
38/// use mcp_execution_server::clock::{Clock, SystemClock};
39///
40/// let clock = SystemClock;
41/// assert!(clock.now().timestamp() > 0);
42/// ```
43#[derive(Debug, Clone, Copy, Default)]
44pub struct SystemClock;
45
46impl Clock for SystemClock {
47    fn now(&self) -> DateTime<Utc> {
48        Utc::now()
49    }
50}
51
52#[cfg(test)]
53pub use test_support::TestClock;
54
55#[cfg(test)]
56mod test_support {
57    use super::Clock;
58    use chrono::{DateTime, Duration, Utc};
59    use std::sync::{Arc, Mutex};
60
61    /// Fake clock for tests: holds a mutable timestamp that can be advanced
62    /// or set directly, so expiry boundaries can be exercised deterministically
63    /// without rewinding `expires_at` after construction.
64    ///
65    /// Cloning shares the underlying timestamp (`Arc<Mutex<_>>`), so a clone
66    /// handed to a `StateManager` or `PendingGeneration` observes the same
67    /// advances as the original.
68    #[derive(Debug, Clone)]
69    pub struct TestClock(Arc<Mutex<DateTime<Utc>>>);
70
71    impl TestClock {
72        /// Creates a fake clock fixed at `now`.
73        #[must_use]
74        pub fn new(now: DateTime<Utc>) -> Self {
75            Self(Arc::new(Mutex::new(now)))
76        }
77
78        /// Sets the clock to an arbitrary point in time.
79        ///
80        /// # Panics
81        ///
82        /// Panics if the internal mutex is poisoned.
83        pub fn set(&self, now: DateTime<Utc>) {
84            *self.0.lock().unwrap() = now;
85        }
86
87        /// Advances the clock forward by `duration`.
88        ///
89        /// # Panics
90        ///
91        /// Panics if the internal mutex is poisoned.
92        pub fn advance(&self, duration: Duration) {
93            let mut guard = self.0.lock().unwrap();
94            *guard += duration;
95        }
96    }
97
98    impl Clock for TestClock {
99        fn now(&self) -> DateTime<Utc> {
100            *self.0.lock().unwrap()
101        }
102    }
103}