tiny-counter 0.1.0

Track event counts across time windows with fixed memory and fast queries
Documentation
//! Merge operation tests - combining counters from multiple stores.
//!
//! Tests merge_event() and merge_all() for distributed system scenarios.

use chrono::Duration;
use tiny_counter::EventStore;

#[test]
fn merge_event_combines_counts() {
    let store1 = EventStore::new();
    store1.record_count("event", 100);

    let store2 = EventStore::new();
    store2.record_count("event", 50);

    let exported = store2.export_all().unwrap();
    let counter = exported.get("event").unwrap().clone();

    store1.merge_event("event", counter).unwrap();

    // Counts should be combined
    assert_eq!(store1.query("event").last_days(1).sum(), Some(150));
}

#[test]
fn merge_all_combines_multiple_events() {
    let store1 = EventStore::new();
    store1.record_count("event1", 10);
    store1.record_count("event2", 20);

    let store2 = EventStore::new();
    store2.record_count("event1", 5);
    store2.record_count("event3", 30);

    let exported = store2.export_all().unwrap();
    store1.merge_all(exported).unwrap();

    assert_eq!(store1.query("event1").last_days(1).sum(), Some(15)); // Combined
    assert_eq!(store1.query("event2").last_days(1).sum(), Some(20)); // Unchanged
    assert_eq!(store1.query("event3").last_days(1).sum(), Some(30)); // New
}

#[test]
fn merge_with_empty_store_is_identity() {
    let store = EventStore::new();
    store.record_count("event", 100);

    let sum_before = store.query("event").last_days(1).sum();

    let empty_store = EventStore::new();
    let exported = empty_store.export_all().unwrap();
    store.merge_all(exported).unwrap();

    let sum_after = store.query("event").last_days(1).sum();

    assert_eq!(sum_before, sum_after);
}

#[test]
fn merge_is_commutative() {
    let store_a1 = EventStore::new();
    store_a1.record_count("event", 50);

    let store_b1 = EventStore::new();
    store_b1.record_count("event", 30);

    // A + B
    let exported_b = store_b1.export_all().unwrap();
    store_a1.merge_all(exported_b).unwrap();
    let sum_ab = store_a1.query("event").last_days(1).sum();

    // B + A
    let store_a2 = EventStore::new();
    store_a2.record_count("event", 50);

    let store_b2 = EventStore::new();
    store_b2.record_count("event", 30);

    let exported_a = store_a2.export_all().unwrap();
    store_b2.merge_all(exported_a).unwrap();
    let sum_ba = store_b2.query("event").last_days(1).sum();

    assert_eq!(sum_ab, sum_ba);
}

#[test]
fn merge_is_associative() {
    // (A + B) + C
    let store_a1 = EventStore::new();
    store_a1.record_count("event", 10);

    let store_b1 = EventStore::new();
    store_b1.record_count("event", 20);

    let store_c1 = EventStore::new();
    store_c1.record_count("event", 30);

    let exported_b1 = store_b1.export_all().unwrap();
    store_a1.merge_all(exported_b1).unwrap();
    let exported_c1 = store_c1.export_all().unwrap();
    store_a1.merge_all(exported_c1).unwrap();

    let sum_abc = store_a1.query("event").last_days(1).sum();

    // A + (B + C)
    let store_a2 = EventStore::new();
    store_a2.record_count("event", 10);

    let store_b2 = EventStore::new();
    store_b2.record_count("event", 20);

    let store_c2 = EventStore::new();
    store_c2.record_count("event", 30);

    let exported_c2 = store_c2.export_all().unwrap();
    store_b2.merge_all(exported_c2).unwrap();
    let exported_bc = store_b2.export_all().unwrap();
    store_a2.merge_all(exported_bc).unwrap();

    let sum_abc2 = store_a2.query("event").last_days(1).sum();

    assert_eq!(sum_abc, sum_abc2);
}

#[test]
fn merge_preserves_first_seen() {
    use std::sync::Arc;
    use tiny_counter::TestClock;

    let fixed_time = chrono::Utc::now();
    let clock = TestClock::build_for_testing_at(fixed_time);

    // Store 1: older event
    let store1 = EventStore::builder()
        .with_clock(Arc::new(clock.clone()))
        .build()
        .unwrap();

    store1.record("event");

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

    // Store 2: newer event
    let store2 = EventStore::builder()
        .with_clock(Arc::new(clock.clone()))
        .build()
        .unwrap();

    store2.record("event");

    // Merge store2 into store1
    let exported = store2.export_all().unwrap();
    store1.merge_all(exported).unwrap();

    // first_seen should be from the older store1 event
    let first_seen = store1.query("event").first_seen();
    assert!(first_seen.is_some());

    // Should be ~6 hours ago
    let duration = first_seen.unwrap();
    assert!(duration.num_hours() >= 5 && duration.num_hours() <= 7);
}