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}