use super::{CacheConfig, CachedTursoStorage};
use crate::TursoStorage;
use libsql::Builder;
use do_memory_core::{Episode, Evidence, Heuristic, Pattern, TaskContext, TaskType};
use std::sync::Arc;
use tempfile::TempDir;
use uuid::Uuid;
async fn create_test_turso_storage() -> (TursoStorage, TempDir) {
let dir = TempDir::new().unwrap();
let db_path = dir.path().join("test_cache.db");
let db = Builder::new_local(&db_path)
.build()
.await
.expect("Failed to create test database");
let storage = TursoStorage::from_database(db).expect("Failed to create storage");
storage
.initialize_schema()
.await
.expect("Failed to init schema");
(storage, dir)
}
fn create_test_episode(id: Uuid) -> Episode {
Episode {
episode_id: id,
task_type: TaskType::CodeGeneration,
task_description: format!("Test episode {}", id),
context: TaskContext {
domain: "test".to_string(),
language: Some("rust".to_string()),
..Default::default()
},
steps: vec![],
outcome: None,
reward: None,
reflection: None,
patterns: vec![],
heuristics: vec![],
applied_patterns: vec![],
salient_features: None,
start_time: chrono::Utc::now(),
end_time: None,
metadata: std::collections::HashMap::new(),
}
}
fn create_test_pattern(id: Uuid) -> Pattern {
Pattern::ToolSequence {
id,
tools: vec!["tool1".to_string(), "tool2".to_string()],
context: TaskContext {
domain: "test".to_string(),
language: Some("rust".to_string()),
..Default::default()
},
success_rate: 0.8,
avg_latency: chrono::Duration::milliseconds(100),
occurrence_count: 5,
effectiveness: Default::default(),
}
}
fn create_test_heuristic(id: Uuid) -> Heuristic {
Heuristic {
heuristic_id: id,
condition: "condition".to_string(),
action: "action".to_string(),
confidence: 0.75,
evidence: Evidence {
episode_ids: vec![],
success_rate: 0.75,
sample_size: 10,
},
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
}
}
#[tokio::test]
async fn test_cache_creation_with_default_config() {
let (storage, _dir) = create_test_turso_storage().await;
let cache_config = CacheConfig::default();
let cached = CachedTursoStorage::new(storage, cache_config);
let stats = cached.stats();
assert_eq!(stats.episode_hits, 0);
assert_eq!(stats.episode_misses, 0);
assert_eq!(stats.pattern_hits, 0);
assert_eq!(stats.pattern_misses, 0);
}
#[tokio::test]
async fn test_cache_creation_with_disabled_caches() {
let (storage, _dir) = create_test_turso_storage().await;
let cache_config = CacheConfig {
enable_episode_cache: false,
enable_pattern_cache: false,
enable_query_cache: false,
..Default::default()
};
let cached = CachedTursoStorage::new(storage, cache_config);
let result = cached.get_episode_cached(Uuid::new_v4()).await;
assert!(result.unwrap().is_none());
}
#[tokio::test]
async fn test_episode_cache_hit() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let episode = create_test_episode(Uuid::new_v4());
cached.store_episode_cached(&episode).await.unwrap();
let result = cached.get_episode_cached(episode.episode_id).await.unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().episode_id, episode.episode_id);
let stats = cached.stats();
assert_eq!(stats.episode_misses, 1); assert_eq!(stats.episode_hits, 0);
let result = cached.get_episode_cached(episode.episode_id).await.unwrap();
assert!(result.is_some());
let stats = cached.stats();
assert_eq!(stats.episode_hits, 1);
assert_eq!(stats.episode_misses, 1);
}
#[tokio::test]
async fn test_episode_cache_miss() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let result = cached.get_episode_cached(Uuid::new_v4()).await.unwrap();
assert!(result.is_none());
let stats = cached.stats();
assert_eq!(stats.episode_misses, 1);
assert_eq!(stats.episode_hits, 0);
}
#[tokio::test]
async fn test_episode_cache_invalidation_on_store() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let episode_id = Uuid::new_v4();
let episode = create_test_episode(episode_id);
cached.store_episode_cached(&episode).await.unwrap();
let _ = cached.get_episode_cached(episode_id).await.unwrap();
let updated_episode = create_test_episode(episode_id);
cached.store_episode_cached(&updated_episode).await.unwrap();
let result = cached.get_episode_cached(episode_id).await.unwrap();
assert!(result.is_some());
let stats = cached.stats();
assert!(stats.episode_misses >= 2);
}
#[tokio::test]
async fn test_episode_cache_invalidation_on_delete() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let episode_id = Uuid::new_v4();
let episode = create_test_episode(episode_id);
cached.store_episode_cached(&episode).await.unwrap();
let _ = cached.get_episode_cached(episode_id).await.unwrap();
cached.delete_episode_cached(episode_id).await.unwrap();
let result = cached.get_episode_cached(episode_id).await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn test_pattern_cache_hit() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let pattern_id = Uuid::new_v4();
let pattern = create_test_pattern(pattern_id);
cached.store_pattern_cached(&pattern).await.unwrap();
let result = cached.get_pattern_cached(pattern_id).await.unwrap();
assert!(result.is_some());
let stats = cached.stats();
assert_eq!(stats.pattern_misses, 1);
assert_eq!(stats.pattern_hits, 0);
let result = cached.get_pattern_cached(pattern_id).await.unwrap();
assert!(result.is_some());
let stats = cached.stats();
assert_eq!(stats.pattern_hits, 1);
assert_eq!(stats.pattern_misses, 1);
}
#[tokio::test]
async fn test_heuristic_cache_hit() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let heuristic_id = Uuid::new_v4();
let heuristic = create_test_heuristic(heuristic_id);
cached.store_heuristic_cached(&heuristic).await.unwrap();
let result = cached.get_heuristic_cached(heuristic_id).await.unwrap();
assert!(result.is_some());
let result = cached.get_heuristic_cached(heuristic_id).await.unwrap();
assert!(result.is_some());
let _stats = cached.stats();
}
#[tokio::test]
async fn test_clear_caches() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
for _ in 0..5 {
let episode = create_test_episode(Uuid::new_v4());
cached.store_episode_cached(&episode).await.unwrap();
let _ = cached.get_episode_cached(episode.episode_id).await.unwrap();
}
let stats_before = cached.stats();
assert!(stats_before.episode_hits > 0 || stats_before.episode_misses > 0);
cached.clear_caches().await;
let _ = cached.get_episode_cached(Uuid::new_v4()).await.unwrap();
let stats_after = cached.stats();
assert_eq!(stats_after.episode_hits, 0);
}
#[tokio::test]
async fn test_cache_config_validation() {
let cache_config = CacheConfig::default();
assert!(cache_config.enable_episode_cache);
assert!(cache_config.enable_pattern_cache);
assert_eq!(cache_config.max_episodes, 10_000);
assert_eq!(cache_config.max_patterns, 5_000);
assert!(!cache_config.episode_ttl.is_zero());
assert!(!cache_config.pattern_ttl.is_zero());
}
#[tokio::test]
async fn test_cache_stats_hit_rate() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let episode = create_test_episode(Uuid::new_v4());
cached.store_episode_cached(&episode).await.unwrap();
let _ = cached.get_episode_cached(episode.episode_id).await.unwrap();
let _ = cached.get_episode_cached(episode.episode_id).await.unwrap();
let _ = cached.get_episode_cached(episode.episode_id).await.unwrap();
let stats = cached.stats();
let hit_rate = stats.episode_hit_rate();
assert!((hit_rate - 0.6666).abs() < 0.01);
}
#[tokio::test]
async fn test_underlying_storage_access() {
let (storage, _dir) = create_test_turso_storage().await;
let cached = CachedTursoStorage::new(storage, CacheConfig::default());
let _ = cached.storage();
let episode = create_test_episode(Uuid::new_v4());
cached.storage().store_episode(&episode).await.unwrap();
let result = cached
.storage()
.get_episode(episode.episode_id)
.await
.unwrap();
assert!(result.is_some());
}