#![cfg_attr(coverage_nightly, coverage(off))]
use crate::cli::DagType;
use crate::models::churn::{ChurnSummary, CodeChurnAnalysis};
use crate::models::dag::DependencyGraph;
use crate::models::template::{
ParameterSpec, ParameterType, TemplateCategory, TemplateResource, Toolchain,
};
use crate::services::cache::{
strategies::TemplateCacheStrategy, CacheConfig, ContentCache, SessionCacheManager,
};
use crate::services::context::{AstItem, FileContext};
use chrono::Utc;
use rustc_hash::FxHashMap;
use semver::Version;
use std::collections::HashMap;
use std::path::PathBuf;
#[tokio::test]
async fn test_session_cache_manager() {
let config = CacheConfig::default();
let manager = SessionCacheManager::new(config);
let diagnostics = manager.get_diagnostics();
assert!(diagnostics.memory_usage_mb >= 0.0);
}
#[tokio::test]
async fn test_ast_cache() {
#[derive(Clone)]
struct TestAstCacheStrategy;
impl crate::services::cache::base::CacheStrategy for TestAstCacheStrategy {
type Key = PathBuf;
type Value = FileContext;
fn cache_key(&self, path: &PathBuf) -> String {
format!("test_ast:{}", path.display())
}
fn validate(&self, _path: &PathBuf, _cached: &FileContext) -> bool {
true
}
fn ttl(&self) -> Option<std::time::Duration> {
Some(std::time::Duration::from_secs(300))
}
fn max_size(&self) -> usize {
100
}
}
let strategy = TestAstCacheStrategy;
let cache = ContentCache::new(strategy);
let file_context = FileContext {
path: "/test/file.rs".to_string(),
language: "rust".to_string(),
items: vec![AstItem::Function {
name: "main".to_string(),
visibility: "pub".to_string(),
is_async: false,
line: 1,
}],
complexity_metrics: None,
};
let key = PathBuf::from("/test/file.rs");
assert!(cache.get(&key).is_none());
cache.put(key.clone(), file_context.clone());
let cached = cache.get(&key);
assert!(cached.is_some());
assert_eq!(cached.unwrap().path, file_context.path);
}
#[tokio::test]
async fn test_template_cache() {
let strategy = TemplateCacheStrategy;
let cache = ContentCache::new(strategy);
let template = TemplateResource {
uri: "template://makefile/rust/cli".to_string(),
name: "Rust CLI Makefile".to_string(),
description: "Makefile for Rust CLI projects".to_string(),
toolchain: Toolchain::RustCli {
cargo_features: vec![],
},
category: TemplateCategory::Makefile,
parameters: vec![ParameterSpec {
name: "project_name".to_string(),
param_type: ParameterType::ProjectName,
required: true,
default_value: None,
validation_pattern: None,
description: "Name of the project".to_string(),
}],
s3_object_key: "templates/makefile/rust/cli.hbs".to_string(),
content_hash: "abc123".to_string(),
semantic_version: Version::new(1, 0, 0),
dependency_graph: vec![],
};
let key = "template://makefile/rust/cli".to_string();
cache.put(key.clone(), template.clone());
let cached = cache.get(&key);
assert!(cached.is_some());
assert_eq!(cached.unwrap().uri, template.uri);
}
#[tokio::test]
async fn test_dag_cache() {
#[derive(Clone)]
struct TestDagCacheStrategy;
impl crate::services::cache::base::CacheStrategy for TestDagCacheStrategy {
type Key = (PathBuf, DagType);
type Value = DependencyGraph;
fn cache_key(&self, (path, dag_type): &(PathBuf, DagType)) -> String {
format!("test_dag:{}:{:?}", path.display(), dag_type)
}
fn validate(&self, _key: &(PathBuf, DagType), _cached: &DependencyGraph) -> bool {
true
}
fn ttl(&self) -> Option<std::time::Duration> {
Some(std::time::Duration::from_secs(180))
}
fn max_size(&self) -> usize {
20
}
}
let strategy = TestDagCacheStrategy;
let cache = ContentCache::new(strategy);
let graph = DependencyGraph {
nodes: FxHashMap::default(),
edges: vec![],
};
let key = (PathBuf::from("/test/project"), DagType::CallGraph);
cache.put(key.clone(), graph.clone());
let cached = cache.get(&key);
assert!(cached.is_some());
}
#[tokio::test]
async fn test_churn_cache() {
#[derive(Clone)]
struct TestChurnCacheStrategy;
impl crate::services::cache::base::CacheStrategy for TestChurnCacheStrategy {
type Key = (PathBuf, u32);
type Value = CodeChurnAnalysis;
fn cache_key(&self, (repo, period_days): &(PathBuf, u32)) -> String {
format!("test_churn:{}:{}", repo.display(), period_days)
}
fn validate(&self, _key: &(PathBuf, u32), _cached: &CodeChurnAnalysis) -> bool {
true
}
fn ttl(&self) -> Option<std::time::Duration> {
Some(std::time::Duration::from_secs(1800))
}
fn max_size(&self) -> usize {
20
}
}
let strategy = TestChurnCacheStrategy;
let cache = ContentCache::new(strategy);
let churn = CodeChurnAnalysis {
generated_at: Utc::now(),
period_days: 30,
repository_root: PathBuf::from("/test/repo"),
files: vec![],
summary: ChurnSummary {
total_commits: 100,
total_files_changed: 10,
hotspot_files: vec![],
stable_files: vec![],
author_contributions: HashMap::new(),
mean_churn_score: 0.0,
variance_churn_score: 0.0,
stddev_churn_score: 0.0,
},
};
let key = (PathBuf::from("/test/repo"), 30u32);
cache.put(key.clone(), churn.clone());
let cached = cache.get(&key);
assert!(cached.is_some());
assert_eq!(cached.unwrap().period_days, churn.period_days);
}
#[tokio::test]
async fn test_git_stats_cache() {
use crate::services::cache::strategies::GitStats;
#[derive(Clone)]
struct TestGitStatsCacheStrategy;
impl crate::services::cache::base::CacheStrategy for TestGitStatsCacheStrategy {
type Key = PathBuf;
type Value = GitStats;
fn cache_key(&self, repo: &PathBuf) -> String {
format!("test_git_stats:{}", repo.display())
}
fn validate(&self, _repo: &PathBuf, _cached: &GitStats) -> bool {
true
}
fn ttl(&self) -> Option<std::time::Duration> {
Some(std::time::Duration::from_secs(900))
}
fn max_size(&self) -> usize {
10
}
}
let strategy = TestGitStatsCacheStrategy;
let cache = ContentCache::new(strategy);
let stats = GitStats {
total_commits: 100,
authors: vec!["Alice".to_string(), "Bob".to_string()],
branch: "main".to_string(),
head_commit: "abc123".to_string(),
};
let key = PathBuf::from("/test/repo");
cache.put(key.clone(), stats.clone());
let cached = cache.get(&key);
assert!(cached.is_some());
assert_eq!(cached.unwrap().total_commits, stats.total_commits);
}
#[tokio::test]
async fn test_cache_eviction() {
#[derive(Clone)]
struct SmallCacheStrategy;
impl crate::services::cache::base::CacheStrategy for SmallCacheStrategy {
type Key = PathBuf;
type Value = FileContext;
fn cache_key(&self, path: &PathBuf) -> String {
format!("test_small:{}", path.display())
}
fn validate(&self, _path: &PathBuf, _cached: &FileContext) -> bool {
true
}
fn ttl(&self) -> Option<std::time::Duration> {
Some(std::time::Duration::from_secs(300))
}
fn max_size(&self) -> usize {
2 }
}
let strategy = SmallCacheStrategy;
let cache = ContentCache::new(strategy);
let file1 = FileContext {
path: "/test/file1.rs".to_string(),
language: "rust".to_string(),
items: vec![],
complexity_metrics: None,
};
let file2 = FileContext {
path: "/test/file2.rs".to_string(),
language: "rust".to_string(),
items: vec![],
complexity_metrics: None,
};
let file3 = FileContext {
path: "/test/file3.rs".to_string(),
language: "rust".to_string(),
items: vec![],
complexity_metrics: None,
};
cache.put(PathBuf::from("/test/file1.rs"), file1);
cache.put(PathBuf::from("/test/file2.rs"), file2);
assert!(cache.get(&PathBuf::from("/test/file1.rs")).is_some());
assert!(cache.get(&PathBuf::from("/test/file2.rs")).is_some());
cache.put(PathBuf::from("/test/file3.rs"), file3);
assert!(cache.get(&PathBuf::from("/test/file1.rs")).is_none());
assert!(cache.get(&PathBuf::from("/test/file2.rs")).is_some());
assert!(cache.get(&PathBuf::from("/test/file3.rs")).is_some());
}
#[tokio::test]
async fn test_cache_clear() {
#[derive(Clone)]
struct TestClearStrategy;
impl crate::services::cache::base::CacheStrategy for TestClearStrategy {
type Key = PathBuf;
type Value = FileContext;
fn cache_key(&self, path: &PathBuf) -> String {
format!("test_clear:{}", path.display())
}
fn validate(&self, _path: &PathBuf, _cached: &FileContext) -> bool {
true
}
fn ttl(&self) -> Option<std::time::Duration> {
Some(std::time::Duration::from_secs(300))
}
fn max_size(&self) -> usize {
100
}
}
let strategy = TestClearStrategy;
let cache = ContentCache::new(strategy);
for i in 0..5 {
let file = FileContext {
path: format!("/test/file{i}.rs"),
language: "rust".to_string(),
items: vec![],
complexity_metrics: None,
};
cache.put(PathBuf::from(format!("/test/file{i}.rs")), file);
}
assert!(cache.get(&PathBuf::from("/test/file0.rs")).is_some());
assert!(cache.get(&PathBuf::from("/test/file4.rs")).is_some());
cache.clear();
for i in 0..5 {
assert!(cache
.get(&PathBuf::from(format!("/test/file{i}.rs")))
.is_none());
}
}
#[tokio::test]
async fn test_cache_ttl() {
#[derive(Clone)]
struct ShortTtlStrategy;
impl crate::services::cache::base::CacheStrategy for ShortTtlStrategy {
type Key = String;
type Value = String;
fn cache_key(&self, key: &String) -> String {
key.clone()
}
fn validate(&self, _key: &String, _cached: &String) -> bool {
true
}
fn ttl(&self) -> Option<std::time::Duration> {
Some(std::time::Duration::from_millis(100))
}
fn max_size(&self) -> usize {
10
}
}
let strategy = ShortTtlStrategy;
let cache = ContentCache::new(strategy);
cache.put("test_key".to_string(), "test_value".to_string());
assert!(cache.get(&"test_key".to_string()).is_some());
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
assert!(cache.get(&"test_key".to_string()).is_none());
}