use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use tempfile::TempDir;
use probe_code::search::{perform_probe, SearchOptions};
fn create_test_file(dir: &TempDir, filename: &str, content: &str) -> PathBuf {
let file_path = dir.path().join(filename);
let mut file = File::create(&file_path).expect("Failed to create test file");
file.write_all(content.as_bytes())
.expect("Failed to write test content");
file_path
}
fn create_test_directory_structure(root_dir: &TempDir) {
let src_dir = root_dir.path().join("src");
fs::create_dir(&src_dir).expect("Failed to create src directory");
let rust_content1 = r#"
// This is a Rust file with a function
fn search_function(query: &str) -> bool {
println!("Searching for: {}", query);
query.contains("search")
}
struct SearchResult {
file: String,
line: usize,
content: String,
}
impl SearchResult {
fn new(file: String, line: usize, content: String) -> Self {
Self { file, line, content }
}
}
"#;
create_test_file(root_dir, "src/search.rs", rust_content1);
let rust_content2 = r#"
mod search;
fn main() {
let query = "search term";
let found = search::search_function(query);
println!("Found: {}", found);
}
"#;
create_test_file(root_dir, "src/main.rs", rust_content2);
let js_content = r#"
// This is a JavaScript file with a function
function searchFunction(query) {
console.log(`Searching for: ${query}`);
return query.includes('search');
}
class SearchResult {
constructor(file, line, content) {
this.file = file;
this.line = line;
this.content = content;
}
}
// Export the functions and classes
module.exports = {
searchFunction,
SearchResult
};
"#;
create_test_file(root_dir, "src/search.js", js_content);
let py_content = r#"
# This is a Python file with a function
def search_function(query):
print(f"Searching for: {query}")
return "search" in query
class SearchResult:
def __init__(self, file, line, content):
self.file = file
self.line = line
self.content = content
"#;
create_test_file(root_dir, "src/search.py", py_content);
let tests_dir = root_dir.path().join("tests");
fs::create_dir(&tests_dir).expect("Failed to create tests directory");
let test_content = r#"
// This is a test file for the search functionality
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_search_function() {
let query = "search term";
let found = search_function(query);
assert!(found);
}
}
"#;
create_test_file(root_dir, "tests/search_test.rs", test_content);
let node_modules_dir = root_dir.path().join("node_modules");
fs::create_dir(&node_modules_dir).expect("Failed to create node_modules directory");
create_test_file(
root_dir,
"node_modules/ignored.js",
"This file should be ignored",
);
}
#[test]
fn test_search_single_term() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_directory_structure(&temp_dir);
let queries = vec!["search".to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: true,
language: None,
reranker: "hybrid",
frequency_search: false,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_results = perform_probe(&options).expect("Failed to perform search");
assert!(!search_results.results.is_empty());
let found_rust = search_results
.results
.iter()
.any(|r| r.file.ends_with("search.rs"));
let found_js = search_results
.results
.iter()
.any(|r| r.file.ends_with("search.js"));
let found_py = search_results
.results
.iter()
.any(|r| r.file.ends_with("search.py"));
assert!(found_rust, "Should find matches in Rust file");
assert!(found_js, "Should find matches in JavaScript file");
assert!(found_py, "Should find matches in Python file");
let found_ignored = search_results
.results
.iter()
.any(|r| r.file.contains("node_modules"));
assert!(!found_ignored, "Should not find matches in ignored files");
}
#[test]
#[ignore] fn test_search_multiple_terms() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_directory_structure(&temp_dir);
let queries = vec!["search".to_string(), "function".to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: true,
language: None,
reranker: "hybrid",
frequency_search: false,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_results = perform_probe(&options).expect("Failed to perform search");
assert!(!search_results.results.is_empty());
let has_both_terms = search_results
.results
.iter()
.any(|r| r.code.contains("search") && r.code.contains("function"));
assert!(has_both_terms, "Should find matches with both terms");
}
#[test]
fn test_search_files_only() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_directory_structure(&temp_dir);
let queries = vec!["search".to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: true,
custom_ignores: &custom_ignores,
exclude_filenames: true,
language: None,
reranker: "hybrid",
frequency_search: false,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_results = perform_probe(&options).expect("Failed to perform search");
assert!(!search_results.results.is_empty());
for result in &search_results.results {
assert_eq!(result.node_type, "file");
assert_eq!(result.code, ""); }
let found_rust = search_results
.results
.iter()
.any(|r| r.file.ends_with("search.rs"));
let found_js = search_results
.results
.iter()
.any(|r| r.file.ends_with("search.js"));
let found_py = search_results
.results
.iter()
.any(|r| r.file.ends_with("search.py"));
assert!(found_rust, "Should find matches in Rust file");
assert!(found_js, "Should find matches in JavaScript file");
assert!(found_py, "Should find matches in Python file");
}
#[test]
#[ignore]
fn test_search_include_filenames() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_directory_structure(&temp_dir);
let search_file_path = create_test_file(
&temp_dir,
"search-file-without-content.txt", "This file doesn't contain the search term anywhere in its content.",
);
println!("Created test file at: {search_file_path:?}");
let queries = vec!["search".to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: false,
language: None,
reranker: "hybrid",
frequency_search: false,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_results = perform_probe(&options).expect("Failed to perform search");
assert!(!search_results.results.is_empty());
let found_by_filename = search_results
.results
.iter()
.any(|r| r.file.contains("search-file-without-content.txt"));
assert!(
found_by_filename,
"Should find file with search in the name"
);
for result in &search_results.results {
if result.file.contains("search-file-without-content.txt") {
assert_eq!(result.matched_by_filename, Some(true));
}
}
}
#[test]
fn test_search_with_limits() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_directory_structure(&temp_dir);
let queries = vec!["search".to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: true,
language: None,
reranker: "hybrid",
frequency_search: false,
max_results: Some(2), max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_results = perform_probe(&options).expect("Failed to perform search");
assert!(!search_results.results.is_empty());
assert!(search_results.results.len() <= 2);
assert!(search_results.limits_applied.is_some());
let limits = search_results.limits_applied.unwrap();
assert_eq!(limits.max_results, Some(2));
if search_results.results.len() == 2 && !search_results.skipped_files.is_empty() {
assert!(!search_results.skipped_files.is_empty());
}
}
#[test]
fn test_frequency_search() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_directory_structure(&temp_dir);
let queries = vec!["search".to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: true,
language: None,
reranker: "hybrid",
frequency_search: true,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_results = perform_probe(&options).expect("Failed to perform search");
assert!(!search_results.results.is_empty());
println!("Frequency search completed successfully");
}
#[test]
fn test_filename_content_term_combination() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let content = r#"
// This is a Go file with a whitelist function
func checkWhitelist(address string) bool {
// Check if the address is in the whitelist
return true
}
func main() {
// Some other code
result := checkWhitelist("192.168.1.1")
fmt.Println(result)
}
"#;
create_test_file(&temp_dir, "ip_utils.go", content);
let queries = vec!["ip".to_string(), "whitelist".to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: false, language: None,
reranker: "hybrid",
frequency_search: false,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let _ = perform_probe(&options).expect("Failed to perform search");
println!("Filename content term combination search completed successfully");
}
#[test]
fn test_search_with_custom_ignores() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_test_directory_structure(&temp_dir);
let custom_ignores = vec!["*.py".to_string()];
let queries = vec!["search".to_string()];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: true,
language: None,
reranker: "hybrid",
frequency_search: false,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: false,
no_merge: true,
merge_threshold: None,
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_results = perform_probe(&options).expect("Failed to perform search");
assert!(!search_results.results.is_empty());
let found_py = search_results
.results
.iter()
.any(|r| r.file.ends_with(".py"));
assert!(!found_py, "Should not find matches in Python files");
let found_rust = search_results
.results
.iter()
.any(|r| r.file.ends_with(".rs"));
let found_js = search_results
.results
.iter()
.any(|r| r.file.ends_with(".js"));
assert!(
found_rust || found_js,
"Should find matches in non-Python files"
);
}
#[test]
fn test_search_with_block_merging() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let file1_path = temp_dir.path().join("merge_test.rs");
let file1_content = r#"
// Test file for block merging
fn calculate_sum(a: i32, b: i32) -> i32 {
// This function calculates a sum
a + b
}
fn calculate_product(a: i32, b: i32) -> i32 {
// This function calculates a product
a * b
}
fn main() {
let x = 5;
let y = 10;
let sum = calculate_sum(x, y);
println!("Sum: {}", sum);
let product = calculate_product(x, y);
println!("Product: {}", product);
}
"#;
let file2_path = temp_dir.path().join("non_adjacent.rs");
let file2_content = r#"
// File with non-adjacent calculational blocks
fn calculate_sum(a: i32, b: i32) -> i32 {
a + b
}
// Many lines of unrelated code...
// ...
// ...
// ...
// ...
// ...
// ...
// ...
// ...
// ...
fn calculate_product(a: i32, b: i32) -> i32 {
a * b
}
"#;
fs::write(file1_path, file1_content).expect("Failed to write test file");
fs::write(file2_path, file2_content).expect("Failed to write test file");
let query = "calculate";
let queries = vec![query.to_string()];
let custom_ignores: Vec<String> = vec![];
let options = SearchOptions {
path: temp_dir.path(),
queries: &queries,
files_only: false,
custom_ignores: &custom_ignores,
exclude_filenames: true,
language: None,
reranker: "combined",
frequency_search: false,
max_results: None,
max_bytes: None,
max_tokens: None,
allow_tests: true,
no_merge: false,
merge_threshold: Some(5),
dry_run: false,
session: None,
timeout: 30,
exact: false,
};
let search_result = perform_probe(&options).expect("Search should succeed");
assert!(
!search_result.results.is_empty(),
"Search should return results"
);
let mut file_counts = std::collections::HashMap::new();
for result in &search_result.results {
let file_name = result.file.clone();
*file_counts.entry(file_name).or_insert(0) += 1;
}
let merge_test_count = file_counts
.get(
&temp_dir
.path()
.join("merge_test.rs")
.to_string_lossy()
.to_string(),
)
.unwrap_or(&0);
assert_eq!(
*merge_test_count, 1,
"Adjacent blocks in merge_test.rs should be merged into a single block"
);
let non_adjacent_count = file_counts
.get(
&temp_dir
.path()
.join("non_adjacent.rs")
.to_string_lossy()
.to_string(),
)
.unwrap_or(&0);
assert!(
*non_adjacent_count >= 1,
"Non-adjacent blocks may be separate or merged depending on threshold"
);
for result in &search_result.results {
if result.file.contains("merge_test.rs") {
assert!(
result.code.contains("calculate_sum") && result.code.contains("calculate_product"),
"Merged block should contain content from both functions"
);
}
}
}