use reflex::{CacheManager, IndexConfig, Indexer, QueryEngine, QueryFilter, SymbolKind};
use std::fs;
use tempfile::TempDir;
#[test]
fn test_full_workflow() {
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
let sample_code = r#"
fn main() {
println!("Hello, Reflex!");
}
fn greet(name: &str) {
println!("Hello, {}!", name);
}
"#;
std::fs::write(temp_path.join("test.rs"), sample_code).unwrap();
let cache = CacheManager::new(temp_path);
let config = IndexConfig::default();
let indexer = Indexer::new(cache, config);
let stats = indexer.index(temp_path, false).unwrap();
assert_eq!(stats.total_files, 1);
let cache = CacheManager::new(temp_path);
let engine = QueryEngine::new(cache);
let results = engine.find_symbol("main").unwrap();
assert_eq!(results.len(), 1); assert_eq!(results[0].symbol.as_deref(), Some("main"));
}
#[test]
fn test_cache_initialization() {
let temp_dir = TempDir::new().unwrap();
let cache = CacheManager::new(temp_dir.path());
assert!(!cache.exists());
cache.init().unwrap();
assert!(cache.path().exists());
}
#[test]
fn test_cache_clear() {
let temp_dir = TempDir::new().unwrap();
let cache = CacheManager::new(temp_dir.path());
cache.init().unwrap();
assert!(cache.path().exists());
cache.clear().unwrap();
assert!(!cache.path().exists());
}
#[test]
fn test_index_and_fulltext_search_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("main.rs"),
"fn main() {\n println!(\"hello world\");\n}",
)
.unwrap();
fs::write(
project.join("lib.rs"),
"pub fn hello() -> String {\n \"hello\".to_string()\n}",
)
.unwrap();
fs::write(project.join("utils.rs"), "// hello helper\nfn helper() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let stats = indexer.index(project, false).unwrap();
assert_eq!(stats.total_files, 3);
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter::default();
let results = engine.search("hello", filter).unwrap();
assert!(results.len() >= 3);
assert!(results.iter().any(|r| r.path.contains("main.rs")));
assert!(results.iter().any(|r| r.path.contains("lib.rs")));
assert!(results.iter().any(|r| r.path.contains("utils.rs")));
}
#[test]
fn test_index_and_symbol_search_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("main.rs"),
"fn greet() {}\nfn main() {\n greet();\n}",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
..Default::default()
};
let results = engine.search("greet", filter).unwrap();
assert!(!results.is_empty());
assert!(results.iter().all(|r| r.kind == SymbolKind::Function));
}
#[test]
fn test_index_and_regex_search_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("main.rs"),
"fn test1() {}\nfn test2() {}\nfn other() {}",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
use_regex: true,
..Default::default()
};
let results = engine.search(r"fn test\d", filter).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_incremental_indexing_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn main() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let stats1 = indexer.index(project, false).unwrap();
assert_eq!(stats1.total_files, 1);
fs::write(project.join("lib.rs"), "pub fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let stats2 = indexer.index(project, false).unwrap();
assert_eq!(stats2.total_files, 2);
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
use_contains: true, ..Default::default()
};
let results = engine.search("mai", filter).unwrap(); assert!(!results.is_empty(), "Should find at least main.rs");
}
#[test]
fn test_modify_file_and_reindex_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let main_path = project.join("main.rs");
fs::write(&main_path, "fn old_function() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
use_contains: true, ..Default::default()
};
let results = engine.search("old", filter.clone()).unwrap();
assert!(!results.is_empty());
fs::write(&main_path, "fn new_function() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let results = engine.search("new", filter).unwrap();
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.symbol.as_ref().is_some_and(|s| s.contains("new")))
);
}
#[test]
fn test_multi_language_indexing_and_search() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn greet() {}").unwrap();
fs::write(project.join("app.ts"), "function greet() {}").unwrap();
fs::write(project.join("script.py"), "def greet(): pass").unwrap();
fs::write(project.join("main.js"), "function greet() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let stats = indexer.index(project, false).unwrap();
assert_eq!(stats.total_files, 4);
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter::default();
let results = engine.search("greet", filter).unwrap();
assert!(results.len() >= 4);
}
#[test]
fn test_language_filtered_search_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn test() {}").unwrap();
fs::write(project.join("test.py"), "def test(): pass").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("test", filter).unwrap();
assert!(results.iter().all(|r| r.path.ends_with(".rs")));
}
#[test]
fn test_combined_filters_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::write(
project.join("src/lib.rs"),
"struct Point {}\nfn point_new() {}",
)
.unwrap();
fs::write(project.join("tests/test.rs"), "fn test_point() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
kind: Some(SymbolKind::Function),
file_pattern: Some("src/".to_string()),
symbols_mode: true,
use_contains: true, ..Default::default()
};
let results = engine.search("poi", filter).unwrap();
assert!(!results.is_empty());
assert!(results.iter().all(|r| r.path.contains("src/")));
assert!(results.iter().all(|r| r.kind == SymbolKind::Function));
}
#[test]
fn test_limit_and_sorting_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let content = (0..20)
.map(|i| format!("fn test{}() {{}}", i))
.collect::<Vec<_>>()
.join("\n");
fs::write(project.join("many.rs"), content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
limit: Some(5),
use_contains: true, ..Default::default()
};
let results = engine.search("test", filter).unwrap();
assert_eq!(results.len(), 5);
for i in 0..results.len().saturating_sub(1) {
assert!(results[i].span.start_line <= results[i + 1].span.start_line);
}
}
#[test]
fn test_query_without_index_fails() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter::default();
let result = engine.search("test", filter);
assert!(result.is_err());
}
#[test]
fn test_index_empty_directory_succeeds() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let stats = indexer.index(project, false).unwrap();
assert_eq!(stats.total_files, 0);
}
#[test]
fn test_search_empty_index_returns_no_results() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter::default();
let results = engine.search("anything", filter).unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_cache_persists_across_sessions() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn test() {}").unwrap();
{
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
}
{
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter::default();
let results = engine.search("test", filter).unwrap();
assert!(!results.is_empty());
}
}
#[test]
fn test_clear_and_rebuild_workflow() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
cache.clear().unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let stats = indexer.index(project, false).unwrap();
assert_eq!(stats.total_files, 1);
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter::default();
let results = engine.search("test", filter).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_glob_single_pattern() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::write(project.join("src/main.rs"), "fn extract_pattern() {}").unwrap();
fs::write(project.join("tests/test.rs"), "fn extract_pattern() {}").unwrap();
fs::write(project.join("other.rs"), "fn extract_pattern() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/**/*.rs".to_string()],
..Default::default()
};
let results = engine.search("extract_pattern", filter).unwrap();
assert!(!results.is_empty());
assert!(results.iter().all(|r| r.path.contains("src/")));
assert!(results.iter().any(|r| r.path.contains("main.rs")));
assert!(!results.iter().any(|r| r.path.contains("tests/")));
assert!(!results.iter().any(|r| r.path.contains("other.rs")));
}
#[test]
fn test_glob_multiple_patterns() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("examples")).unwrap();
fs::create_dir_all(project.join("build")).unwrap();
fs::write(project.join("src/lib.rs"), "TODO: implement").unwrap();
fs::write(project.join("examples/demo.rs"), "TODO: add example").unwrap();
fs::write(project.join("build/gen.rs"), "TODO: generated").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/**".to_string(), "**/examples/**".to_string()],
..Default::default()
};
let results = engine.search("TODO", filter).unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().any(|r| r.path.contains("src/lib.rs")));
assert!(results.iter().any(|r| r.path.contains("examples/demo.rs")));
assert!(!results.iter().any(|r| r.path.contains("build/")));
}
#[test]
fn test_glob_wildcard_patterns() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("test_one.rs"), "fn test() {}").unwrap();
fs::write(project.join("test_two.rs"), "fn test() {}").unwrap();
fs::write(project.join("other.rs"), "fn test() {}").unwrap();
fs::write(project.join("main.py"), "def test(): pass").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/test_*.rs".to_string()],
..Default::default()
};
let results = engine.search("test", filter).unwrap();
assert_eq!(results.len(), 2);
assert!(
results
.iter()
.all(|r| r.path.contains("test_") && r.path.ends_with(".rs"))
);
assert!(!results.iter().any(|r| r.path.contains("other.rs")));
assert!(!results.iter().any(|r| r.path.contains("main.py")));
}
#[test]
fn test_glob_specific_extension() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn pattern() {}").unwrap();
fs::write(project.join("app.ts"), "function pattern() {}").unwrap();
fs::write(project.join("script.py"), "def pattern(): pass").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/*.rs".to_string()],
..Default::default()
};
let results = engine.search("pattern", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.ends_with(".rs"));
}
#[test]
fn test_glob_specific_directory() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src/parsers")).unwrap();
fs::create_dir_all(project.join("src/utils")).unwrap();
fs::write(project.join("src/parsers/rust.rs"), "TODO: parse").unwrap();
fs::write(project.join("src/utils/helpers.rs"), "TODO: help").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/parsers/**".to_string()],
..Default::default()
};
let results = engine.search("TODO", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("parsers/rust.rs"));
assert!(!results.iter().any(|r| r.path.contains("utils/")));
}
#[test]
fn test_exclude_single_pattern() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("build")).unwrap();
fs::write(project.join("src/main.rs"), "fn extract_pattern() {}").unwrap();
fs::write(
project.join("build/generated.rs"),
"fn extract_pattern() {}",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
exclude_patterns: vec!["**/build/**".to_string()],
..Default::default()
};
let results = engine.search("extract_pattern", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/main.rs"));
assert!(!results.iter().any(|r| r.path.contains("build/")));
}
#[test]
fn test_exclude_multiple_patterns() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("build")).unwrap();
fs::create_dir_all(project.join("target")).unwrap();
fs::write(project.join("src/main.rs"), "TODO: implement").unwrap();
fs::write(project.join("build/gen.rs"), "TODO: generated").unwrap();
fs::write(project.join("target/debug.rs"), "TODO: debug").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
exclude_patterns: vec!["**/build/**".to_string(), "**/target/**".to_string()],
..Default::default()
};
let results = engine.search("TODO", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/main.rs"));
}
#[test]
fn test_exclude_generated_files() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn pattern() {}").unwrap();
fs::write(project.join("generated.rs"), "fn pattern() {}").unwrap();
fs::write(project.join("codegen.rs"), "fn pattern() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
exclude_patterns: vec!["**/generated.rs".to_string(), "**/codegen.rs".to_string()],
..Default::default()
};
let results = engine.search("pattern", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("main.rs"));
}
#[test]
fn test_exclude_specific_directories() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::create_dir_all(project.join("examples")).unwrap();
fs::write(project.join("src/lib.rs"), "TODO").unwrap();
fs::write(project.join("tests/test.rs"), "TODO").unwrap();
fs::write(project.join("examples/demo.rs"), "TODO").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
exclude_patterns: vec!["**/tests/**".to_string(), "**/examples/**".to_string()],
..Default::default()
};
let results = engine.search("TODO", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/lib.rs"));
}
#[test]
fn test_paths_only_deduplication() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let content = r#"
fn extract_pattern() {}
fn test_extract() {}
struct Pattern {}
fn another_extract_pattern() {}
"#;
fs::write(project.join("main.rs"), content).unwrap();
fs::write(project.join("other.rs"), "fn extract_pattern() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
paths_only: true,
use_contains: true, ..Default::default()
};
let results = engine.search("extract", filter).unwrap();
assert_eq!(results.len(), 2);
let mut paths: Vec<_> = results.iter().map(|r| &r.path).collect();
paths.sort();
paths.dedup();
assert_eq!(paths.len(), 2);
}
#[test]
fn test_paths_only_with_language_filter() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn pattern() {}").unwrap();
fs::write(project.join("app.ts"), "function pattern() {}").unwrap();
fs::write(project.join("lib.rs"), "fn pattern() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
paths_only: true,
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("pattern", filter).unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.path.ends_with(".rs")));
}
#[test]
fn test_paths_only_single_match_per_file() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let content = (0..50)
.map(|i| format!("fn test{}() {{}}", i))
.collect::<Vec<_>>()
.join("\n");
fs::write(project.join("many.rs"), content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
paths_only: true,
use_contains: true, ..Default::default()
};
let results = engine.search("test", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("many.rs"));
}
#[test]
fn test_paths_only_across_multiple_files() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::write(project.join("src/a.rs"), "TODO TODO TODO").unwrap();
fs::write(project.join("src/b.rs"), "TODO").unwrap();
fs::write(project.join("src/c.rs"), "TODO TODO").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
paths_only: true,
..Default::default()
};
let results = engine.search("TODO", filter).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_glob_and_exclude_together() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src/parsers")).unwrap();
fs::create_dir_all(project.join("src/utils")).unwrap();
fs::write(project.join("src/parsers/rust.rs"), "TODO: parse").unwrap();
fs::write(project.join("src/parsers/generated.rs"), "TODO: generated").unwrap();
fs::write(project.join("src/utils/helpers.rs"), "TODO: help").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/**".to_string()],
exclude_patterns: vec!["**/generated.rs".to_string()],
..Default::default()
};
let results = engine.search("TODO", filter).unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().any(|r| r.path.contains("rust.rs")));
assert!(results.iter().any(|r| r.path.contains("helpers.rs")));
assert!(!results.iter().any(|r| r.path.contains("generated.rs")));
}
#[test]
fn test_glob_exclude_and_language() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("build")).unwrap();
fs::write(project.join("src/main.rs"), "fn pattern() {}").unwrap();
fs::write(project.join("src/app.ts"), "function pattern() {}").unwrap();
fs::write(project.join("build/gen.rs"), "fn pattern() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/**".to_string()],
exclude_patterns: vec!["**/*.ts".to_string()],
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("pattern", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/main.rs"));
}
#[test]
fn test_glob_exclude_and_symbols() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
let src_content = r#"
fn extract_pattern() {}
fn test() {
extract_pattern();
}
"#;
fs::write(project.join("src/lib.rs"), src_content).unwrap();
fs::write(project.join("tests/test.rs"), "fn extract_pattern() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/**".to_string()],
exclude_patterns: vec!["**/tests/**".to_string()],
symbols_mode: true,
use_contains: true, ..Default::default()
};
let results = engine.search("extract", filter).unwrap();
assert!(!results.is_empty());
assert!(results.iter().all(|r| r.path.contains("src/")));
assert!(results.iter().all(|r| r.kind == SymbolKind::Function));
}
#[test]
fn test_all_filters_together() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::create_dir_all(project.join("build")).unwrap();
fs::write(
project.join("src/main.rs"),
"fn extract_pattern() {}\nfn other_extract() {}",
)
.unwrap();
fs::write(project.join("tests/test.rs"), "fn extract_pattern() {}").unwrap();
fs::write(project.join("build/gen.rs"), "fn extract_pattern() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/**".to_string()],
exclude_patterns: vec!["**/tests/**".to_string(), "**/build/**".to_string()],
language: Some(reflex::Language::Rust),
symbols_mode: true,
paths_only: true,
use_contains: true, ..Default::default()
};
let results = engine.search("extract", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/main.rs"));
}
#[test]
fn test_glob_exclude_paths_with_limit() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
for i in 0..10 {
fs::write(project.join(format!("src/file{}.rs", i)), "TODO: implement").unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
glob_patterns: vec!["**/src/**".to_string()],
paths_only: true,
limit: Some(5),
..Default::default()
};
let results = engine.search("TODO", filter).unwrap();
assert_eq!(results.len(), 5);
assert!(results.iter().all(|r| r.path.contains("src/")));
}
#[test]
fn test_ast_query_basic_rust() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("main.rs"),
"fn hello() {}\nfn world() {}\nstruct Point {}",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
..Default::default()
};
let results = engine
.search_ast_all_files("(function_item) @fn", filter)
.unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.kind == SymbolKind::Function));
assert!(results.iter().any(|r| r.symbol.as_deref() == Some("hello")));
assert!(results.iter().any(|r| r.symbol.as_deref() == Some("world")));
}
#[test]
fn test_ast_query_with_glob_pattern() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::write(project.join("src/lib.rs"), "fn src_func() {}").unwrap();
fs::write(project.join("tests/test.rs"), "fn test_func() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
glob_patterns: vec!["**/src/**/*.rs".to_string()],
..Default::default()
};
let results = engine
.search_ast_all_files("(function_item) @fn", filter)
.unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/lib.rs"));
assert_eq!(results[0].symbol.as_deref(), Some("src_func"));
}
#[test]
fn test_ast_query_with_exclude_pattern() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("build")).unwrap();
fs::write(project.join("src/main.rs"), "fn main_func() {}").unwrap();
fs::write(project.join("build/generated.rs"), "fn gen_func() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
exclude_patterns: vec!["**/build/**".to_string()],
..Default::default()
};
let results = engine
.search_ast_all_files("(function_item) @fn", filter)
.unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/main.rs"));
assert!(!results.iter().any(|r| r.path.contains("build/")));
}
#[test]
fn test_ast_query_multiple_languages() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn rust_func() {}").unwrap();
fs::write(project.join("app.py"), "def python_func(): pass").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter_rust = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
..Default::default()
};
let results_rust = engine
.search_ast_all_files("(function_item) @fn", filter_rust)
.unwrap();
assert_eq!(results_rust.len(), 1);
assert_eq!(results_rust[0].symbol.as_deref(), Some("rust_func"));
let filter_python = QueryFilter {
language: Some(reflex::Language::Python),
use_ast: true,
..Default::default()
};
let results_python = engine
.search_ast_all_files("(function_definition) @fn", filter_python)
.unwrap();
assert_eq!(results_python.len(), 1);
assert!(results_python[0].preview.contains("python_func"));
}
#[test]
fn test_ast_query_requires_language() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: None, use_ast: true,
..Default::default()
};
let result = engine.search_ast_all_files("(function_item) @fn", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Language must be specified"));
}
#[test]
fn test_ast_query_structs() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("main.rs"),
"struct Point {}\nstruct Line {}\nfn test() {}",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
..Default::default()
};
let results = engine
.search_ast_all_files("(struct_item) @struct", filter)
.unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.kind == SymbolKind::Struct));
assert!(results.iter().any(|r| r.symbol.as_deref() == Some("Point")));
assert!(results.iter().any(|r| r.symbol.as_deref() == Some("Line")));
}
#[test]
fn test_ast_query_glob_and_exclude_together() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src/parsers")).unwrap();
fs::create_dir_all(project.join("src/utils")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::write(project.join("src/parsers/rust.rs"), "fn parse_rust() {}").unwrap();
fs::write(
project.join("src/parsers/generated.rs"),
"fn gen_parse() {}",
)
.unwrap();
fs::write(project.join("src/utils/helpers.rs"), "fn helper() {}").unwrap();
fs::write(project.join("tests/test.rs"), "fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
glob_patterns: vec!["**/src/**/*.rs".to_string()],
exclude_patterns: vec!["**/generated.rs".to_string()],
..Default::default()
};
let results = engine
.search_ast_all_files("(function_item) @fn", filter)
.unwrap();
assert_eq!(results.len(), 2);
assert!(
results
.iter()
.any(|r| r.symbol.as_deref() == Some("parse_rust"))
);
assert!(
results
.iter()
.any(|r| r.symbol.as_deref() == Some("helper"))
);
assert!(
!results
.iter()
.any(|r| r.symbol.as_deref() == Some("gen_parse"))
);
assert!(!results.iter().any(|r| r.symbol.as_deref() == Some("test")));
}
#[test]
fn test_ast_query_python_classes() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("app.py"),
"class User:\n pass\n\nclass Product:\n pass\n\ndef helper():\n pass",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Python),
use_ast: true,
..Default::default()
};
let results = engine
.search_ast_all_files("(class_definition) @class", filter)
.unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.kind == SymbolKind::Class));
assert!(results.iter().any(|r| r.preview.contains("User")));
assert!(results.iter().any(|r| r.preview.contains("Product")));
}
#[test]
fn test_ast_query_invalid_pattern() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
..Default::default()
};
let result = engine.search_ast_all_files("(function_item @fn", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Invalid AST query pattern") || error_msg.contains("error"));
}
#[test]
fn test_keyword_rust_struct() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let corpus_content = include_str!("corpus/rust/structs.rs");
fs::write(project.join("structs.rs"), corpus_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(results.len(), 11);
assert!(results.iter().all(|r| r.kind == SymbolKind::Struct));
}
#[test]
fn test_keyword_php_class() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let corpus_content = include_str!("corpus/php/classes.php");
fs::write(project.join("classes.php"), corpus_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::PHP),
..Default::default()
};
let results = engine.search("class", filter).unwrap();
assert_eq!(results.len(), 9);
assert!(results.iter().all(|r| r.kind == SymbolKind::Class));
}
#[test]
fn test_keyword_typescript_interface() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let corpus_content = include_str!("corpus/typescript/interfaces.ts");
fs::write(project.join("interfaces.ts"), corpus_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::TypeScript),
..Default::default()
};
let results = engine.search("interface", filter).unwrap();
assert_eq!(results.len(), 11);
assert!(results.iter().all(|r| r.kind == SymbolKind::Interface));
}
#[test]
fn test_keyword_without_lang_flag() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let php_content = include_str!("corpus/edge_cases/mixed_languages/php_classes.php");
let ts_content = include_str!("corpus/edge_cases/mixed_languages/ts_classes.ts");
let js_content = include_str!("corpus/edge_cases/mixed_languages/js_classes.js");
fs::write(project.join("test.php"), php_content).unwrap();
fs::write(project.join("test.ts"), ts_content).unwrap();
fs::write(project.join("test.js"), js_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: None, ..Default::default()
};
let results = engine.search("class", filter).unwrap();
assert_eq!(results.len(), 6);
assert!(results.iter().all(|r| r.kind == SymbolKind::Class));
}
#[test]
fn test_keyword_with_lang_filter() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let php_content = include_str!("corpus/edge_cases/mixed_languages/php_classes.php");
let ts_content = include_str!("corpus/edge_cases/mixed_languages/ts_classes.ts");
fs::write(project.join("test.php"), php_content).unwrap();
fs::write(project.join("test.ts"), ts_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::PHP),
..Default::default()
};
let results = engine.search("class", filter).unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.kind == SymbolKind::Class));
assert!(results.iter().all(|r| r.path.ends_with(".php")));
}
#[test]
fn test_keywords_in_strings_ignored() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let corpus_content = include_str!("corpus/edge_cases/keywords_in_strings.rs");
fs::write(project.join("test.rs"), corpus_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("struct", filter.clone()).unwrap();
assert_eq!(results.len(), 0);
let results_fn = engine.search("fn", filter).unwrap();
assert_eq!(results_fn.len(), 5);
assert!(results_fn.iter().all(|r| r.kind == SymbolKind::Function));
}
#[test]
fn test_exact_counts_rust_corpus() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let corpus_content = include_str!("corpus/rust/structs.rs");
fs::write(project.join("structs.rs"), corpus_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(
results.len(),
11,
"Expected 11 structs in rust/structs.rs corpus"
);
assert!(results.iter().all(|r| r.kind == SymbolKind::Struct));
}
#[test]
fn test_keyword_plus_glob() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::write(project.join("src/lib.rs"), "struct SrcStruct {}").unwrap();
fs::write(project.join("tests/test.rs"), "struct TestStruct {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
glob_patterns: vec!["**/src/**".to_string()],
..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/"));
assert!(results[0].symbol.as_deref() == Some("SrcStruct"));
}
#[test]
fn test_keyword_plus_exclude() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("target")).unwrap();
fs::write(project.join("src/lib.rs"), "struct SrcStruct {}").unwrap();
fs::write(project.join("target/debug.rs"), "struct TargetStruct {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
exclude_patterns: vec!["**/target/**".to_string()],
..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/"));
assert!(!results.iter().any(|r| r.path.contains("target/")));
}
#[test]
fn test_non_keyword_search_normal_mode() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("test.rs"),
"struct Point {}\nfn my_struct_builder() {}\nlet my_struct = Point {};",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
use_contains: true, ..Default::default()
};
let results = engine.search("my_struct", filter).unwrap();
assert!(!results.is_empty());
assert!(results.iter().any(|r| r.kind == SymbolKind::Function));
}
#[test]
fn test_uppercase_keyword_not_trigger() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("test.rs"),
"struct lowercase {}\nstruct UPPERCASE {}",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
..Default::default()
};
let results = engine.search("STRUCT", filter).unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_explicit_kind_overrides_keyword_inference() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("test.rs"),
"fn greet() {}\nfn process() {}\nstruct Point {}",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
kind: Some(SymbolKind::Function), ..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(results.len(), 2); assert!(results.iter().all(|r| r.kind == SymbolKind::Function));
}
#[test]
fn test_partial_keyword_match_normal_search() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let corpus_content = include_str!("corpus/edge_cases/keywords_partial.rs");
fs::write(project.join("test.rs"), corpus_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
use_contains: true, ..Default::default()
};
let results = engine.search("struct_builder", filter).unwrap();
assert!(!results.is_empty());
assert!(results.iter().any(|r| r.kind == SymbolKind::Function));
assert!(results.iter().any(|r| {
r.symbol
.as_ref()
.is_some_and(|s| s.contains("struct_builder"))
}));
}
#[test]
fn test_keyword_exact_match_required() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("test.rs"),
"fn test() {}\nfn another() {}\nlet fn_pointer = test;",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("fn", filter).unwrap();
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.kind == SymbolKind::Function));
}
#[test]
fn test_keyword_empty_results() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("test.rs"), "fn test() {}\nfn another() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Rust),
..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_multi_language_exact_counts() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let python_classes = include_str!("corpus/python/classes.py");
let go_structs = include_str!("corpus/go/structs.go");
let java_classes = include_str!("corpus/java/classes.java");
fs::write(project.join("test.py"), python_classes).unwrap();
fs::write(project.join("test.go"), go_structs).unwrap();
fs::write(project.join("test.java"), java_classes).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
..Default::default()
};
let results = engine.search("class", filter).unwrap();
assert_eq!(results.len(), 24);
assert!(results.iter().all(|r| r.kind == SymbolKind::Class));
let python_count = results.iter().filter(|r| r.path.ends_with(".py")).count();
let java_count = results.iter().filter(|r| r.path.ends_with(".java")).count();
assert_eq!(python_count, 13); assert_eq!(java_count, 11); }
#[test]
fn test_keyword_with_paths_only() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::write(
project.join("src/models.rs"),
"struct User {}\nstruct Post {}",
)
.unwrap();
fs::write(
project.join("src/types.rs"),
"struct Config {}\nstruct State {}",
)
.unwrap();
fs::write(project.join("src/lib.rs"), "struct App {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
paths_only: true,
..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(results.len(), 3);
let paths: Vec<_> = results.iter().map(|r| &r.path).collect();
let unique_paths: std::collections::HashSet<_> = paths.iter().collect();
assert_eq!(unique_paths.len(), 3);
}
#[test]
fn test_keyword_with_limit() {
let temp = TempDir::new().unwrap();
let project = temp.path();
let corpus_content = include_str!("corpus/rust/structs.rs");
fs::write(project.join("test.rs"), corpus_content).unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
limit: Some(5),
..Default::default()
};
let results = engine.search("struct", filter).unwrap();
assert_eq!(results.len(), 5);
assert!(results.iter().all(|r| r.kind == SymbolKind::Struct));
}
#[test]
fn test_keyword_multi_language_function_synonyms() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("test.rs"), "fn rust_func() {}").unwrap();
fs::write(project.join("test.py"), "def python_func(): pass").unwrap();
fs::write(project.join("test.js"), "function js_func() {}").unwrap();
fs::write(project.join("test.go"), "func go_func() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter_rust = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Rust),
..Default::default()
};
let results_rust = engine.search("fn", filter_rust).unwrap();
assert_eq!(results_rust.len(), 1);
assert!(results_rust[0].path.ends_with(".rs"));
let filter_python = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Python),
..Default::default()
};
let results_python = engine.search("def", filter_python).unwrap();
assert_eq!(results_python.len(), 1);
assert!(results_python[0].path.ends_with(".py"));
let filter_js = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::JavaScript),
..Default::default()
};
let results_js = engine.search("function", filter_js).unwrap();
assert_eq!(results_js.len(), 1);
assert!(results_js[0].path.ends_with(".js"));
let filter_go = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Go),
..Default::default()
};
let results_go = engine.search("func", filter_go).unwrap();
assert_eq!(results_go.len(), 1);
assert!(results_go[0].path.ends_with(".go"));
}
#[test]
fn test_broad_query_short_pattern_blocked() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
force: false, ..Default::default()
};
let result = engine.search("ab", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Query too broad"));
assert!(error_msg.contains("too short"));
assert!(error_msg.contains("--force"));
}
#[test]
fn test_broad_query_short_pattern_bypass_with_force() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "let abc = 42;").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
force: true, ..Default::default()
};
let result = engine.search("ab", filter);
assert!(result.is_ok());
}
#[test]
fn test_broad_query_ast_without_glob_blocked() {
let temp = TempDir::new().unwrap();
let project = temp.path();
for i in 0..105 {
fs::write(project.join(format!("test{}.rs", i)), "fn func() {}").unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
force: false, glob_patterns: vec![], ..Default::default()
};
let result = engine.search_ast_all_files("(function_item) @fn", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Query too broad"));
assert!(error_msg.contains("AST query without --glob"));
assert!(error_msg.contains("--force"));
}
#[test]
fn test_broad_query_ast_without_glob_bypass_with_force() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("test.rs"), "fn func() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
force: true, glob_patterns: vec![], ..Default::default()
};
let result = engine.search_ast_all_files("(function_item) @fn", filter);
assert!(result.is_ok());
let results = result.unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_broad_query_ast_with_glob_allowed() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir_all(project.join("src")).unwrap();
fs::create_dir_all(project.join("tests")).unwrap();
fs::write(project.join("src/main.rs"), "fn main() {}").unwrap();
fs::write(project.join("tests/test.rs"), "fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
force: false, glob_patterns: vec!["**/src/**/*.rs".to_string()], ..Default::default()
};
let result = engine.search_ast_all_files("(function_item) @fn", filter);
assert!(result.is_ok());
let results = result.unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].path.contains("src/main.rs"));
}
#[test]
fn test_broad_query_long_pattern_allowed() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn extract_symbols() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
force: false, use_contains: true, ..Default::default()
};
let result = engine.search("extract", filter);
assert!(result.is_ok());
let results = result.unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_broad_query_regex_short_pattern_allowed() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "let ab = 42; let abc = 43;").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
use_regex: true,
force: false, ..Default::default()
};
let result = engine.search("ab", filter);
assert!(result.is_ok());
}
#[test]
fn test_broad_query_error_message_short_pattern() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(project.join("main.rs"), "fn test() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
force: false,
..Default::default()
};
let result = engine.search("ab", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Query too broad"));
assert!(error_msg.contains("too short"));
assert!(error_msg.contains("2 characters"));
assert!(error_msg.contains("Use a longer, more specific pattern"));
assert!(error_msg.contains("Add a language filter: --lang"));
assert!(error_msg.contains("Add a file path filter: --file"));
assert!(error_msg.contains("Use --force to bypass this check"));
assert!(error_msg.contains("rfx query \"ab\" --force"));
}
#[test]
fn test_broad_query_error_message_ast_without_glob() {
let temp = TempDir::new().unwrap();
let project = temp.path();
for i in 0..105 {
fs::write(project.join(format!("test{}.rs", i)), "fn test() {}").unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
force: false,
glob_patterns: vec![],
..Default::default()
};
let result = engine.search_ast_all_files("(function_item) @fn", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Query too broad"));
assert!(error_msg.contains("AST query without --glob"));
assert!(error_msg.contains("scan the ENTIRE codebase"));
assert!(error_msg.contains("Add --glob to restrict AST query"));
assert!(error_msg.contains("Use --symbols instead"));
assert!(error_msg.contains("Use --force to bypass this check"));
}
#[test]
fn test_broad_query_with_exclude_still_requires_glob_for_ast() {
let temp = TempDir::new().unwrap();
let project = temp.path();
for i in 0..105 {
fs::write(project.join(format!("test{}.rs", i)), "fn func() {}").unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
language: Some(reflex::Language::Rust),
use_ast: true,
force: false,
glob_patterns: vec![], exclude_patterns: vec!["**/target/**".to_string()], ..Default::default()
};
let result = engine.search_ast_all_files("(function_item) @fn", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Query too broad"));
assert!(error_msg.contains("AST query without --glob"));
}
#[test]
fn test_broad_query_large_index_short_pattern_early_check() {
let temp = TempDir::new().unwrap();
let project = temp.path();
for i in 0..150 {
fs::write(project.join(format!("file{}.rs", i)), "fn get_data() {}").unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
force: false, test_large_index_threshold: Some(100), ..Default::default()
};
let result = engine.search("get", filter);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Query too broad"));
assert!(error_msg.contains("large index"));
assert!(error_msg.contains("150 files")); assert!(error_msg.contains("3 characters")); assert!(error_msg.contains("--force"));
}
#[test]
fn test_broad_query_large_index_force_bypass() {
let temp = TempDir::new().unwrap();
let project = temp.path();
for i in 0..150 {
if i == 0 {
fs::write(project.join(format!("file{}.rs", i)), "fn get_data() {}").unwrap();
} else {
fs::write(project.join(format!("file{}.rs", i)), "fn process() {}").unwrap();
}
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
force: true, use_contains: true, test_large_index_threshold: Some(100), ..Default::default()
};
let result = engine.search("get", filter);
assert!(result.is_ok());
let results = result.unwrap();
assert!(!results.is_empty()); }
#[test]
fn test_broad_query_small_index_short_pattern_allowed() {
let temp = TempDir::new().unwrap();
let project = temp.path();
for i in 0..100 {
fs::write(project.join(format!("file{}.rs", i)), "fn get_data() {}").unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
force: false, use_contains: true, ..Default::default()
};
let result = engine.search("get", filter);
assert!(result.is_ok());
let results = result.unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_broad_query_language_filter_applied_before_check() {
let temp = TempDir::new().unwrap();
let project = temp.path();
for i in 0..120 {
fs::write(
project.join(format!("kernel{}.c", i)),
"int index_lookup(void) { return 0; }",
)
.unwrap();
}
for i in 0..80 {
fs::write(
project.join(format!("rust{}.rs", i)),
"fn index_lookup() -> usize { 0 }",
)
.unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
language: Some(reflex::Language::Rust),
use_contains: true, force: false, test_large_index_threshold: Some(100), ..Default::default()
};
let result = engine.search("index", filter);
assert!(
result.is_ok(),
"Query should succeed when language filter reduces candidate set below threshold"
);
let results = result.unwrap();
assert!(
!results.is_empty(),
"Should find at least one symbol matching 'index' in Rust files"
);
}
#[test]
fn test_broad_query_glob_filter_applied_before_check() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::create_dir(project.join("build")).unwrap();
for i in 0..120 {
fs::write(
project.join(format!("build/file{}.rs", i)),
"fn index_lookup() -> usize { 0 }",
)
.unwrap();
}
fs::create_dir(project.join("src")).unwrap();
for i in 0..80 {
fs::write(
project.join(format!("src/file{}.rs", i)),
"fn index_lookup() -> usize { 0 }",
)
.unwrap();
}
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let filter = QueryFilter {
symbols_mode: true,
glob_patterns: vec!["src/**/*.rs".to_string()],
use_contains: true, force: false, test_large_index_threshold: Some(100), ..Default::default()
};
let result = engine.search("index", filter);
assert!(
result.is_ok(),
"Query should succeed when glob filter reduces candidate set below threshold. \
Without the fix, this would error: 'Query too broad - would be expensive to execute'"
);
}
#[test]
fn test_partial_write_recovery_trigrams_bin() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("hello.rs"),
"fn greet() -> &'static str { \"hello\" }",
)
.unwrap();
fs::write(
project.join("lib.rs"),
"pub fn add(a: i32, b: i32) -> i32 { a + b }",
)
.unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
assert!(cache.validate().is_ok(), "Initial cache should be valid");
let trigrams_path = project.join(".reflex/trigrams.bin");
{
let file = std::fs::OpenOptions::new()
.write(true)
.open(&trigrams_path)
.unwrap();
file.set_len(2).unwrap();
}
let cache = CacheManager::new(project);
let result = cache.validate();
assert!(
result.is_err(),
"Truncated trigrams.bin must fail validate()"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("trigrams.bin"),
"Error must name the corrupted file, got: {err_msg}"
);
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let result = indexer.index(project, false);
assert!(
result.is_ok(),
"Re-indexing after partial trigrams.bin write must succeed, got: {:?}",
result.err()
);
let cache = CacheManager::new(project);
assert!(
cache.validate().is_ok(),
"Rebuilt cache must pass validate()"
);
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let results = engine.search("greet", QueryFilter::default()).unwrap();
assert!(!results.is_empty(), "Should find 'greet' in rebuilt cache");
}
#[test]
fn test_partial_write_recovery_content_bin() {
let temp = TempDir::new().unwrap();
let project = temp.path();
fs::write(
project.join("main.rs"),
"fn main() { println!(\"world\"); }",
)
.unwrap();
fs::write(project.join("util.rs"), "pub fn helper() {}").unwrap();
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
indexer.index(project, false).unwrap();
let cache = CacheManager::new(project);
assert!(cache.validate().is_ok(), "Initial cache should be valid");
let content_path = project.join(".reflex/content.bin");
{
let file = std::fs::OpenOptions::new()
.write(true)
.open(&content_path)
.unwrap();
file.set_len(2).unwrap();
}
let cache = CacheManager::new(project);
let result = cache.validate();
assert!(
result.is_err(),
"Truncated content.bin must fail validate()"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("content.bin"),
"Error must name the corrupted file, got: {err_msg}"
);
let cache = CacheManager::new(project);
let indexer = Indexer::new(cache, IndexConfig::default());
let result = indexer.index(project, false);
assert!(
result.is_ok(),
"Re-indexing after partial content.bin write must succeed, got: {:?}",
result.err()
);
let cache = CacheManager::new(project);
assert!(
cache.validate().is_ok(),
"Rebuilt cache must pass validate()"
);
let cache = CacheManager::new(project);
let engine = QueryEngine::new(cache);
let results = engine.search("world", QueryFilter::default()).unwrap();
assert!(!results.is_empty(), "Should find 'world' in rebuilt cache");
}