mod fixtures;
use codesearch::search::search_code;
use codesearch::types::SearchOptions;
use codesearch::{analysis, complexity, deadcode, export};
use fixtures::TestWorkspace;
use std::fs;
#[test]
fn test_search_and_export_workflow() {
let workspace = TestWorkspace::new();
let options = SearchOptions::default();
let results = search_code("test", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty(), "Should find test matches");
let export_path = workspace.path().join("results.json");
export::export_results(&results, export_path.to_str().unwrap(), "test").expect("Export failed");
assert!(export_path.exists());
let content = fs::read_to_string(&export_path).expect("Failed to read export");
assert!(content.contains("test"));
}
#[test]
fn test_search_with_multiple_extensions() {
let workspace = TestWorkspace::new();
let options = SearchOptions {
extensions: Some(vec!["rs".to_string(), "py".to_string()]),
..Default::default()
};
let results = search_code("main", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty());
for result in results {
assert!(result.file.ends_with(".rs") || result.file.ends_with(".py"));
}
}
#[test]
fn test_search_with_fuzzy_matching() {
let workspace = TestWorkspace::new();
let options = SearchOptions {
fuzzy: true,
fuzzy_threshold: 0.5,
..Default::default()
};
let results = search_code("tst", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty());
}
#[test]
fn test_analyze_then_search() {
let workspace = TestWorkspace::new();
let analyze_result = analysis::analyze_codebase(workspace.path(), None, None);
assert!(analyze_result.is_ok());
let options = SearchOptions::default();
let results = search_code("fn", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty());
}
#[test]
fn test_complexity_analysis_workflow() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"complex.rs",
r#"
fn complex_function(x: i32) -> i32 {
if x > 0 {
if x > 10 {
if x > 20 {
return x * 2;
}
return x + 10;
}
return x + 5;
}
return 0;
}
"#,
);
let result = complexity::analyze_complexity(
workspace.path(),
Some(&[String::from("rs")]),
None,
Some(1),
false,
);
assert!(result.is_ok());
}
#[test]
fn test_deadcode_detection_workflow() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"deadcode.rs",
r#"
fn used_function() {
println!("Used");
}
fn unused_function() {
// TODO: implement this
let unused_var = 42;
}
"#,
);
let result = deadcode::detect_dead_code(workspace.path(), Some(&[String::from("rs")]), None);
assert!(result.is_ok());
}
#[test]
fn test_search_ranking() {
let workspace = TestWorkspace::new();
let options = SearchOptions {
rank: true,
..Default::default()
};
let results = search_code("test", workspace.path(), &options).expect("Search failed");
if results.len() > 1 {
for i in 0..results.len() - 1 {
assert!(results[i].score >= results[i + 1].score);
}
}
}
#[test]
fn test_search_with_exclusions() {
let workspace = TestWorkspace::new();
let subdir = workspace.create_subdir("excluded");
fs::write(subdir.join("test.rs"), "fn excluded_test() {}").expect("Failed to write");
let options = SearchOptions {
exclude: Some(vec!["excluded".to_string()]),
..Default::default()
};
let results = search_code("excluded", workspace.path(), &options).expect("Search failed");
for result in results {
assert!(!result.file.contains("excluded"));
}
}
#[test]
fn test_max_results_limit() {
let mut workspace = TestWorkspace::new();
for i in 0..10 {
workspace.add_file(&format!("file{i}.txt"), "test test test test test");
}
let options = SearchOptions {
max_results: 2,
..Default::default()
};
let results = search_code("test", workspace.path(), &options).expect("Search failed");
for result in results {
assert!(result.matches.len() <= 2);
}
}
#[test]
fn test_case_sensitive_search() {
let mut workspace = TestWorkspace::new();
workspace.add_file("case.txt", "Test TEST test TeSt");
let options_sensitive = SearchOptions {
ignore_case: false,
..Default::default()
};
let results = search_code("Test", workspace.path(), &options_sensitive).expect("Search failed");
for result in results {
assert!(result.content.contains("Test"));
}
}
#[test]
fn test_case_insensitive_search() {
let mut workspace = TestWorkspace::new();
workspace.add_file("case.txt", "Test TEST test TeSt");
let options_insensitive = SearchOptions {
ignore_case: true,
..Default::default()
};
let results =
search_code("test", workspace.path(), &options_insensitive).expect("Search failed");
assert!(!results.is_empty());
}
#[test]
fn test_empty_directory() {
let workspace = TestWorkspace::with_files(&[]);
let options = SearchOptions::default();
let results = search_code("test", workspace.path(), &options).expect("Search failed");
assert!(results.is_empty());
}
#[test]
fn test_nested_directories() {
let workspace = TestWorkspace::new();
let subdir1 = workspace.create_subdir("level1");
let subdir2 = subdir1.join("level2");
fs::create_dir_all(&subdir2).expect("Failed to create nested dir");
fs::write(subdir2.join("nested.rs"), "fn nested_test() {}").expect("Failed to write");
let options = SearchOptions::default();
let results = search_code("nested", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty());
assert!(results[0].file.contains("level1"));
assert!(results[0].file.contains("level2"));
}
#[test]
fn test_bm25_ranking() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"frequent.rs",
"fn helper() {}
fn helper2() { helper(); }
fn helper3() { helper(); helper2(); }
",
);
workspace.add_file("rare.rs", "fn rare_helper() {}");
let options = SearchOptions {
rank: true,
use_bm25: true,
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty(), "Should find helper matches");
for r in &results {
assert!(r.score >= 0.0 && r.score <= 100.0, "BM25 score should be in 0-100 range");
}
}
#[test]
fn test_context_filter_only_code() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"mixed.rs",
"fn helper() { println!(\"hello\"); }\n// helper is a test function\n",
);
let options = SearchOptions {
context_filter: Some(codesearch::types::ContextFilter::Code),
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert!(
results.iter().any(|r| r.content.contains("println!")),
"Should find code match"
);
assert!(
!results.iter().any(|r| r.content.starts_with("//")),
"Should not find comment match"
);
}
#[test]
fn test_context_filter_only_comments() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"mixed.rs",
"fn helper() { println!(\"hello\"); }\n// helper is a test function\n",
);
let options = SearchOptions {
context_filter: Some(codesearch::types::ContextFilter::Comments),
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert!(
results.iter().any(|r| r.content.starts_with("//")),
"Should find comment match"
);
assert!(
!results.iter().any(|r| r.content.contains("println!")),
"Should not find code match"
);
}
#[test]
fn test_fixed_string_search() {
let mut workspace = TestWorkspace::new();
workspace.add_file("test.rs", "fn test_helper() { let x = 42; }");
let options = SearchOptions {
fixed_string: true,
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty(), "Fixed-string should find literal match");
assert!(results.iter().any(|r| r.content.contains("helper")));
}
#[test]
fn test_fixed_string_no_regex_interpretation() {
let mut workspace = TestWorkspace::new();
workspace.add_file("regex.rs", "let x = \"[a-z]+\";");
let options = SearchOptions {
fixed_string: true,
..Default::default()
};
let results = search_code("[a-z]+", workspace.path(), &options).expect("Search failed");
assert!(
!results.is_empty(),
"Fixed-string should match literal regex-like text"
);
}
#[test]
fn test_fixed_string_case_insensitive() {
let mut workspace = TestWorkspace::new();
workspace.add_file("case.rs", "fn Helper() {}");
let options = SearchOptions {
fixed_string: true,
ignore_case: true,
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert!(
!results.is_empty(),
"Case-insensitive fixed-string should match"
);
}
#[test]
fn test_smart_case_lowercase_query() {
let mut workspace = TestWorkspace::new();
workspace.add_file("case.rs", "fn Helper() {}\nfn helper() {}");
let options = SearchOptions {
ignore_case: false, ..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 2, "Smart case: lowercase query should match both cases");
}
#[test]
fn test_smart_case_uppercase_query() {
let mut workspace = TestWorkspace::new();
workspace.add_file("case.rs", "fn Helper() {}\nfn helper() {}");
let options = SearchOptions {
ignore_case: false, ..Default::default()
};
let results = search_code("Helper", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 1, "Smart case: uppercase query should match only exact case");
assert!(results[0].content.contains("Helper"));
}
#[test]
fn test_only_declarations_filter() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"decl.rs",
"fn helper() {}\n helper();\n",
);
let options = SearchOptions {
declaration_filter: Some(codesearch::types::DeclarationFilter::Declarations),
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 1, "Should find only declaration");
assert!(results[0].content.contains("fn helper"));
}
#[test]
fn test_only_usages_filter() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"usage.rs",
"fn helper() {}\n helper();\n",
);
let options = SearchOptions {
declaration_filter: Some(codesearch::types::DeclarationFilter::Usages),
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 1, "Should find only usage");
assert!(results[0].content.contains("helper();"));
}
#[test]
fn test_test_file_dampening() {
let mut workspace = TestWorkspace::new();
workspace.add_file("src/helper.rs", "fn helper() {}");
workspace.add_file("tests/helper_test.rs", "fn helper() {}");
let options = SearchOptions {
rank: true,
..Default::default()
};
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
let src_pos = results.iter().position(|r| r.file.contains("src/"));
let test_pos = results.iter().position(|r| r.file.contains("tests/"));
assert!(
src_pos.is_some(),
"Should find match in src file"
);
assert!(
test_pos.is_some(),
"Should find match in test file"
);
if let (Some(src), Some(test)) = (src_pos, test_pos) {
assert!(
src < test,
"Src file match should rank higher than test file match"
);
assert!(
results[src].score > results[test].score,
"Src file score should be higher than test file score"
);
}
}
#[test]
fn test_json_output_contains_expected_fields() {
let mut workspace = TestWorkspace::new();
workspace.add_file("test.rs", "fn helper() {}");
let options = SearchOptions::default();
let results = search_code("helper", workspace.path(), &options).expect("Search failed");
assert!(!results.is_empty());
let json = serde_json::to_string(&results).expect("JSON serialization failed");
assert!(json.contains("file"), "JSON should contain file field");
assert!(json.contains("line_number"), "JSON should contain line_number field");
assert!(json.contains("content"), "JSON should contain content field");
assert!(json.contains("matches"), "JSON should contain matches field");
assert!(json.contains("score"), "JSON should contain score field");
assert!(json.contains("relevance"), "JSON should contain relevance field");
}
#[test]
fn test_config_file_loading() {
use std::io::Write;
let mut workspace = TestWorkspace::new();
workspace.add_file("src/main.rs", "fn helper() {}");
workspace.add_file("src/lib.rs", "fn helper2() {}");
let config_path = workspace.path().join(".codesearch.toml");
let mut file = std::fs::File::create(&config_path).unwrap();
file.write_all(b"max_results = 1\n").unwrap();
let config = codesearch::config::Config::load(workspace.path())
.expect("Config should load");
assert_eq!(config.max_results, Some(1));
}