Skip to main content

browser_automation_cli/
clock.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Injectable clock for deterministic tests (rules: dependency inversion).
3//!
4//! Production paths use [`SystemClock`](crate::clock::SystemClock).
5//! Tests may inject [`FixedClock`](crate::clock::FixedClock).
6
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9/// Wall-clock abstraction for pure domain logic and tests.
10pub trait Clock: Send + Sync {
11    /// Current time as Unix epoch milliseconds.
12    fn now_unix_ms(&self) -> u64;
13
14    /// Current [`SystemTime`].
15    fn now(&self) -> SystemTime {
16        UNIX_EPOCH + Duration::from_millis(self.now_unix_ms())
17    }
18}
19
20/// Real wall clock (production default).
21#[derive(Debug, Default, Clone, Copy)]
22pub struct SystemClock;
23
24impl Clock for SystemClock {
25    fn now_unix_ms(&self) -> u64 {
26        SystemTime::now()
27            .duration_since(UNIX_EPOCH)
28            .map(|d| d.as_millis() as u64)
29            .unwrap_or(0)
30    }
31}
32
33/// Fixed clock for deterministic unit tests.
34#[derive(Debug, Clone, Copy)]
35pub struct FixedClock {
36    /// Unix epoch milliseconds returned by every call.
37    pub unix_ms: u64,
38}
39
40impl Clock for FixedClock {
41    fn now_unix_ms(&self) -> u64 {
42        self.unix_ms
43    }
44}
45
46/// Process-wide convenience: system clock milliseconds (no global mutability).
47pub fn system_unix_ms() -> u64 {
48    SystemClock.now_unix_ms()
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn fixed_clock_is_stable() {
57        let c = FixedClock { unix_ms: 1_700_000_000_000 };
58        assert_eq!(c.now_unix_ms(), 1_700_000_000_000);
59        assert_eq!(c.now_unix_ms(), c.now_unix_ms());
60    }
61
62    #[test]
63    fn system_clock_is_nonzero() {
64        assert!(SystemClock.now_unix_ms() > 0);
65    }
66}