#![cfg(feature = "memory")]
use std::ops::Add;
use std::time::Duration;
use cachet::{Cache, CacheEntry};
use cachet_tier::{CacheOp, MockCache};
use tick::{Clock, ClockControl};
#[cfg_attr(miri, ignore)]
#[test]
fn wrapper_name() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let wrapper = cache.inner();
assert!(!wrapper.name().is_empty());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_get_miss() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let result = cache.get(&"nonexistent".to_string()).await.unwrap();
assert!(result.is_none());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_get_hit() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let key = "key".to_string();
cache.insert(key.clone(), CacheEntry::new(42)).await.unwrap();
let result = cache.get(&key).await.unwrap();
assert!(result.is_some());
assert_eq!(*result.unwrap().value(), 42);
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_insert() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let key = "key".to_string();
cache.insert(key.clone(), CacheEntry::new(42)).await.unwrap();
assert!(cache.get(&key).await.unwrap().is_some());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_invalidate() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let key = "key".to_string();
cache.insert(key.clone(), CacheEntry::new(42)).await.unwrap();
cache.invalidate(&key).await.unwrap();
assert!(cache.get(&key).await.unwrap().is_none());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_clear() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
cache.insert("k1".to_string(), CacheEntry::new(1)).await.unwrap();
cache.insert("k2".to_string(), CacheEntry::new(2)).await.unwrap();
cache.clear().await.unwrap();
assert!(cache.get(&"k1".to_string()).await.unwrap().is_none());
assert!(cache.get(&"k2".to_string()).await.unwrap().is_none());
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_len_returns_correct_count() {
let clock = Clock::new_frozen();
let cache = Cache::builder(clock).storage(MockCache::<String, i32>::new()).build();
assert_eq!(cache.len().await.unwrap(), 0);
cache.insert("key".to_string(), CacheEntry::new(42)).await.unwrap();
assert_eq!(cache.len().await.unwrap(), 1);
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_with_ttl_configured() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().ttl(Duration::from_secs(60)).build();
let key = "key".to_string();
cache.insert(key.clone(), CacheEntry::new(42)).await.unwrap();
let result = cache.get(&key).await.unwrap();
assert!(result.is_some());
assert_eq!(*result.unwrap().value(), 42);
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_entry_with_ttl() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let key = "key".to_string();
let entry = CacheEntry::expires_after(42, Duration::from_secs(120));
cache.insert(key.clone(), entry).await.unwrap();
let result = cache.get(&key).await.unwrap();
assert!(result.is_some());
assert_eq!(*result.unwrap().value(), 42);
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_no_ttl_configured() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let key = "key".to_string();
cache.insert(key.clone(), CacheEntry::new(42)).await.unwrap();
let result = cache.get(&key).await.unwrap();
assert!(result.is_some());
assert_eq!(*result.unwrap().value(), 42);
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_get_error_is_recorded() {
let clock = Clock::new_frozen();
let mock = MockCache::<String, i32>::new();
mock.fail_when(|op| matches!(op, CacheOp::Get(_)));
let cache = Cache::builder(clock).storage(mock).build();
let result = cache.get(&"key".to_string()).await;
result.unwrap_err();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_insert_error_is_recorded() {
let clock = Clock::new_frozen();
let mock = MockCache::<String, i32>::new();
mock.fail_when(|op| matches!(op, CacheOp::Insert { .. }));
let cache = Cache::builder(clock).storage(mock).build();
let result = cache.insert("key".to_string(), CacheEntry::new(42)).await;
result.unwrap_err();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_invalidate_error_is_recorded() {
let clock = Clock::new_frozen();
let mock = MockCache::<String, i32>::new();
mock.fail_when(|op| matches!(op, CacheOp::Invalidate(_)));
let cache = Cache::builder(clock).storage(mock).build();
let result = cache.invalidate(&"key".to_string()).await;
result.unwrap_err();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_clear_error_is_recorded() {
let clock = Clock::new_frozen();
let mock = MockCache::<String, i32>::new();
mock.fail_when(|op| matches!(op, CacheOp::Clear));
let cache = Cache::builder(clock).storage(mock).build();
let result = cache.clear().await;
result.unwrap_err();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_expired_entry_returns_none() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock.clone())
.memory()
.ttl(Duration::from_secs(60))
.build();
let key = "key".to_string();
let entry = CacheEntry::expires_at(42, Duration::from_secs(1), clock.system_time() - Duration::from_secs(100));
cache.insert(key.clone(), entry).await.unwrap();
let result = cache.get(&key).await.unwrap();
assert!(result.is_none(), "expired entry should return None");
}
#[cfg_attr(miri, ignore)]
#[test]
fn wrapper_inner_returns_reference() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock).memory().build();
let _inner = cache.inner().inner();
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_entry_expired_by_tier_ttl_without_per_entry_ttl() {
let control = ClockControl::new();
let clock = control.to_clock();
let ttl = Duration::from_secs(1);
let cache = Cache::builder::<String, i32>(clock.clone()).memory().ttl(ttl).build();
let key = "key".to_string();
let entry = CacheEntry::new(42);
cache.insert(key.clone(), entry).await.unwrap();
control.advance(ttl.add(Duration::from_secs(1)));
let result = cache.get(&key).await.unwrap();
assert!(result.is_none(), "entry expired by tier TTL should return None");
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn wrapper_tier_ttl_expires_entry_without_per_entry_ttl() {
let clock = Clock::new_frozen();
let cache = Cache::builder::<String, i32>(clock.clone())
.memory()
.ttl(Duration::from_secs(10))
.build();
let key = "key".to_string();
let mut entry = CacheEntry::new(42);
entry.ensure_cached_at(clock.system_time() - Duration::from_secs(100));
cache.insert(key.clone(), entry).await.unwrap();
let result = cache.get(&key).await.unwrap();
assert!(result.is_none(), "entry should be expired by tier TTL alone");
}