#[cfg(any(test, feature = "test-seams"))]
use crate::GatewardenError;
use chrono::{DateTime, Utc};
pub trait Clock: Send + Sync {
fn now_utc(&self) -> DateTime<Utc>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now_utc(&self) -> DateTime<Utc> {
Utc::now()
}
}
#[cfg(any(test, feature = "test-seams"))]
#[derive(Debug, Clone)]
pub struct MockClock {
now: DateTime<Utc>,
}
#[cfg(any(test, feature = "test-seams"))]
impl MockClock {
pub fn new(now: DateTime<Utc>) -> Self {
Self { now }
}
pub fn from_rfc3339(s: &str) -> Result<Self, GatewardenError> {
let parsed = DateTime::parse_from_rfc3339(s).map_err(|e| {
GatewardenError::ProtocolError(format!("invalid RFC 3339 timestamp: {e}"))
})?;
Ok(Self {
now: parsed.with_timezone(&Utc),
})
}
pub fn advance(&mut self, duration: chrono::Duration) {
self.now += duration;
}
}
#[cfg(any(test, feature = "test-seams"))]
impl Clock for MockClock {
fn now_utc(&self) -> DateTime<Utc> {
self.now
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Datelike;
#[test]
fn system_clock_returns_time() {
let clock = SystemClock;
let now = clock.now_utc();
assert!(now.year() >= 2024);
}
#[test]
fn mock_clock_is_deterministic() {
let clock = MockClock::from_rfc3339("2025-01-15T12:00:00Z").unwrap();
assert_eq!(clock.now_utc().to_rfc3339(), "2025-01-15T12:00:00+00:00");
assert_eq!(clock.now_utc().to_rfc3339(), "2025-01-15T12:00:00+00:00");
}
#[test]
fn mock_clock_advances() {
let mut clock = MockClock::from_rfc3339("2025-01-15T12:00:00Z").unwrap();
clock.advance(chrono::Duration::hours(1));
assert_eq!(clock.now_utc().to_rfc3339(), "2025-01-15T13:00:00+00:00");
}
}