ddns_a/
time.rs

1//! Time abstraction for testability.
2//!
3//! This module provides a [`Clock`] trait that allows injecting mock clocks
4//! in tests while using the real system clock in production, and a [`Sleeper`]
5//! trait for injectable async delays.
6
7use std::time::{Duration, SystemTime};
8
9/// Abstraction over system time for testability.
10///
11/// Implementations provide the current time, allowing tests to inject
12/// controlled time values instead of relying on actual system time.
13///
14/// # Example
15///
16/// ```
17/// use ddns_a::time::{Clock, SystemClock};
18///
19/// let clock = SystemClock;
20/// let now = clock.now();
21/// assert!(now >= std::time::SystemTime::UNIX_EPOCH);
22/// ```
23pub trait Clock: Send + Sync {
24    /// Returns the current time.
25    fn now(&self) -> SystemTime;
26}
27
28/// Production clock using actual system time.
29///
30/// This is the default clock implementation that delegates to
31/// [`SystemTime::now()`].
32#[derive(Debug, Clone, Copy, Default)]
33pub struct SystemClock;
34
35impl Clock for SystemClock {
36    fn now(&self) -> SystemTime {
37        SystemTime::now()
38    }
39}
40
41/// Abstraction over async sleep for testability.
42///
43/// Implementations provide async delay functionality, allowing tests to
44/// inject instant/mock sleeps instead of waiting for real time.
45///
46/// # Example
47///
48/// ```
49/// use ddns_a::time::{Sleeper, TokioSleeper};
50/// use std::time::Duration;
51///
52/// async fn example() {
53///     let sleeper = TokioSleeper;
54///     sleeper.sleep(Duration::from_millis(100)).await;
55/// }
56/// ```
57pub trait Sleeper: Send + Sync {
58    /// Sleeps for the specified duration.
59    fn sleep(&self, duration: Duration) -> impl std::future::Future<Output = ()> + Send;
60}
61
62/// Production sleeper using tokio's sleep.
63///
64/// This is the default sleeper implementation that delegates to
65/// [`tokio::time::sleep`].
66#[derive(Debug, Clone, Copy, Default)]
67pub struct TokioSleeper;
68
69impl Sleeper for TokioSleeper {
70    async fn sleep(&self, duration: Duration) {
71        tokio::time::sleep(duration).await;
72    }
73}
74
75/// Mock sleeper that returns immediately without waiting.
76///
77/// Useful for testing retry logic without actual delays.
78#[derive(Debug, Clone, Copy, Default)]
79pub struct InstantSleeper;
80
81impl Sleeper for InstantSleeper {
82    async fn sleep(&self, _duration: Duration) {
83        // Return immediately - no actual sleep
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use std::sync::atomic::{AtomicU64, Ordering};
91    use std::time::Duration;
92
93    /// A mock clock for testing that returns controlled time values.
94    struct MockClock {
95        /// Seconds since `UNIX_EPOCH`, atomically updated.
96        secs: AtomicU64,
97    }
98
99    impl MockClock {
100        fn new(initial_secs: u64) -> Self {
101            Self {
102                secs: AtomicU64::new(initial_secs),
103            }
104        }
105
106        fn advance(&self, secs: u64) {
107            self.secs.fetch_add(secs, Ordering::SeqCst);
108        }
109    }
110
111    impl Clock for MockClock {
112        fn now(&self) -> SystemTime {
113            SystemTime::UNIX_EPOCH + Duration::from_secs(self.secs.load(Ordering::SeqCst))
114        }
115    }
116
117    #[test]
118    fn system_clock_returns_current_time() {
119        let clock = SystemClock;
120        let before = SystemTime::now();
121        let result = clock.now();
122        let after = SystemTime::now();
123
124        assert!(result >= before);
125        assert!(result <= after);
126    }
127
128    #[test]
129    fn system_clock_is_send_sync() {
130        fn assert_send_sync<T: Send + Sync>() {}
131        assert_send_sync::<SystemClock>();
132    }
133
134    fn assert_default<T: Default>() {}
135
136    #[test]
137    fn system_clock_is_default() {
138        assert_default::<SystemClock>();
139    }
140
141    #[test]
142    fn system_clock_is_copy() {
143        let clock1 = SystemClock;
144        let clock2 = clock1;
145        // Both are usable (Copy semantics)
146        let _ = clock1.now();
147        let _ = clock2.now();
148    }
149
150    #[test]
151    fn mock_clock_returns_controlled_time() {
152        let clock = MockClock::new(1_000_000);
153        let expected = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000_000);
154
155        assert_eq!(clock.now(), expected);
156    }
157
158    #[test]
159    fn mock_clock_can_advance() {
160        let clock = MockClock::new(0);
161
162        assert_eq!(clock.now(), SystemTime::UNIX_EPOCH);
163
164        clock.advance(100);
165        assert_eq!(
166            clock.now(),
167            SystemTime::UNIX_EPOCH + Duration::from_secs(100)
168        );
169
170        clock.advance(50);
171        assert_eq!(
172            clock.now(),
173            SystemTime::UNIX_EPOCH + Duration::from_secs(150)
174        );
175    }
176
177    #[test]
178    fn mock_clock_is_send_sync() {
179        fn assert_send_sync<T: Send + Sync>() {}
180        assert_send_sync::<MockClock>();
181    }
182
183    // Sleeper tests
184
185    #[tokio::test]
186    async fn tokio_sleeper_completes() {
187        let sleeper = TokioSleeper;
188        // Very short sleep to verify it works
189        sleeper.sleep(Duration::from_millis(1)).await;
190    }
191
192    #[test]
193    fn tokio_sleeper_is_send_sync() {
194        fn assert_send_sync<T: Send + Sync>() {}
195        assert_send_sync::<TokioSleeper>();
196    }
197
198    #[test]
199    fn tokio_sleeper_is_default() {
200        assert_default::<TokioSleeper>();
201    }
202
203    #[test]
204    fn tokio_sleeper_is_copy() {
205        let sleeper1 = TokioSleeper;
206        let sleeper2 = sleeper1;
207        // Both are usable (Copy semantics)
208        let _ = sleeper1;
209        let _ = sleeper2;
210    }
211
212    #[tokio::test]
213    async fn instant_sleeper_returns_immediately() {
214        let sleeper = InstantSleeper;
215        let start = std::time::Instant::now();
216        sleeper.sleep(Duration::from_secs(1000)).await;
217        // Should complete almost instantly
218        assert!(start.elapsed() < Duration::from_millis(100));
219    }
220
221    #[test]
222    fn instant_sleeper_is_send_sync() {
223        fn assert_send_sync<T: Send + Sync>() {}
224        assert_send_sync::<InstantSleeper>();
225    }
226
227    #[test]
228    fn instant_sleeper_is_default() {
229        assert_default::<InstantSleeper>();
230    }
231
232    #[test]
233    fn instant_sleeper_is_copy() {
234        let sleeper1 = InstantSleeper;
235        let sleeper2 = sleeper1;
236        // Both are usable (Copy semantics)
237        let _ = sleeper1;
238        let _ = sleeper2;
239    }
240}