use super::{AdaptiveCache, AdaptiveCacheConfig};
use tokio::time::sleep as async_sleep;
use tokio::time::Duration as TokioDuration;
use uuid::Uuid;
fn create_test_config() -> AdaptiveCacheConfig {
AdaptiveCacheConfig {
max_size: 100,
default_ttl: Duration::from_secs(60),
min_ttl: Duration::from_secs(10),
max_ttl: Duration::from_secs(300),
hot_threshold: 5,
cold_threshold: 1,
adaptation_rate: 0.25,
window_size: 10,
cleanup_interval_secs: 1,
enable_background_cleanup: false,
}
}
use std::time::Duration;
#[tokio::test]
async fn test_adaptive_cache_creation() {
let cache = AdaptiveCache::<String>::new(create_test_config());
let metrics = cache.get_metrics().await;
assert_eq!(metrics.base.item_count, 0);
}
#[tokio::test]
async fn test_adaptive_cache_hit_and_miss() {
let cache = AdaptiveCache::<String>::new(create_test_config());
let id = Uuid::new_v4();
let hit = cache
.record_access(id, false, Some("value".to_string()))
.await;
assert!(!hit);
let hit = cache.record_access(id, true, None).await;
assert!(hit);
let metrics = cache.get_metrics().await;
assert_eq!(metrics.base.hits, 1);
assert_eq!(metrics.base.misses, 1);
assert_eq!(metrics.base.hit_rate, 0.5);
}
#[tokio::test]
async fn test_hot_item_ttl_increase() {
let config = create_test_config();
let cache = AdaptiveCache::<String>::new(config);
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
for _ in 0..6 {
cache.record_access(id, true, None).await;
}
let ttl = cache.ttl(id).await;
assert!(ttl.is_some());
let ttl_secs = ttl.unwrap().as_secs();
assert!(
ttl_secs > 60,
"TTL should increase for hot items, got {}s",
ttl_secs
);
}
#[tokio::test]
async fn test_cold_item_ttl_decrease() {
let config = create_test_config();
let cache = AdaptiveCache::<String>::new(config);
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
cache.record_access(id, true, None).await;
let ttl = cache.ttl(id).await;
assert!(ttl.is_some());
let ttl_secs = ttl.unwrap().as_secs();
assert!(
ttl_secs < 60,
"TTL should decrease for cold items, got {}s",
ttl_secs
);
}
#[tokio::test]
async fn test_ttl_bounds_enforcement() {
let config = AdaptiveCacheConfig {
max_size: 100,
default_ttl: Duration::from_secs(60),
min_ttl: Duration::from_secs(10),
max_ttl: Duration::from_secs(120),
hot_threshold: 5,
cold_threshold: 1,
adaptation_rate: 0.5, window_size: 10,
cleanup_interval_secs: 1,
enable_background_cleanup: false,
};
let cache = AdaptiveCache::<String>::new(config);
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
for _ in 0..20 {
cache.record_access(id, true, None).await;
}
let ttl = cache.ttl(id).await.unwrap();
assert!(
ttl.as_secs() <= 120,
"TTL should be capped at max, got {}s",
ttl.as_secs()
);
}
#[tokio::test]
async fn test_access_count_tracking() {
let cache = AdaptiveCache::<String>::new(create_test_config());
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
for i in 0..5 {
let hit = cache.record_access(id, true, None).await;
assert!(hit, "Access {} should be a hit", i + 1);
}
let count = cache.access_count(id).await;
assert_eq!(count, Some(5));
}
#[tokio::test]
async fn test_hot_cold_distribution() {
let config = create_test_config();
let cache = AdaptiveCache::<String>::new(config);
let hot_id = Uuid::new_v4();
let cold_id = Uuid::new_v4();
cache
.record_access(hot_id, false, Some("hot".to_string()))
.await;
for _ in 0..6 {
cache.record_access(hot_id, true, None).await;
}
cache
.record_access(cold_id, false, Some("cold".to_string()))
.await;
cache.record_access(cold_id, true, None).await;
let metrics = cache.get_metrics().await;
assert_eq!(metrics.hot_item_count, 1);
assert_eq!(metrics.cold_item_count, 1);
}
#[tokio::test]
async fn test_ttl_expiration() {
let config = AdaptiveCacheConfig {
max_size: 100,
default_ttl: Duration::from_secs(1),
min_ttl: Duration::from_secs(1),
max_ttl: Duration::from_secs(1),
hot_threshold: 10,
cold_threshold: 1,
adaptation_rate: 0.1,
window_size: 10,
cleanup_interval_secs: 1,
enable_background_cleanup: false,
};
let cache = AdaptiveCache::<String>::new(config);
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
assert!(cache.contains(id).await);
async_sleep(TokioDuration::from_secs(3)).await;
assert!(!cache.contains(id).await);
}
#[tokio::test]
async fn test_manual_cleanup() {
let config = AdaptiveCacheConfig {
max_size: 100,
default_ttl: Duration::from_secs(1),
min_ttl: Duration::from_secs(1),
max_ttl: Duration::from_secs(1),
hot_threshold: 10,
cold_threshold: 1,
adaptation_rate: 0.1,
window_size: 10,
cleanup_interval_secs: 1,
enable_background_cleanup: false,
};
let cache = AdaptiveCache::<String>::new(config);
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
cache
.record_access(id1, false, Some("v1".to_string()))
.await;
cache
.record_access(id2, false, Some("v2".to_string()))
.await;
async_sleep(TokioDuration::from_secs(3)).await;
let cleaned = cache.cleanup_expired().await;
assert_eq!(cleaned, 2);
let metrics = cache.get_metrics().await;
assert_eq!(metrics.base.item_count, 0);
}
#[tokio::test]
async fn test_remove_entry() {
let cache = AdaptiveCache::<String>::new(create_test_config());
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
assert!(cache.contains(id).await);
cache.remove(id).await;
assert!(!cache.contains(id).await);
}
#[tokio::test]
async fn test_clear_all() {
let cache = AdaptiveCache::<String>::new(create_test_config());
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
cache
.record_access(id1, false, Some("v1".to_string()))
.await;
cache
.record_access(id2, false, Some("v2".to_string()))
.await;
cache.clear().await;
assert!(cache.is_empty().await);
let metrics = cache.get_metrics().await;
assert_eq!(metrics.base.item_count, 0);
}
#[tokio::test]
async fn test_get_and_record() {
let cache = AdaptiveCache::<String>::new(create_test_config());
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
let result = cache.get_and_record(id).await;
assert_eq!(result, Some("value".to_string()));
let count = cache.access_count(id).await;
assert_eq!(count, Some(1));
}
#[tokio::test]
async fn test_nonexistent_entry() {
let cache = AdaptiveCache::<String>::new(create_test_config());
let id = Uuid::new_v4();
assert!(!cache.contains(id).await);
assert!(cache.access_count(id).await.is_none());
assert!(cache.ttl(id).await.is_none());
assert!(cache.get(id).await.is_none());
}