use async_trait::async_trait;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use tokio::time::sleep as tokio_sleep;
#[async_trait]
pub trait TimeProvider: Send + Sync + std::fmt::Debug {
async fn sleep(&self, duration: Duration);
fn now(&self) -> SystemTime;
fn instant(&self) -> Instant;
fn should_skip_delays(&self) -> bool {
false
}
}
#[derive(Debug, Clone, Default)]
pub struct RealTimeProvider;
impl RealTimeProvider {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl TimeProvider for RealTimeProvider {
async fn sleep(&self, duration: Duration) {
tokio_sleep(duration).await;
}
fn now(&self) -> SystemTime {
SystemTime::now()
}
fn instant(&self) -> Instant {
Instant::now()
}
}
#[derive(Debug, Clone, Default)]
pub struct MockTimeProvider {
skip_delays: bool,
}
impl MockTimeProvider {
pub fn new() -> Self {
Self { skip_delays: true }
}
pub fn with_real_delays() -> Self {
Self { skip_delays: false }
}
}
#[async_trait]
impl TimeProvider for MockTimeProvider {
async fn sleep(&self, duration: Duration) {
if !self.skip_delays {
tokio_sleep(duration).await;
} else if duration > Duration::from_millis(1) {
tokio_sleep(Duration::from_millis(1)).await;
}
}
fn now(&self) -> SystemTime {
SystemTime::now()
}
fn instant(&self) -> Instant {
Instant::now()
}
fn should_skip_delays(&self) -> bool {
self.skip_delays
}
}
pub type SharedTimeProvider = Arc<dyn TimeProvider>;
pub fn production_time_provider() -> SharedTimeProvider {
Arc::new(RealTimeProvider::new())
}
pub fn test_time_provider() -> SharedTimeProvider {
Arc::new(MockTimeProvider::new())
}
pub fn integration_test_time_provider() -> SharedTimeProvider {
Arc::new(MockTimeProvider::with_real_delays())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mock_time_provider_skips_delays() {
let provider = MockTimeProvider::new();
let start = Instant::now();
provider.sleep(Duration::from_secs(10)).await;
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_millis(100),
"Mock sleep took too long: {elapsed:?}"
);
}
#[tokio::test]
async fn test_real_time_provider_actually_sleeps() {
let provider = RealTimeProvider::new();
let start = Instant::now();
provider.sleep(Duration::from_millis(50)).await;
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(50),
"Real sleep was too short: {elapsed:?}"
);
}
}