hydracache 0.54.0

User-facing HydraCache runtime crate.
Documentation
use hydracache::{
    ClusterEpoch, HybridLogicalClock, PartitionId, PartitionKey, SessionContextMetrics, SessionId,
    SessionRequest, SessionToken, SessionTokenError, SessionWatermark, VersionStamp,
};

fn stamp(version: u64) -> VersionStamp {
    VersionStamp::new(
        version,
        ClusterEpoch::new(1),
        HybridLogicalClock::new(version, 0),
    )
}

fn key(partition: u32, region: &str) -> PartitionKey {
    PartitionKey::new(PartitionId::new(partition), region)
}

#[test]
fn session_context_watermark_observe_and_covers_roundtrip() {
    let mut watermark = SessionWatermark::new(4);
    let key = key(7, "eu");

    watermark.observe(key.clone(), stamp(5));

    assert!(watermark.covers(&key, stamp(5)));
    assert!(watermark.covers(&key, stamp(4)));
    assert!(!watermark.covers(&key, stamp(6)));
}

#[test]
fn session_context_watermark_is_bounded_and_coarsens_when_full() {
    let mut watermark = SessionWatermark::new(2);

    watermark.observe(key(1, "eu"), stamp(1));
    watermark.observe(key(2, "eu"), stamp(2));
    watermark.observe(key(3, "us"), stamp(3));

    assert_eq!(watermark.len(), 2);
    assert_eq!(watermark.cap(), 2);
    assert_eq!(watermark.coarsened_total(), 1);
    assert!(!watermark.covers(&key(1, "eu"), stamp(1)));
    assert!(watermark.covers(&key(3, "us"), stamp(3)));
}

#[test]
fn session_context_forged_or_replayed_token_is_rejected() {
    let secret = b"test-secret";
    let session = SessionId::new("session-a");
    let token = SessionToken::issue(session.clone(), SessionWatermark::new(4), 7, 10, secret);

    assert!(token.verify(&session, secret, 7).is_ok());
    assert_eq!(
        token.clone().forged().verify(&session, secret, 7),
        Err(SessionTokenError::Forged)
    );
    assert_eq!(
        token.verify(&session, secret, 8),
        Err(SessionTokenError::Replayed)
    );
    assert_eq!(
        token.verify(&SessionId::new("session-b"), secret, 7),
        Err(SessionTokenError::WrongSession)
    );
}

#[test]
fn session_context_sessionless_path_is_unchanged() {
    let request = SessionRequest::Sessionless;

    assert!(request.is_sessionless());
}

#[test]
fn session_context_metrics_reflect_watermark_shape() {
    let mut watermark = SessionWatermark::new(1);
    watermark.observe(key(1, "eu"), stamp(1));
    watermark.observe(key(2, "us"), stamp(2));

    let metrics = SessionContextMetrics::from(&watermark);

    assert_eq!(metrics.session_watermark_entries, 1);
    assert_eq!(metrics.session_watermark_coarsened_total, 1);
}