oxcache 0.2.0

A high-performance multi-level cache library for Rust with L1 (memory) and L2 (Redis) caching.
// Copyright (c) 2025-2026, Kirky.X
//
// MIT License
//
// DashMap 后端单元测试

use oxcache::backend::interface::{CacheConnector, CacheReader, CacheWriter};
use oxcache::backend::memory::dashmap::{
    dashmap_memory, dashmap_memory_with_capacity, dashmap_memory_with_capacity_and_ttl, DashMapBackendBuilder,
    DashMapMemoryBackend,
};
use oxcache::backend::score::BackendScore;
use std::time::Duration;

/// Poll until a condition is true or timeout. Replaces fixed sleep for timing-dependent tests.
/// ponytail: simple polling loop, add exponential backoff if CI flakiness persists.
async fn poll_until<F, Fut>(timeout: Duration, interval: Duration, f: F) -> bool
where
    F: Fn() -> Fut,
    Fut: std::future::Future<Output = bool>,
{
    let start = std::time::Instant::now();
    while start.elapsed() < timeout {
        if f().await {
            return true;
        }
        tokio::time::sleep(interval).await;
    }
    false
}

#[tokio::test]
async fn test_dashmap_new_default_state() {
    let backend = DashMapMemoryBackend::new();
    assert!(backend.capacity() > 0);
    assert!(backend.is_empty().await.unwrap());
}

#[tokio::test]
async fn test_dashmap_builder_default() {
    let backend = DashMapBackendBuilder::default().build();
    assert!(backend.capacity() > 0);
}

#[tokio::test]
async fn test_dashmap_builder_with_capacity_custom() {
    let backend = DashMapBackendBuilder::default().capacity(5000).build();
    assert_eq!(backend.capacity(), 5000);
}

#[tokio::test]
async fn test_dashmap_builder_with_ttl_default_ttl() {
    let backend = DashMapBackendBuilder::default()
        .capacity(1000)
        .default_ttl(Duration::from_secs(60))
        .build();
    assert_eq!(backend.capacity(), 1000);
}

#[tokio::test]
async fn test_dashmap_set_and_get_basic_roundtrip() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    let value = backend.get("key1").await.unwrap();
    assert_eq!(value, Some(b"value1".to_vec()));
}

#[tokio::test]
async fn test_dashmap_get_nonexistent_returns_none() {
    let backend = DashMapMemoryBackend::new();
    let value = backend.get("nonexistent").await.unwrap();
    assert!(value.is_none());
}

#[tokio::test]
async fn test_dashmap_delete_removes_key() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    assert!(backend.exists("key1").await.unwrap());

    backend.delete("key1").await.unwrap();
    assert!(!backend.exists("key1").await.unwrap());
}

#[tokio::test]
async fn test_dashmap_exists_checks_key_presence() {
    let backend = DashMapMemoryBackend::new();

    assert!(!backend.exists("key1").await.unwrap());
    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    assert!(backend.exists("key1").await.unwrap());
}

#[tokio::test]
async fn test_dashmap_clear_empties_all() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    backend.set("key2", b"value2".to_vec(), None).await.unwrap();

    backend.clear().await.unwrap();

    assert!(backend.is_empty().await.unwrap());
    assert!(!backend.exists("key1").await.unwrap());
    assert!(!backend.exists("key2").await.unwrap());
}

#[tokio::test]
async fn test_dashmap_close_shutdown_empties() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    backend.shutdown().await;

    assert!(backend.is_empty().await.unwrap());
}

#[tokio::test]
async fn test_dashmap_ttl_returns_remaining() {
    let backend = DashMapMemoryBackend::new();

    backend
        .set("key1", b"value1".to_vec(), Some(Duration::from_secs(60)))
        .await
        .unwrap();

    let ttl = backend.ttl("key1").await.unwrap();
    assert!(ttl.is_some());
    assert!(ttl.unwrap() <= Duration::from_secs(60));
}

#[tokio::test]
async fn test_dashmap_ttl_nonexistent_returns_none() {
    let backend = DashMapMemoryBackend::new();
    let ttl = backend.ttl("nonexistent").await.unwrap();
    assert!(ttl.is_none());
}

#[tokio::test]
async fn test_dashmap_expire_sets_ttl() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    let ttl_before = backend.ttl("key1").await.unwrap();
    assert!(ttl_before.is_none());

    let result = backend.expire("key1", Duration::from_secs(30)).await.unwrap();
    assert!(result);

    let ttl_after = backend.ttl("key1").await.unwrap();
    assert!(ttl_after.is_some());
}

#[tokio::test]
async fn test_dashmap_expire_nonexistent_returns_false() {
    let backend = DashMapMemoryBackend::new();
    let result = backend.expire("nonexistent", Duration::from_secs(30)).await.unwrap();
    assert!(!result);
}

#[tokio::test]
async fn test_dashmap_health_check_returns_ok() {
    let backend = DashMapMemoryBackend::new();
    backend.health_check().await.unwrap();
}

#[tokio::test]
async fn test_dashmap_stats_returns_metrics() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    backend.get("key1").await.unwrap();
    backend.get("nonexistent").await.unwrap();

    let stats = backend.stats().await.unwrap();
    assert_eq!(stats.get("type"), Some(&"dashmap".to_string()));
    assert!(stats.contains_key("capacity"));
    assert!(stats.contains_key("entry_count"));
    assert!(stats.contains_key("hits"));
    assert!(stats.contains_key("misses"));
    assert!(stats.contains_key("hit_rate"));
}

#[tokio::test]
async fn test_dashmap_len_tracks_count() {
    let backend = DashMapMemoryBackend::new();

    assert_eq!(backend.len().await.unwrap(), 0);

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    assert_eq!(backend.len().await.unwrap(), 1);

    backend.set("key2", b"value2".to_vec(), None).await.unwrap();
    assert_eq!(backend.len().await.unwrap(), 2);

    backend.delete("key1").await.unwrap();
    assert_eq!(backend.len().await.unwrap(), 1);
}

#[tokio::test]
async fn test_dashmap_capacity_method_returns_positive() {
    let backend = DashMapMemoryBackend::new();
    let capacity = backend.capacity();
    assert!(capacity > 0);
}

#[test]
fn test_dashmap_hit_rate_empty() {
    let backend = DashMapMemoryBackend::new();
    assert_eq!(backend.hit_rate(), 0.0);
}

#[tokio::test]
async fn test_dashmap_hit_rate_with_hits() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    backend.get("key1").await.unwrap();
    backend.get("key1").await.unwrap();
    backend.get("nonexistent").await.unwrap();

    let hit_rate = backend.hit_rate();
    assert!(hit_rate > 0.0 && hit_rate <= 1.0);
}

#[test]
fn test_dashmap_entry_count_initial_zero() {
    let backend = DashMapMemoryBackend::new();
    assert_eq!(backend.entry_count(), 0);
}

#[test]
fn test_dashmap_backend_score_returns_positive() {
    let backend = DashMapMemoryBackend::new();
    assert!(backend.score() > 0);
    assert!(!backend.is_persistent());
    assert_eq!(backend.backend_name(), "dashmap");
}

#[test]
fn test_dashmap_clone_copies_capacity() {
    let backend1 = DashMapMemoryBackend::new();
    let backend2 = backend1.clone();
    assert_eq!(backend1.capacity(), backend2.capacity());
}

#[test]
fn test_dashmap_debug_includes_name() {
    let backend = DashMapMemoryBackend::new();
    let debug_str = format!("{:?}", backend);
    assert!(debug_str.contains("DashMapMemoryBackend"));
}

#[test]
fn test_convenience_dashmap_memory_default_has_capacity() {
    let backend = dashmap_memory();
    assert!(backend.capacity() > 0);
}

#[test]
fn test_convenience_dashmap_memory_with_capacity_custom() {
    let backend = dashmap_memory_with_capacity(2000);
    assert_eq!(backend.capacity(), 2000);
}

#[test]
fn test_convenience_dashmap_memory_with_capacity_and_ttl_custom() {
    let backend = dashmap_memory_with_capacity_and_ttl(3000, Duration::from_secs(120));
    assert_eq!(backend.capacity(), 3000);
}

#[tokio::test]
async fn test_dashmap_overwrite_replaces_value() {
    let backend = DashMapMemoryBackend::new();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();
    backend.set("key1", b"value2".to_vec(), None).await.unwrap();

    let value = backend.get("key1").await.unwrap();
    assert_eq!(value, Some(b"value2".to_vec()));
}

#[tokio::test]
async fn test_dashmap_large_value_handles_1mb() {
    let backend = DashMapMemoryBackend::new();
    let large_value = vec![0u8; 1024 * 1024];

    backend.set("large_key", large_value.clone(), None).await.unwrap();
    let value = backend.get("large_key").await.unwrap();
    assert_eq!(value, Some(large_value));
}

#[tokio::test]
async fn test_dashmap_many_keys_handles_100() {
    let backend = DashMapMemoryBackend::builder().capacity(1000).build();

    for i in 0..100 {
        let key = format!("key_{}", i);
        let value = format!("value_{}", i);
        backend.set(&key, value.as_bytes().to_vec(), None).await.unwrap();
    }

    assert_eq!(backend.len().await.unwrap(), 100);

    for i in 0..100 {
        let key = format!("key_{}", i);
        let expected = format!("value_{}", i);
        let value = backend.get(&key).await.unwrap();
        assert_eq!(value, Some(expected.as_bytes().to_vec()));
    }
}

#[tokio::test]
async fn test_dashmap_ttl_expiration_evicts_after_ttl() {
    let backend = DashMapMemoryBackend::new();

    backend
        .set("key1", b"value1".to_vec(), Some(Duration::from_millis(50)))
        .await
        .unwrap();

    assert!(backend.get("key1").await.unwrap().is_some());

    assert!(
        poll_until(Duration::from_millis(500), Duration::from_millis(10), || async {
            backend.get("key1").await.unwrap().is_none()
        })
        .await
    );
}

#[tokio::test]
async fn test_dashmap_default_ttl_evicts_after_default() {
    let backend = DashMapMemoryBackend::builder()
        .default_ttl(Duration::from_millis(100))
        .build();

    backend.set("key1", b"value1".to_vec(), None).await.unwrap();

    assert!(backend.get("key1").await.unwrap().is_some());

    assert!(
        poll_until(Duration::from_millis(500), Duration::from_millis(10), || async {
            backend.get("key1").await.unwrap().is_none()
        })
        .await
    );
}