#![cfg(feature = "cli")]
use std::path::PathBuf;
use tempfile::TempDir;
mod cli_workflow_tests {
use super::*;
use clap::Parser;
use std::process::Command;
#[test]
fn test_cli_index_command_parsing() {
use leindex::cli::Cli;
let cli = Cli::parse_from(["leindex", "index", "/test/project", "--force", "--progress"]);
use leindex::cli::Commands;
match cli.command {
Some(Commands::Index {
path,
force,
progress,
..
}) => {
assert_eq!(path, PathBuf::from("/test/project"));
assert!(force);
assert!(progress);
}
_ => panic!("Expected Index command"),
}
}
#[test]
fn test_cli_search_command_parsing() {
use leindex::cli::Cli;
let cli = Cli::parse_from(["leindex", "search", "authentication", "--top-k", "20"]);
use leindex::cli::Commands;
match cli.command {
Some(Commands::Search { query, top_k }) => {
assert_eq!(query, "authentication");
assert_eq!(top_k, 20);
}
_ => panic!("Expected Search command"),
}
}
#[test]
fn test_cli_analyze_command_parsing() {
use leindex::cli::Cli;
let cli = Cli::parse_from([
"leindex",
"analyze",
"How does auth work?",
"--tokens",
"5000",
]);
use leindex::cli::Commands;
match cli.command {
Some(Commands::Analyze {
query,
token_budget,
}) => {
assert_eq!(query, "How does auth work?");
assert_eq!(token_budget, 5000);
}
_ => panic!("Expected Analyze command"),
}
}
#[test]
fn test_cli_diagnostics_command_parsing() {
use leindex::cli::Cli;
let cli = Cli::parse_from(["leindex", "diagnostics"]);
use leindex::cli::Commands;
match cli.command {
Some(Commands::Diagnostics) => {
}
_ => panic!("Expected Diagnostics command"),
}
}
#[test]
fn test_cli_verbose_flag() {
use leindex::cli::Cli;
let cli = Cli::parse_from(["leindex", "-v", "diagnostics"]);
assert!(cli.verbose);
}
#[test]
fn test_cli_index_handles_deeply_nested_rust_without_aborting() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path();
let deep_file = project_dir.join("deep.rs");
let mut source = String::from("fn main() {\n");
let mut indent = String::from(" ");
for _ in 0..6000 {
source.push_str(&format!("{indent}if true {{\n"));
indent.push_str(" ");
}
source.push_str(&format!("{indent}let _x = 1;\n"));
for depth in (0..6000).rev() {
indent.truncate(depth * 4 + 4);
source.push_str(&format!("{indent}}}\n"));
}
source.push_str("}\n");
std::fs::write(&deep_file, source).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_leindex"))
.arg("index")
.arg(project_dir)
.output()
.unwrap();
assert!(
output.status.success(),
"expected leindex index to succeed for deep nesting.\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
}
mod cache_management_tests {
use super::*;
use leindex::cli::memory::{
create_pdg_entry, create_search_entry, CacheEntry, CacheSpiller, MemoryConfig, WarmStrategy,
};
use std::collections::HashMap;
#[test]
fn test_cache_entry_creation() {
let pdg_data = vec![1u8, 2, 3, 4];
let pdg_entry = create_pdg_entry("test_project".to_string(), 100, 200, &pdg_data);
match pdg_entry {
CacheEntry::PDG {
project_id,
node_count,
edge_count,
..
} => {
assert_eq!(project_id, "test_project");
assert_eq!(node_count, 100);
assert_eq!(edge_count, 200);
}
_ => panic!("Expected PDG entry"),
}
let search_data = vec![5u8, 6, 7, 8];
let search_entry = create_search_entry("test_project".to_string(), 50, &search_data);
match search_entry {
CacheEntry::SearchIndex {
project_id,
entry_count,
..
} => {
assert_eq!(project_id, "test_project");
assert_eq!(entry_count, 50);
}
_ => panic!("Expected SearchIndex entry"),
}
}
#[test]
fn test_cache_spiller_creation() {
let temp_dir = TempDir::new().unwrap();
let cache_dir = temp_dir.path().join("cache");
let config = MemoryConfig {
cache_dir,
..Default::default()
};
let spiller = CacheSpiller::new(config);
assert!(spiller.is_ok());
}
#[test]
fn test_cache_insert_and_retrieve() {
let temp_dir = TempDir::new().unwrap();
let cache_dir = temp_dir.path().join("cache");
let config = MemoryConfig {
cache_dir,
max_cache_bytes: 10_000,
..Default::default()
};
let mut spiller = CacheSpiller::new(config).unwrap();
let store = spiller.store_mut();
let entry = CacheEntry::Binary {
metadata: {
let mut map = HashMap::new();
map.insert("type".to_string(), "test".to_string());
map
},
serialized_data: vec![0u8; 100],
};
store.insert("test_key".to_string(), entry.clone()).unwrap();
let retrieved = store.get("test_key");
assert!(retrieved.is_some());
}
#[test]
fn test_cache_eviction_on_size_limit() {
let temp_dir = TempDir::new().unwrap();
let cache_dir = temp_dir.path().join("cache");
let config = MemoryConfig {
cache_dir,
max_cache_bytes: 500, ..Default::default()
};
let mut spiller = CacheSpiller::new(config).unwrap();
let store = spiller.store_mut();
for i in 0..10 {
let entry = CacheEntry::Binary {
metadata: HashMap::new(),
serialized_data: vec![0u8; 100], };
store.insert(format!("key_{}", i), entry).unwrap();
}
assert!(store.len() < 10);
assert!(store.total_bytes() <= 500);
}
#[test]
fn test_cache_key_generation() {
use leindex::cli::memory::{analysis_cache_key, pdg_cache_key, search_cache_key};
assert_eq!(pdg_cache_key("myproject"), "pdg:myproject");
assert_eq!(search_cache_key("myproject"), "search:myproject");
assert!(analysis_cache_key("how does auth work").starts_with("analysis:"));
}
#[test]
fn test_warm_strategy_variants() {
assert_eq!(WarmStrategy::All, WarmStrategy::All);
assert_eq!(WarmStrategy::PDGOnly, WarmStrategy::PDGOnly);
assert_eq!(WarmStrategy::SearchIndexOnly, WarmStrategy::SearchIndexOnly);
assert_eq!(WarmStrategy::RecentFirst, WarmStrategy::RecentFirst);
assert_ne!(WarmStrategy::All, WarmStrategy::PDGOnly);
assert_ne!(WarmStrategy::PDGOnly, WarmStrategy::SearchIndexOnly);
}
}
mod storage_persistence_tests {
use super::*;
use leindex::cli::memory::MemoryConfig;
#[test]
fn test_memory_config_default() {
let config = MemoryConfig::default();
assert_eq!(config.spill_threshold, 0.75); assert_eq!(config.check_interval_secs, 30);
assert!(config.auto_spill);
assert_eq!(config.max_cache_bytes, 96_000_000); }
#[test]
fn test_memory_config_serialization() {
let config = MemoryConfig {
spill_threshold: 0.9,
check_interval_secs: 60,
auto_spill: false,
max_cache_bytes: 1_000_000_000,
cache_dir: PathBuf::from("/tmp/cache"),
};
let serialized = serde_json::to_string(&config).unwrap();
let deserialized: MemoryConfig = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.spill_threshold, 0.9);
assert_eq!(deserialized.check_interval_secs, 60);
assert!(!deserialized.auto_spill);
assert_eq!(deserialized.max_cache_bytes, 1_000_000_000);
assert_eq!(deserialized.cache_dir, PathBuf::from("/tmp/cache"));
}
}
mod error_handling_tests {
use leindex::cli::errors::LeIndexError;
#[test]
fn test_error_display() {
let error = LeIndexError::Parse {
message: "Test parse error".to_string(),
file_path: Some("/test/file.rs".into()),
suggestion: None,
};
let display_str = format!("{}", error);
assert!(display_str.contains("Test parse error"));
}
#[test]
fn test_error_conversion() {
use std::io;
let io_error = io::Error::new(io::ErrorKind::NotFound, "File not found");
let le_error: LeIndexError = io_error.into();
match le_error {
LeIndexError::Io { .. } => {
}
_ => panic!("Expected Io error variant"),
}
}
}
mod e2e_workflow_tests {
use super::*;
#[test]
fn test_leindex_creation_workflow() {
use leindex::cli::LeIndex;
let temp_dir = TempDir::new().unwrap();
let project_path = temp_dir.path();
let src_dir = project_path.join("src");
std::fs::create_dir_all(&src_dir).unwrap();
let main_file = src_dir.join("main.rs");
std::fs::write(
&main_file,
"fn main() {\n println!(\"Hello, world!\");\n}",
)
.unwrap();
let leindex = LeIndex::new(project_path);
assert!(leindex.is_ok());
let leindex = leindex.unwrap();
assert!(!leindex.project_id().is_empty());
assert!(leindex.project_path().starts_with(temp_dir.path()));
}
#[test]
fn test_diagnostics_workflow() {
use leindex::cli::LeIndex;
let temp_dir = TempDir::new().unwrap();
let project_path = temp_dir.path();
let leindex = LeIndex::new(project_path).unwrap();
let diagnostics = leindex.get_diagnostics();
assert!(diagnostics.is_ok());
let diag = diagnostics.unwrap();
assert!(!diag.project_id.is_empty());
assert!(diag.stats.files_parsed == 0); }
#[test]
fn test_cache_integration_with_leindex() {
use leindex::cli::memory::MemoryConfig;
use leindex::cli::LeIndex;
let temp_dir = TempDir::new().unwrap();
let project_path = temp_dir.path().to_path_buf();
let cache_dir = temp_dir.path().join("cache");
let _config = MemoryConfig {
cache_dir,
max_cache_bytes: 10_000_000,
..Default::default()
};
let storage_path = project_path.join(".leindex");
std::fs::create_dir_all(&storage_path).unwrap();
let leindex = LeIndex::new(&project_path).unwrap();
let diagnostics = leindex.get_diagnostics().unwrap();
assert_eq!(diagnostics.cache_entries, 0);
assert_eq!(diagnostics.cache_bytes, 0);
assert_eq!(diagnostics.spilled_entries, 0);
assert_eq!(diagnostics.spilled_bytes, 0);
}
}
mod cache_spill_reload_tests {
use super::*;
use leindex::cli::memory::WarmStrategy;
use leindex::cli::LeIndex;
fn create_test_project(temp_dir: &TempDir) -> PathBuf {
let project_path = temp_dir.path().to_path_buf();
let src_dir = project_path.join("src");
std::fs::create_dir_all(&src_dir).unwrap();
let main_file = src_dir.join("main.rs");
std::fs::write(
&main_file,
r#"
fn main() {
println!("Hello, world!");
greet();
}
fn greet() {
println!("Greetings!");
}
"#,
)
.unwrap();
project_path
}
#[test]
fn test_spill_pdg_cache_when_no_pdg() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.spill_pdg_cache();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No PDG in memory"));
}
#[test]
fn test_spill_vector_cache() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.spill_vector_cache();
assert!(result.is_ok());
let stats = leindex.get_cache_stats().unwrap();
assert!(stats.spilled_entries > 0 || stats.cache_entries > 0);
}
#[test]
fn test_spill_all_caches() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.spill_all_caches();
assert!(result.is_ok());
let (pdg_bytes, vector_bytes) = result.unwrap();
assert_eq!(vector_bytes, vector_bytes); assert_eq!(pdg_bytes, 0);
}
#[test]
fn test_reload_pdg_when_already_loaded() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let _ = leindex.index_project(false);
let result = leindex.reload_pdg_from_cache();
assert!(result.is_ok());
}
#[test]
fn test_reload_vector_without_pdg() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.reload_vector_from_pdg();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No PDG available"));
}
#[test]
fn test_warm_caches_all_strategy() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.warm_caches(WarmStrategy::All);
assert!(result.is_ok());
let warm_result = result.unwrap();
assert_eq!(warm_result.entries_warmed, warm_result.entries_warmed);
}
#[test]
fn test_warm_caches_pdg_only_strategy() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.warm_caches(WarmStrategy::PDGOnly);
assert!(result.is_ok());
}
#[test]
fn test_warm_caches_search_only_strategy() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.warm_caches(WarmStrategy::SearchIndexOnly);
assert!(result.is_ok());
}
#[test]
fn test_get_cache_stats() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.get_cache_stats();
assert!(result.is_ok());
let stats = result.unwrap();
assert_eq!(stats.cache_entries, stats.cache_entries);
assert_eq!(stats.cache_bytes, stats.cache_bytes);
assert_eq!(stats.spilled_entries, stats.spilled_entries);
assert_eq!(stats.spilled_bytes, stats.spilled_bytes);
}
#[test]
fn test_check_memory_and_spill_below_threshold() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.check_memory_and_spill();
assert!(result.is_ok());
let spilled = result.unwrap();
assert!(!spilled);
}
#[test]
fn test_cache_spill_and_reload_workflow() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let index_result = leindex.index_project(false);
assert!(index_result.is_ok());
let initial_stats = leindex.get_cache_stats().unwrap();
let spill_result = leindex.spill_all_caches();
assert!(spill_result.is_ok());
let spilled_stats = leindex.get_cache_stats().unwrap();
assert!(spilled_stats.spilled_entries >= initial_stats.spilled_entries);
}
#[test]
fn test_warm_caches_recent_first_strategy() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let result = leindex.warm_caches(WarmStrategy::RecentFirst);
assert!(result.is_ok());
let warm_result = result.unwrap();
assert_eq!(warm_result.entries_warmed, warm_result.entries_warmed);
}
#[test]
fn test_cache_stats_after_spill() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let initial = leindex.get_cache_stats().unwrap();
leindex.spill_vector_cache().unwrap();
let after = leindex.get_cache_stats().unwrap();
assert!(after.spilled_entries >= initial.spilled_entries);
}
#[test]
fn test_multiple_spill_operations() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
leindex.spill_vector_cache().unwrap();
let first_stats = leindex.get_cache_stats().unwrap();
leindex.spill_vector_cache().unwrap();
let second_stats = leindex.get_cache_stats().unwrap();
assert!(second_stats.spilled_entries >= first_stats.spilled_entries);
}
#[test]
fn test_search_cache_hit_rate_increases_on_repeat_query() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let _ = leindex.index_project(false).unwrap();
let _ = leindex.search("greet", 5, None).unwrap();
let first = leindex.get_cache_stats().unwrap();
let _ = leindex.search("greet", 5, None).unwrap();
let second = leindex.get_cache_stats().unwrap();
assert!(second.cache_hits >= first.cache_hits);
assert!(second.cache_hit_rate >= first.cache_hit_rate);
}
#[test]
fn test_analysis_cache_persists_across_sessions() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
{
let mut first = LeIndex::new(&project_path).unwrap();
let _ = first.index_project(false).unwrap();
let _ = first.analyze("How does greet work?", 800).unwrap();
let stats = first.get_cache_stats().unwrap();
assert!(stats.cache_writes > 0);
}
let mut second = LeIndex::new(&project_path).unwrap();
let _ = second.load_from_storage();
let _ = second.analyze("How does greet work?", 800).unwrap();
let stats = second.get_cache_stats().unwrap();
assert!(
stats.cache_disk_hits > 0 || stats.cache_memory_hits > 0,
"Expected cache hit from persisted session data"
);
}
#[test]
fn test_diagnostics_reports_cache_temperature() {
let temp_dir = TempDir::new().unwrap();
let project_path = create_test_project(&temp_dir);
let mut leindex = LeIndex::new(&project_path).unwrap();
let cold = leindex.get_diagnostics().unwrap();
assert_eq!(cold.cache_temperature, "cold");
let stats = leindex.index_project(false).unwrap();
assert!(
stats.indexed_nodes > 0,
"Expected indexed nodes > 0, got {:?}",
stats
);
let _ = leindex.search("greet", 5, None).unwrap();
let _ = leindex.search("greet", 5, None).unwrap();
let cache_stats = leindex.get_cache_stats().unwrap();
assert!(
cache_stats.cache_hits > 0,
"Expected cache hits > 0 after repeated query, got {:?}",
cache_stats
);
let warm_or_hot = leindex.get_diagnostics().unwrap();
assert!(
warm_or_hot.cache_temperature == "warm" || warm_or_hot.cache_temperature == "hot",
"Expected warm/hot cache temperature, got {:?}",
warm_or_hot
);
}
}