use ccboard_core::cache::MetadataCache;
use ccboard_core::parsers::SessionIndexParser;
use ccboard_core::DataStore;
use std::sync::Arc;
#[tokio::test]
async fn test_cache_write_real_file() {
let home = dirs::home_dir().unwrap().join(".claude");
if !home.exists() {
eprintln!("SKIP: ~/.claude not found");
return;
}
let cache_dir = home.join("cache-test");
let _ = std::fs::remove_dir_all(&cache_dir);
let cache = MetadataCache::new(&cache_dir).unwrap();
let projects_dir = home.join("projects");
let session_path = walkdir::WalkDir::new(&projects_dir)
.max_depth(3)
.into_iter()
.filter_map(|e| e.ok())
.find(|e| {
e.path()
.extension()
.map(|ext| ext == "jsonl")
.unwrap_or(false)
})
.map(|e| e.path().to_path_buf());
let Some(session_path) = session_path else {
eprintln!("SKIP: no session files found");
return;
};
eprintln!("Testing with session: {}", session_path.display());
let parser = SessionIndexParser::new().with_cache(Arc::new(cache));
let meta = parser.scan_session(&session_path).await.unwrap();
eprintln!(
"Parsed session: id={}, tokens={}",
meta.id, meta.total_tokens
);
let cache_path = cache_dir.join("session-metadata.db");
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let output = std::process::Command::new("sqlite3")
.arg(&cache_path)
.arg("SELECT COUNT(*) FROM session_metadata;")
.output()
.expect("sqlite3 not installed");
let count: usize = String::from_utf8_lossy(&output.stdout)
.trim()
.parse()
.unwrap_or(0);
eprintln!("Cache entries after write: {}", count);
assert!(count > 0, "Cache should have at least 1 entry after write!");
let _ = std::fs::remove_dir_all(&cache_dir);
}
#[tokio::test]
async fn test_datastore_uses_cache() {
let home = dirs::home_dir().unwrap().join(".claude");
if !home.exists() {
eprintln!("SKIP: ~/.claude not found");
return;
}
let cache_path = home.join("cache/session-metadata.db");
let _ = std::fs::remove_file(&cache_path);
let _ = std::fs::remove_file(cache_path.with_extension("db-shm"));
let _ = std::fs::remove_file(cache_path.with_extension("db-wal"));
eprintln!("Cache cleared");
let start = std::time::Instant::now();
let store = DataStore::with_defaults(home.clone(), None);
let report = store.initial_load().await;
let elapsed = start.elapsed();
eprintln!(
"Initial load took {:?} (sessions: {})",
elapsed, report.sessions_scanned
);
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
if cache_path.exists() {
let output = std::process::Command::new("sqlite3")
.arg(&cache_path)
.arg("SELECT COUNT(*) FROM session_metadata;")
.output()
.expect("sqlite3 not installed");
let count: usize = String::from_utf8_lossy(&output.stdout)
.trim()
.parse()
.unwrap_or(0);
eprintln!("Cache entries after DataStore load: {}", count);
if count == 0 {
eprintln!("⚠️ WARNING: Cache exists but empty - writes are not happening!");
} else {
eprintln!("✅ Cache populated with {} entries", count);
}
let start2 = std::time::Instant::now();
let store2 = DataStore::with_defaults(home.clone(), None);
let report2 = store2.initial_load().await;
let elapsed2 = start2.elapsed();
eprintln!(
"Second load took {:?} (sessions: {})",
elapsed2, report2.sessions_scanned
);
if elapsed2 < elapsed / 2 {
eprintln!("✅ Cache speedup observed: {:?} → {:?}", elapsed, elapsed2);
} else {
eprintln!("⚠️ WARNING: No speedup - cache not being used for reads!");
}
} else {
eprintln!("❌ ERROR: Cache file not created!");
panic!("Cache should be created by DataStore");
}
}
#[tokio::test]
async fn test_cache_hit_speedup() {
let home = dirs::home_dir().unwrap().join(".claude");
if !home.exists() {
eprintln!("SKIP: ~/.claude not found");
return;
}
let cache_dir = home.join("cache-speedtest");
let _ = std::fs::remove_dir_all(&cache_dir);
let cache = Arc::new(MetadataCache::new(&cache_dir).unwrap());
let projects_dir = home.join("projects");
let sessions: Vec<_> = walkdir::WalkDir::new(&projects_dir)
.max_depth(3)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.extension()
.map(|ext| ext == "jsonl")
.unwrap_or(false)
})
.take(10)
.map(|e| e.path().to_path_buf())
.collect();
if sessions.is_empty() {
eprintln!("SKIP: no sessions found");
return;
}
eprintln!("Testing with {} sessions", sessions.len());
let parser_nocache = SessionIndexParser::new();
let start = std::time::Instant::now();
for path in &sessions {
let _ = parser_nocache.scan_session(path).await;
}
let uncached_time = start.elapsed();
eprintln!("Uncached parse: {:?}", uncached_time);
let parser_cache = SessionIndexParser::new().with_cache(cache.clone());
let start = std::time::Instant::now();
for path in &sessions {
let _ = parser_cache.scan_session(path).await;
}
let cached_cold_time = start.elapsed();
eprintln!("Cached (cold) parse: {:?}", cached_cold_time);
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let start = std::time::Instant::now();
for path in &sessions {
let _ = parser_cache.scan_session(path).await;
}
let cached_warm_time = start.elapsed();
eprintln!("Cached (warm) parse: {:?}", cached_warm_time);
let speedup = uncached_time.as_millis() as f64 / cached_warm_time.as_millis().max(1) as f64;
eprintln!("Speedup: {:.2}x", speedup);
if speedup > 5.0 {
eprintln!("✅ Cache provides significant speedup");
} else {
eprintln!(
"⚠️ WARNING: Cache speedup is only {:.2}x (expected >5x)",
speedup
);
}
let _ = std::fs::remove_dir_all(&cache_dir);
}