tiny-counter 0.1.0

Track event counts across time windows with fixed memory and fast queries
Documentation
//! Tests for schedule-based rate limiting constraints (.during(), .outside_of())

use tiny_counter::{EventStore, Schedule};

// ============================================================================
// Schedule Constraints - Business Hours
// ============================================================================

#[test]
fn during_business_hours() {
    use chrono::{NaiveDate, TimeZone, Utc};
    use std::sync::Arc;
    use tiny_counter::TestClock;

    // Set time to 10am on a weekday
    let business_time = Utc.from_utc_datetime(
        &NaiveDate::from_ymd_opt(2024, 6, 17) // Monday
            .unwrap()
            .and_hms_opt(10, 0, 0)
            .unwrap(),
    );
    let clock = TestClock::build_for_testing_at(business_time);

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

    let schedule = Schedule::hours(9, 17).unwrap();

    let result = store.limit().during(schedule).check("action");
    assert!(result.is_ok());
}

#[test]
fn outside_business_hours_rejects() {
    use chrono::{NaiveDate, TimeZone, Utc};
    use std::sync::Arc;
    use tiny_counter::TestClock;

    // Set time to 8pm (after business hours)
    let after_hours = Utc.from_utc_datetime(
        &NaiveDate::from_ymd_opt(2024, 6, 17)
            .unwrap()
            .and_hms_opt(20, 0, 0)
            .unwrap(),
    );
    let clock = TestClock::build_for_testing_at(after_hours);

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

    let schedule = Schedule::hours(9, 17).unwrap();

    let result = store.limit().during(schedule).check("action");
    assert!(result.is_err());
}

#[test]
fn outside_of_schedule_allows_after_hours() {
    use chrono::{NaiveDate, TimeZone, Utc};
    use std::sync::Arc;
    use tiny_counter::TestClock;

    // Set time to 8pm (after business hours)
    let after_hours = Utc.from_utc_datetime(
        &NaiveDate::from_ymd_opt(2024, 6, 17)
            .unwrap()
            .and_hms_opt(20, 0, 0)
            .unwrap(),
    );
    let clock = TestClock::build_for_testing_at(after_hours);

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

    let schedule = Schedule::hours(9, 17).unwrap();

    // Should be allowed outside business hours
    let result = store.limit().outside_of(schedule).check("maintenance");
    assert!(result.is_ok());
}

// ============================================================================
// Schedule Constraints - Weekdays/Weekends
// ============================================================================

#[test]
fn during_weekdays() {
    use chrono::{NaiveDate, TimeZone, Utc};
    use std::sync::Arc;
    use tiny_counter::TestClock;

    // Set time to Monday
    let monday = Utc.from_utc_datetime(
        &NaiveDate::from_ymd_opt(2024, 6, 17)
            .unwrap()
            .and_hms_opt(12, 0, 0)
            .unwrap(),
    );
    let clock = TestClock::build_for_testing_at(monday);

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

    let result = store
        .limit()
        .during(Schedule::weekdays())
        .check("work_task");
    assert!(result.is_ok());
}

#[test]
fn during_weekends() {
    use chrono::{NaiveDate, TimeZone, Utc};
    use std::sync::Arc;
    use tiny_counter::TestClock;

    // Set time to Saturday
    let saturday = Utc.from_utc_datetime(
        &NaiveDate::from_ymd_opt(2024, 6, 15)
            .unwrap()
            .and_hms_opt(12, 0, 0)
            .unwrap(),
    );
    let clock = TestClock::build_for_testing_at(saturday);

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

    let result = store
        .limit()
        .during(Schedule::weekends())
        .check("leisure_activity");
    assert!(result.is_ok());
}

#[test]
fn weekdays_rejects_weekend() {
    use chrono::{NaiveDate, TimeZone, Utc};
    use std::sync::Arc;
    use tiny_counter::TestClock;

    // Set time to Sunday
    let sunday = Utc.from_utc_datetime(
        &NaiveDate::from_ymd_opt(2024, 6, 16)
            .unwrap()
            .and_hms_opt(12, 0, 0)
            .unwrap(),
    );
    let clock = TestClock::build_for_testing_at(sunday);

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

    let result = store
        .limit()
        .during(Schedule::weekdays())
        .check("work_task");
    assert!(result.is_err());
}

// ============================================================================
// Schedule Validation
// ============================================================================

#[test]
fn invalid_schedule_hours_rejected() {
    // Hour > 23 should fail
    let result = Schedule::hours(9, 25);
    assert!(result.is_err());

    // Start >= end should fail
    let result = Schedule::hours(17, 9);
    assert!(result.is_err());

    // Equal hours should fail
    let result = Schedule::hours(9, 9);
    assert!(result.is_err());
}