ddd_toolkit_core/mock/
clock.rs1use crate::port::clock::Clock;
2
3#[derive(Debug)]
6pub struct FixedClock(std::sync::Mutex<chrono::DateTime<chrono::Utc>>);
7
8impl FixedClock {
9 pub fn new(now: chrono::DateTime<chrono::Utc>) -> Self {
12 Self(std::sync::Mutex::new(now))
13 }
14
15 pub fn set(&self, now: chrono::DateTime<chrono::Utc>) {
17 *self.0.lock().unwrap_or_else(|e| e.into_inner()) = now;
18 }
19}
20
21impl Clock for FixedClock {
22 fn now(&self) -> chrono::DateTime<chrono::Utc> {
23 *self.0.lock().unwrap_or_else(|e| e.into_inner())
24 }
25}
26
27#[cfg(test)]
28mod test {
29 use super::*;
30
31 fn utc(rfc3339: &str) -> chrono::DateTime<chrono::Utc> {
32 chrono::DateTime::parse_from_rfc3339(rfc3339)
33 .unwrap()
34 .with_timezone(&chrono::Utc)
35 }
36
37 #[test]
38 fn new_then_now_returns_configured_time() {
39 let time = utc("2026-07-19T00:00:00Z");
40 let clock = FixedClock::new(time);
41
42 assert_eq!(clock.now(), time);
43 }
44
45 #[test]
46 fn now_returns_equal_time_on_repeated_calls() {
47 let time = utc("2026-07-19T00:00:00Z");
48 let clock = FixedClock::new(time);
49
50 assert_eq!(clock.now(), time);
51 assert_eq!(clock.now(), time);
52 assert_eq!(clock.now(), time);
53 }
54
55 #[test]
56 fn set_changes_time_returned_by_now() {
57 let clock = FixedClock::new(utc("2026-07-19T00:00:00Z"));
58
59 let later = utc("2026-07-20T12:34:56Z");
60 clock.set(later);
61
62 assert_eq!(clock.now(), later);
63 }
64
65 #[test]
66 fn set_from_another_thread_is_visible_to_now() {
67 let clock = std::sync::Arc::new(FixedClock::new(utc("2026-07-19T00:00:00Z")));
68
69 let later = utc("2026-07-20T12:34:56Z");
70 let writer = std::sync::Arc::clone(&clock);
71 std::thread::spawn(move || writer.set(later))
72 .join()
73 .unwrap();
74
75 assert_eq!(clock.now(), later);
76 }
77
78 #[test]
79 fn box_dyn_clock_is_usable() {
80 let time = utc("2026-07-19T00:00:00Z");
81 let clock: Box<dyn Clock> = Box::new(FixedClock::new(time));
82
83 assert_eq!(clock.now(), time);
84 }
85
86 #[test]
87 fn debug_format_contains_type_name() {
88 let clock = FixedClock::new(utc("2026-07-19T00:00:00Z"));
89
90 assert!(format!("{clock:?}").contains("FixedClock"));
91 }
92
93 #[test]
94 fn now_returns_extreme_timestamps_as_is() {
95 let clock = FixedClock::new(chrono::DateTime::<chrono::Utc>::MIN_UTC);
96 assert_eq!(clock.now(), chrono::DateTime::<chrono::Utc>::MIN_UTC);
97
98 clock.set(chrono::DateTime::<chrono::Utc>::MAX_UTC);
99 assert_eq!(clock.now(), chrono::DateTime::<chrono::Utc>::MAX_UTC);
100 }
101
102 #[test]
105 fn clock_recovers_from_poisoned_mutex() {
106 let time = utc("2026-07-19T00:00:00Z");
107 let clock = FixedClock::new(time);
108
109 let poisoned = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
110 let _guard = clock.0.lock().unwrap();
111 panic!("simulated panic while holding the lock");
112 }));
113 assert!(poisoned.is_err());
114 assert!(clock.0.is_poisoned());
115
116 assert_eq!(clock.now(), time);
117 }
118}