hydracache 0.52.0

User-facing HydraCache runtime crate.
Documentation
use hydracache::{
    prepare_replicated_payload, ClusterEpoch, CompatVersion, EffectiveReplicationMap,
    InMemoryReplicatedValueStore, PartitionId, Replicas, ReplicatedValueRecord,
    ReplicatedValueStore, Replication, ReplicationCryptoError, ReplicationKeyProvider,
    UpgradeGuard, UpgradeStep, CACHE_INVALIDATION_FRAME_VERSION,
    REPLICATED_VALUE_RECORD_FORMAT_VERSION,
};

#[test]
fn durable_replicated_values_replicated_value_survives_backup_restart() {
    let mut store = InMemoryReplicatedValueStore::with_budget(1024);
    let record = ReplicatedValueRecord::value(
        PartitionId::new(3),
        7,
        ClusterEpoch::new(2),
        b"sealed-user".to_vec(),
    );

    store.upsert("user:42", record.clone()).unwrap();
    let snapshot = store.snapshot();
    drop(store);

    let reopened = InMemoryReplicatedValueStore::reopen_from_snapshot(1024, snapshot);
    assert_eq!(reopened.get("user:42").unwrap(), Some(record));
}

#[test]
fn durable_replicated_values_restart_then_anti_entropy_converges_with_primary() {
    let mut backup = InMemoryReplicatedValueStore::with_budget(1024);
    backup
        .upsert(
            "user:42",
            ReplicatedValueRecord::value(
                PartitionId::new(3),
                7,
                ClusterEpoch::new(2),
                b"old".to_vec(),
            ),
        )
        .unwrap();
    let snapshot = backup.snapshot();
    let mut reopened = InMemoryReplicatedValueStore::reopen_from_snapshot(1024, snapshot);

    reopened
        .upsert(
            "user:42",
            ReplicatedValueRecord::value(
                PartitionId::new(3),
                9,
                ClusterEpoch::new(3),
                b"new".to_vec(),
            ),
        )
        .unwrap();

    let stored = reopened.get("user:42").unwrap().unwrap();
    assert_eq!(stored.version, 9);
    assert_eq!(stored.epoch, ClusterEpoch::new(3));
}

#[test]
fn durable_replicated_values_sealed_bytes_only_are_persisted() {
    struct XorProvider;

    impl ReplicationKeyProvider for XorProvider {
        fn seal(&self, plaintext: &[u8]) -> Result<Vec<u8>, ReplicationCryptoError> {
            Ok(plaintext.iter().map(|byte| byte ^ 0xaa).collect())
        }

        fn open(&self, sealed: &[u8]) -> Result<Vec<u8>, ReplicationCryptoError> {
            self.seal(sealed)
        }
    }

    let provider = XorProvider;
    let payload = prepare_replicated_payload(
        b"secret-profile",
        Replication::Eligible,
        false,
        Some(&provider),
        None,
    )
    .unwrap()
    .unwrap();
    assert_ne!(payload.bytes, b"secret-profile");

    let mut store = InMemoryReplicatedValueStore::with_budget(1024);
    store
        .upsert(
            "profile:7",
            ReplicatedValueRecord::value(
                PartitionId::new(9),
                1,
                ClusterEpoch::new(1),
                payload.bytes.clone(),
            ),
        )
        .unwrap();

    let stored = store.get("profile:7").unwrap().unwrap();
    let sealed = match stored.state {
        hydracache::ReplicatedSlot::Value { value, .. } => value,
        hydracache::ReplicatedSlot::Tombstone { .. } => panic!("expected value"),
    };
    assert_ne!(sealed, b"secret-profile");
    assert_eq!(provider.open(&sealed).unwrap(), b"secret-profile");
}

#[test]
fn durable_replicated_values_total_bytes_budget_rejects_over_limit_not_silently() {
    let mut store = InMemoryReplicatedValueStore::with_budget(4);

    let error = store
        .upsert(
            "big",
            ReplicatedValueRecord::value(
                PartitionId::new(1),
                1,
                ClusterEpoch::new(1),
                b"too-large".to_vec(),
            ),
        )
        .unwrap_err();

    assert!(error.to_string().contains("budget exceeded"));
    assert_eq!(store.rejected_total(), 1);
    assert!(store.get("big").unwrap().is_none());
}

#[test]
fn durable_replicated_values_tombstone_persisted_blocks_resurrection_after_restart() {
    let mut store = InMemoryReplicatedValueStore::with_budget(1024);
    store
        .tombstone("user:42", PartitionId::new(3), 10, ClusterEpoch::new(4))
        .unwrap();
    let snapshot = store.snapshot();
    let mut reopened = InMemoryReplicatedValueStore::reopen_from_snapshot(1024, snapshot);

    reopened
        .upsert(
            "user:42",
            ReplicatedValueRecord::value(
                PartitionId::new(3),
                9,
                ClusterEpoch::new(3),
                b"stale".to_vec(),
            ),
        )
        .unwrap();

    let stored = reopened.get("user:42").unwrap().unwrap();
    assert!(stored.is_tombstone());
    assert_eq!(stored.version, 10);
}

#[test]
fn durable_replicated_values_format_version_round_trips() {
    assert_eq!(REPLICATED_VALUE_RECORD_FORMAT_VERSION, 1);

    let record = ReplicatedValueRecord::value(
        PartitionId::new(9),
        17,
        ClusterEpoch::new(4),
        b"sealed-current-format".to_vec(),
    );
    let encoded = serde_json::to_string(&record).unwrap();
    let decoded: ReplicatedValueRecord = serde_json::from_str(&encoded).unwrap();

    assert_eq!(decoded, record);
}

#[test]
fn durable_replicated_values_future_format_is_rejected_by_upgrade_guard() {
    let guard = UpgradeGuard::current();
    let step = UpgradeStep {
        from: CompatVersion::new(0, 42, 0),
        to: CompatVersion::new(0, 43, 0),
        raft_log_format: 1,
        value_record_format: REPLICATED_VALUE_RECORD_FORMAT_VERSION + 1,
        wire_frame_version: CACHE_INVALIDATION_FRAME_VERSION,
    };

    let error = guard.check(step).unwrap_err();

    assert!(error.to_string().contains("incompatible persisted"));
}

#[test]
fn durable_replicated_values_scan_owned_returns_records_for_readable_map() {
    let mut store = InMemoryReplicatedValueStore::with_budget(1024);
    store
        .upsert(
            "user:42",
            ReplicatedValueRecord::value(
                PartitionId::new(1),
                1,
                ClusterEpoch::new(1),
                b"v".to_vec(),
            ),
        )
        .unwrap();
    let map = EffectiveReplicationMap::new(Replicas::new("member-a", vec!["member-b".into()]));

    let owned = store.scan_owned(&map).unwrap();
    assert_eq!(owned.len(), 1);
    assert_eq!(owned[0].0, "user:42");
}