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