#![cfg(feature = "memory")]
use std::time::Duration;
use oxcache::backend::{DashMapMemoryBackend, MokaMemoryBackend};
use oxcache::cache::{ChainCache, ChainLink};
use oxcache::Cache;
#[tokio::test(flavor = "multi_thread")]
async fn test_cache_sync_full_lifecycle() {
let cache: Cache<String, String> = Cache::builder().sync_mode(true).build().await.unwrap();
cache.set_sync(&"k1".to_string(), &"v1".to_string()).unwrap();
let value = cache.get_sync(&"k1".to_string()).unwrap();
assert_eq!(value, Some("v1".to_string()));
assert!(cache.exists_sync(&"k1".to_string()).unwrap());
assert!(!cache.exists_sync(&"missing".to_string()).unwrap());
cache.delete_sync(&"k1".to_string()).unwrap();
assert_eq!(cache.get_sync(&"k1".to_string()).unwrap(), None);
assert!(!cache.exists_sync(&"k1".to_string()).unwrap());
}
#[tokio::test(flavor = "multi_thread")]
async fn test_cache_sync_with_ttl_expires() {
let cache: Cache<String, String> = Cache::builder().sync_mode(true).build().await.unwrap();
cache
.set_with_ttl_sync(&"k".to_string(), &"v".to_string(), Some(Duration::from_millis(50)))
.unwrap();
let value = cache.get_sync(&"k".to_string()).unwrap();
assert_eq!(value, Some("v".to_string()));
tokio::time::sleep(Duration::from_millis(100)).await;
let mut expired = false;
for _ in 0..10 {
if cache.get_sync(&"k".to_string()).unwrap().is_none() {
expired = true;
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert!(expired, "sync get should return None after TTL expires");
}
#[tokio::test(flavor = "multi_thread")]
async fn test_cache_get_or_sync_hit_and_miss() {
let cache: Cache<String, String> = Cache::builder().sync_mode(true).build().await.unwrap();
let call_count = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let call_count_clone = call_count.clone();
let value = cache
.get_or_sync(&"user:1".to_string(), || {
call_count_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Ok("Alice".to_string())
})
.unwrap();
assert_eq!(value, "Alice");
assert_eq!(call_count.load(std::sync::atomic::Ordering::SeqCst), 1);
let value = cache
.get_or_sync(&"user:1".to_string(), || Ok("Should not be called".to_string()))
.unwrap();
assert_eq!(value, "Alice");
assert_eq!(call_count.load(std::sync::atomic::Ordering::SeqCst), 1);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_chain_sync_multi_backend_roundtrip() {
let moka = MokaMemoryBackend::new();
let dashmap = DashMapMemoryBackend::new();
let moka_ref = moka.clone();
let dashmap_ref = dashmap.clone();
let chain = ChainCache::builder()
.link(ChainLink::from_sync_backend(moka))
.link(ChainLink::from_sync_backend(dashmap))
.build();
chain.set_sync("k", b"v".to_vec(), None).unwrap();
use oxcache::backend::interface::SyncCacheReader;
assert_eq!(SyncCacheReader::get(&moka_ref, "k").unwrap(), Some(b"v".to_vec()));
assert_eq!(SyncCacheReader::get(&dashmap_ref, "k").unwrap(), Some(b"v".to_vec()));
let value = chain.get_sync("k").unwrap();
assert_eq!(value, Some(b"v".to_vec()));
chain.delete_sync("k").unwrap();
assert_eq!(SyncCacheReader::get(&moka_ref, "k").unwrap(), None);
assert_eq!(SyncCacheReader::get(&dashmap_ref, "k").unwrap(), None);
assert_eq!(chain.get_sync("k").unwrap(), None);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_chain_sync_ttl_propagates_to_all_links() {
let moka = MokaMemoryBackend::new();
let dashmap = DashMapMemoryBackend::new();
let moka_ref = moka.clone();
let dashmap_ref = dashmap.clone();
let chain = ChainCache::builder()
.link(ChainLink::from_sync_backend(moka))
.link(ChainLink::from_sync_backend(dashmap))
.build();
chain
.set_sync("k", b"v".to_vec(), Some(Duration::from_millis(50)))
.unwrap();
use oxcache::backend::interface::SyncCacheReader;
assert_eq!(SyncCacheReader::get(&moka_ref, "k").unwrap(), Some(b"v".to_vec()));
assert_eq!(SyncCacheReader::get(&dashmap_ref, "k").unwrap(), Some(b"v".to_vec()));
tokio::time::sleep(Duration::from_millis(100)).await;
assert_eq!(SyncCacheReader::get(&dashmap_ref, "k").unwrap(), None);
let mut moka_expired = false;
for _ in 0..10 {
if SyncCacheReader::get(&moka_ref, "k").unwrap().is_none() {
moka_expired = true;
break;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
assert!(moka_expired, "moka link should expire after TTL");
assert_eq!(chain.get_sync("k").unwrap(), None);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_sync_and_async_coexist() {
let cache: Cache<String, String> = Cache::builder().sync_mode(true).build().await.unwrap();
cache
.set(&"async_key".to_string(), &"async_value".to_string())
.await
.unwrap();
let value = cache.get_sync(&"async_key".to_string()).unwrap();
assert_eq!(value, Some("async_value".to_string()));
cache
.set_sync(&"sync_key".to_string(), &"sync_value".to_string())
.unwrap();
let value = cache.get(&"sync_key".to_string()).await.unwrap();
assert_eq!(value, Some("sync_value".to_string()));
}