tiny-counter 0.1.0

Track event counts across time windows with fixed memory and fast queries
Documentation
use std::sync::{Arc, Mutex};

use chrono::{DateTime, Duration, Utc};

use crate::Clock;

/// Clock implementations for time abstraction.
///
/// System clock that returns the current real time.
#[derive(Clone)]
pub struct SystemClock;

impl SystemClock {
    /// Creates a new SystemClock wrapped in Arc for sharing.
    #[allow(clippy::new_ret_no_self)]
    pub fn new() -> Arc<dyn Clock> {
        Arc::new(Self)
    }
}

impl Default for SystemClock {
    fn default() -> Self {
        Self
    }
}

impl Clock for SystemClock {
    fn now(&self) -> DateTime<Utc> {
        Utc::now()
    }
}

/// Test clock that allows manual time control for testing.
#[derive(Clone)]
pub struct TestClock {
    pub(crate) time: Arc<Mutex<DateTime<Utc>>>,
}

impl TestClock {
    /// Creates a new TestClock at the current time.
    #[allow(clippy::new_ret_no_self)]
    pub fn new() -> Arc<dyn Clock> {
        Arc::new(Self {
            time: Arc::new(Mutex::new(Utc::now())),
        })
    }

    /// Creates a new TestClock at a specific time.
    pub fn new_at(time: DateTime<Utc>) -> Arc<dyn Clock> {
        Arc::new(Self {
            time: Arc::new(Mutex::new(time)),
        })
    }

    /// Creates a concrete TestClock instance for direct use in tests.
    pub fn build_for_testing() -> Self {
        Self {
            time: Arc::new(Mutex::new(Utc::now())),
        }
    }

    /// Creates a concrete TestClock instance at a specific time for tests.
    pub fn build_for_testing_at(time: DateTime<Utc>) -> Self {
        Self {
            time: Arc::new(Mutex::new(time)),
        }
    }

    /// Advances the clock by the given duration.
    pub fn advance(&self, duration: Duration) {
        let mut time = self.time.lock().unwrap();
        *time += duration;
    }

    /// Sets the clock to a specific time.
    pub fn set(&self, new_time: DateTime<Utc>) {
        let mut time = self.time.lock().unwrap();
        *time = new_time;
    }
}

impl Default for TestClock {
    fn default() -> Self {
        Self {
            time: Arc::new(Mutex::new(Utc::now())),
        }
    }
}

impl Clock for TestClock {
    fn now(&self) -> DateTime<Utc> {
        *self.time.lock().unwrap()
    }
}

#[cfg(test)]
mod tests {
    use std::{
        sync::{Arc, Mutex},
        thread,
    };

    use chrono::TimeZone;

    use super::*;

    #[test]
    fn test_system_clock_new() {
        let clock = SystemClock::new();
        let _ = clock.now(); // Should not panic
    }

    #[test]
    fn test_system_clock_returns_real_time() {
        let clock = SystemClock::new();
        let before = Utc::now();
        let clock_time = clock.now();
        let after = Utc::now();

        // Clock time should be between before and after (within 1 second tolerance)
        assert!(clock_time >= before - Duration::seconds(1));
        assert!(clock_time <= after + Duration::seconds(1));
    }

    #[test]
    fn test_system_clock_is_send_sync() {
        fn assert_send<T: Send>() {}
        fn assert_sync<T: Sync>() {}
        assert_send::<SystemClock>();
        assert_sync::<SystemClock>();
    }

    #[test]
    fn test_test_clock_new() {
        let clock = TestClock::new();
        let _ = clock.now(); // Should not panic
    }

    #[test]
    fn test_test_clock_new_at() {
        let time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
        let clock = TestClock::new_at(time);
        assert_eq!(clock.now(), time);
    }

    #[test]
    fn test_test_clock_advance() {
        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();

        // Create TestClock directly to access advance method
        let test_clock = TestClock {
            time: Arc::new(Mutex::new(start_time)),
        };

        test_clock.advance(Duration::hours(5));
        let expected = start_time + Duration::hours(5);
        assert_eq!(test_clock.now(), expected);
    }

    #[test]
    fn test_test_clock_advance_is_additive() {
        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
        let test_clock = TestClock {
            time: Arc::new(Mutex::new(start_time)),
        };

        test_clock.advance(Duration::hours(2));
        test_clock.advance(Duration::hours(3));

        let expected = start_time + Duration::hours(5);
        assert_eq!(test_clock.now(), expected);
    }

    #[test]
    fn test_test_clock_set() {
        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
        let test_clock = TestClock {
            time: Arc::new(Mutex::new(start_time)),
        };

        let new_time = Utc.with_ymd_and_hms(2025, 6, 15, 12, 30, 0).unwrap();
        test_clock.set(new_time);

        assert_eq!(test_clock.now(), new_time);
    }

    #[test]
    fn test_test_clock_is_clone() {
        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
        let test_clock = TestClock {
            time: Arc::new(Mutex::new(start_time)),
        };

        let clock2 = test_clock.clone();

        // Both should share the same time
        test_clock.advance(Duration::hours(1));
        assert_eq!(clock2.now(), start_time + Duration::hours(1));
    }

    #[test]
    fn test_test_clock_concurrent_access() {
        let start_time = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
        let test_clock = TestClock {
            time: Arc::new(Mutex::new(start_time)),
        };

        let clock1 = test_clock.clone();
        let clock2 = test_clock.clone();

        let handle1 = thread::spawn(move || {
            clock1.advance(Duration::hours(1));
        });

        let handle2 = thread::spawn(move || {
            clock2.advance(Duration::hours(2));
        });

        handle1.join().unwrap();
        handle2.join().unwrap();

        // Total advancement should be 3 hours
        assert_eq!(test_clock.now(), start_time + Duration::hours(3));
    }

    #[test]
    fn test_arc_dyn_clock_pattern() {
        // Test that Arc<dyn Clock> works correctly
        let clock: Arc<dyn Clock> = SystemClock::new();
        let _ = clock.now();

        let test_clock: Arc<dyn Clock> = TestClock::new();
        let _ = test_clock.now();
    }
}