#![allow(clippy::expect_used)]
use do_memory_core::{Episode, TaskContext, TaskType};
use do_memory_storage_redb::{
AdaptiveCache, AdaptiveCacheAdapter, AdaptiveCacheConfig, Cache, CacheConfig, CacheMetrics,
LRUCache, RedbStorage,
};
use std::time::Duration;
use tempfile::tempdir;
use uuid::Uuid;
#[tokio::test]
async fn test_adaptive_cache_works_in_isolation() {
let config = 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: 2,
adaptation_rate: 0.25,
window_size: 10,
cleanup_interval_secs: 0, enable_background_cleanup: false,
};
let cache: AdaptiveCache<String> = AdaptiveCache::new(config);
let id = Uuid::new_v4();
let value = "test_value".to_string();
let found = cache.record_access(id, false, Some(value.clone())).await;
assert!(!found, "First access should be a miss");
let found = cache.record_access(id, true, None).await;
assert!(found, "Second access should be a hit");
let retrieved = cache.get(id).await;
assert_eq!(retrieved, Some(value));
let metrics = cache.get_metrics().await;
assert_eq!(metrics.base.hits, 1);
assert_eq!(metrics.base.misses, 1);
}
#[tokio::test]
async fn test_adaptive_cache_ttl_adjustment() {
let config = AdaptiveCacheConfig {
max_size: 100,
default_ttl: Duration::from_secs(60),
min_ttl: Duration::from_secs(10),
max_ttl: Duration::from_secs(300),
hot_threshold: 3,
cold_threshold: 1,
adaptation_rate: 0.5,
window_size: 10,
cleanup_interval_secs: 0,
enable_background_cleanup: false,
};
let cache: AdaptiveCache<String> = AdaptiveCache::new(config);
let id = Uuid::new_v4();
cache
.record_access(id, false, Some("value".to_string()))
.await;
let initial_ttl = cache.ttl(id).await.expect("Should have TTL");
assert_eq!(
initial_ttl,
Duration::from_secs(60),
"Should start with default TTL"
);
for _ in 0..5 {
cache.record_access(id, true, None).await;
}
let hot_ttl = cache.ttl(id).await.expect("Should have TTL after accesses");
assert!(
hot_ttl > initial_ttl,
"Hot item TTL ({:?}) should be > initial TTL ({:?})",
hot_ttl,
initial_ttl
);
assert!(
hot_ttl <= Duration::from_secs(300),
"TTL should not exceed max_ttl"
);
}
#[tokio::test]
async fn test_adaptive_cache_hot_cold_detection() {
let config = 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: 2,
adaptation_rate: 0.25,
window_size: 10,
cleanup_interval_secs: 0,
enable_background_cleanup: false,
};
let cache: AdaptiveCache<String> = AdaptiveCache::new(config);
let hot_id = Uuid::new_v4();
cache
.record_access(hot_id, false, Some("hot".to_string()))
.await;
for _ in 0..10 {
cache.record_access(hot_id, true, None).await;
}
let cold_id = Uuid::new_v4();
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;
let hot_count = cache.access_count(hot_id).await;
assert_eq!(hot_count, Some(10), "Hot item should have 10 accesses");
let cold_count = cache.access_count(cold_id).await;
assert_eq!(cold_count, Some(1), "Cold item should have 1 access");
assert!(
metrics.hot_item_count >= 1,
"Should have at least 1 hot item"
);
assert!(
metrics.cold_item_count >= 1,
"Should have at least 1 cold item"
);
}
#[tokio::test]
async fn test_redb_storage_uses_adaptive_cache_by_default() {
let dir = tempdir().expect("Failed to create temp dir");
let db_path = dir.path().join("test.redb");
let storage = RedbStorage::new(&db_path)
.await
.expect("Failed to create storage");
let metrics: CacheMetrics = storage.get_cache_metrics().await;
assert_eq!(metrics.hits, 0);
assert_eq!(metrics.misses, 0);
assert_eq!(metrics.item_count, 0);
let episode = Episode::new(
"Test task".to_string(),
TaskContext::default(),
TaskType::Testing,
);
storage
.store_episode(&episode)
.await
.expect("Failed to store");
let metrics = storage.get_cache_metrics().await;
assert_eq!(
metrics.misses, 1,
"Should have recorded a miss for the new episode"
);
}
#[tokio::test]
async fn test_redb_storage_can_use_legacy_lru_cache() {
let dir = tempdir().expect("Failed to create temp dir");
let db_path = dir.path().join("test_lru.redb");
let config = CacheConfig {
max_size: 100,
default_ttl_secs: 1800,
cleanup_interval_secs: 60,
enable_background_cleanup: false,
};
let storage = RedbStorage::new_with_cache_config(&db_path, config)
.await
.expect("Failed to create storage with LRU cache");
let metrics = storage.get_cache_metrics().await;
assert_eq!(metrics.hits, 0);
assert_eq!(metrics.misses, 0);
}
#[tokio::test]
async fn test_redb_storage_with_custom_adaptive_config() {
let dir = tempdir().expect("Failed to create temp dir");
let db_path = dir.path().join("test_adaptive_custom.redb");
let config = AdaptiveCacheConfig {
max_size: 500,
default_ttl: Duration::from_secs(900),
min_ttl: Duration::from_secs(60),
max_ttl: Duration::from_secs(3600),
hot_threshold: 5,
cold_threshold: 1,
adaptation_rate: 0.3,
window_size: 15,
cleanup_interval_secs: 120,
enable_background_cleanup: false,
};
let storage = RedbStorage::new_with_adaptive_config(&db_path, config)
.await
.expect("Failed to create storage with custom adaptive config");
let metrics = storage.get_cache_metrics().await;
assert_eq!(metrics.hits, 0);
assert_eq!(metrics.misses, 0);
}
#[test]
fn test_cache_config_types_are_different() {
let lru_config = CacheConfig {
max_size: 100,
default_ttl_secs: 1800,
cleanup_interval_secs: 60,
enable_background_cleanup: true,
};
let adaptive_config = AdaptiveCacheConfig {
max_size: 100,
default_ttl: Duration::from_secs(1800),
min_ttl: Duration::from_secs(300),
max_ttl: Duration::from_secs(7200),
hot_threshold: 10,
cold_threshold: 2,
adaptation_rate: 0.25,
window_size: 20,
cleanup_interval_secs: 60,
enable_background_cleanup: true,
};
assert_ne!(
std::any::type_name_of_val(&lru_config),
std::any::type_name_of_val(&adaptive_config)
);
}
#[tokio::test]
async fn test_adaptive_cache_stores_values_lru_does_not() {
let adaptive_config = AdaptiveCacheConfig::default();
let adaptive_cache: AdaptiveCache<String> = AdaptiveCache::new(adaptive_config);
let id = Uuid::new_v4();
adaptive_cache
.record_access(id, false, Some("stored_value".to_string()))
.await;
let value = adaptive_cache.get(id).await;
assert_eq!(value, Some("stored_value".to_string()));
let lru_config = CacheConfig::default();
let lru_cache = LRUCache::new(lru_config);
lru_cache.record_access(id, false, Some(100)).await;
let metrics = lru_cache.get_metrics().await;
assert_eq!(metrics.misses, 1);
}
#[test]
fn test_architecture_implementation_complete() {
println!("Architecture implementation complete - AdaptiveCacheAdapter is the default cache");
}
#[tokio::test]
async fn test_adaptive_cache_adapter_integration() {
let config = AdaptiveCacheConfig {
max_size: 100,
default_ttl: Duration::from_secs(60),
min_ttl: Duration::from_secs(10),
max_ttl: Duration::from_secs(300),
hot_threshold: 3,
cold_threshold: 1,
adaptation_rate: 0.5,
window_size: 10,
cleanup_interval_secs: 0,
enable_background_cleanup: false,
};
let adapter = AdaptiveCacheAdapter::new(config);
let id = Uuid::new_v4();
let found = adapter.record_access(id, false, Some(100)).await;
assert!(!found, "First access should be a miss");
assert!(adapter.contains(id).await, "Entry should exist after miss");
for _ in 0..5 {
adapter.record_access(id, true, None).await;
}
let hot_count = adapter.hot_count().await;
assert!(hot_count > 0, "Should have at least one hot item");
let metrics = adapter.get_metrics().await;
assert!(metrics.hits > 0, "Should have recorded hits");
assert_eq!(
metrics.misses, 1,
"Should have one miss from initial insert"
);
}