tiny-counter 0.1.0

Track event counts across time windows with fixed memory and fast queries
Documentation
//! Tests for .sum() query method

use chrono::{Duration, Utc};
use std::sync::Arc;
use tiny_counter::{EventStore, TestClock};

#[test]
fn query_sum_aggregation() {
    let store = EventStore::new();

    store.record_count("event", 10);
    store.record_count("event", 20);
    store.record_count("event", 30);

    let sum = store.query("event").last_days(1).sum();
    assert_eq!(sum, Some(60));
}

#[test]
fn query_sum_with_time_windows() {
    let fixed_time = Utc::now();
    let clock = TestClock::build_for_testing_at(fixed_time);

    let store = EventStore::builder()
        .with_clock(Arc::new(clock.clone()))
        .track_hours(24)
        .build()
        .unwrap();

    // Record event now
    store.record("event");

    // Advance clock 2 hours
    clock.advance(Duration::hours(2));

    // Record another event
    store.record("event");

    // Last hour should only see the recent event
    assert_eq!(store.query("event").last_hours(1).sum(), Some(1));

    // Last 3 hours should see both
    assert_eq!(store.query("event").last_hours(3).sum(), Some(2));
}

#[test]
fn query_many_events_sum() {
    let store = EventStore::new();

    store.record_count("event1", 10);
    store.record_count("event2", 20);
    store.record_count("event3", 30);

    let total = store
        .query_many(&["event1", "event2", "event3"])
        .last_days(1)
        .sum();

    assert_eq!(total, Some(60));
}

#[test]
fn query_many_with_nonexistent_event() {
    let store = EventStore::new();

    store.record_count("event1", 10);
    store.record_count("event2", 20);

    let total = store
        .query_many(&["event1", "event2", "nonexistent"])
        .last_days(1)
        .sum();

    // Should sum existing events only
    assert_eq!(total, Some(30));
}

#[test]
fn query_ever_returns_all_data() {
    let store = EventStore::new();

    store.record_count("event", 100);

    let count = store.query("event").ever().sum();
    assert_eq!(count, Some(100));
}

#[test]
fn query_all_time_units() {
    // Explicitly configure all 6 time units to test all query methods work
    let store = EventStore::builder()
        .track_minutes(60)
        .track_hours(72)
        .track_days(56)
        .track_weeks(52)
        .track_months(12)
        .track_years(4)
        .build()
        .unwrap();

    for _ in 0..5 {
        store.record("test_event");
    }

    // All time units should return the same count for recent events
    assert_eq!(store.query("test_event").last_minutes(1).sum(), Some(5));
    assert_eq!(store.query("test_event").last_hours(1).sum(), Some(5));
    assert_eq!(store.query("test_event").last_days(1).sum(), Some(5));
    assert_eq!(store.query("test_event").last_weeks(1).sum(), Some(5));
    assert_eq!(store.query("test_event").last_months(1).sum(), Some(5));
    assert_eq!(store.query("test_event").last_years(1).sum(), Some(5));
}

#[test]
fn query_nonexistent_event_returns_none() {
    let store = EventStore::new();

    let count = store.query("nonexistent").last_days(1).sum();
    assert_eq!(count, None);
}

#[test]
fn query_empty_store() {
    let store = EventStore::new();

    assert_eq!(store.query("any").last_days(1).sum(), None);
    assert_eq!(store.query("any").last_hours(1).sum(), None);
    assert_eq!(store.query("any").ever().sum(), None);
}

#[test]
fn query_event_outside_window() {
    let fixed_time = Utc::now();
    let clock = TestClock::build_for_testing_at(fixed_time);

    let store = EventStore::builder()
        .with_clock(Arc::new(clock.clone()))
        .track_hours(24)
        .build()
        .unwrap();

    store.record("event");

    // Advance clock way past the tracking window
    clock.advance(Duration::hours(48));

    // Event should have rotated out
    let count = store.query("event").last_hours(24).sum();
    assert_eq!(count, Some(0));
}

#[test]
fn large_count_aggregations() {
    let store = EventStore::new();

    store.record_count("large", u32::MAX / 2);

    // Average should handle large values
    let avg = store.query("large").last_days(1).average();
    assert!(avg.is_some());
}

#[test]
fn very_old_event_outside_retention() {
    let store = EventStore::new();

    // Record event from 10 years ago (outside default 12-month retention)
    let long_ago = Utc::now() - Duration::days(3650);
    store.record_at("ancient_event", long_ago).unwrap();

    // Event is outside retention window
    let sum = store.query("ancient_event").ever().sum();
    assert!(sum == Some(0) || sum.is_none());
}

#[test]
fn events_spanning_years() {
    // Configure with sufficient retention to cover 3 years of events
    let store = EventStore::builder().track_years(4).build().unwrap();

    let now = Utc::now();

    // Record events over past 3 years (within 4-year retention)
    for i in 0..3 {
        let timestamp = now - Duration::days(365 * i);
        store.record_at("yearly_event", timestamp).unwrap();
    }

    let sum = store.query("yearly_event").ever().sum();
    assert_eq!(sum, Some(3));
}

#[test]
fn query_zero_time_range() {
    let store = EventStore::new();
    store.record("event");

    // last_days(0) behavior
    let sum = store.query("event").last_days(0).sum();
    assert!(sum.is_some());
}