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));
}
#[test]
fn test_search_with_context_lines() {
let mut workspace = TestWorkspace::new();
workspace.add_file(
"ctx.rs",
"fn before() {}\nfn target() {\n let x = 1;\n}\nfn after() {}\n",
);
let options = SearchOptions {
context: 1,
..Default::default()
};
let results = search_code("target", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 1, "Should find one match");
assert_eq!(
results[0].before_context,
vec!["fn before() {}"],
"Should have 1 line of before-context"
);
assert_eq!(
results[0].after_context,
vec![" let x = 1;"],
"Should have 1 line of after-context"
);
assert_eq!(results[0].line_number, 2);
}
#[test]
fn test_search_context_zero_by_default() {
let mut workspace = TestWorkspace::new();
workspace.add_file("nctx.rs", "before\nfn target() {}\nafter\n");
let options = SearchOptions::default();
let results = search_code("target", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 1);
assert!(
results[0].before_context.is_empty(),
"No context by default"
);
assert!(results[0].after_context.is_empty(), "No context by default");
}
#[test]
fn test_search_context_larger_than_file() {
let mut workspace = TestWorkspace::new();
workspace.add_file("small.rs", "a\nmatch\nb\n");
let options = SearchOptions {
context: 50,
..Default::default()
};
let results = search_code("match", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 1);
assert_eq!(results[0].before_context, vec!["a"]);
assert_eq!(results[0].after_context, vec!["b"]);
}
#[test]
fn test_search_context_json_serialization() {
let mut workspace = TestWorkspace::new();
workspace.add_file("json_ctx.rs", "line1\nline2\nmatch_me\nline4\nline5\n");
let options = SearchOptions {
context: 2,
..Default::default()
};
let results = search_code("match_me", workspace.path(), &options).expect("Search failed");
let json = serde_json::to_string(&results).expect("JSON serialization failed");
assert!(
json.contains("before_context"),
"JSON should include before_context"
);
assert!(
json.contains("after_context"),
"JSON should include after_context"
);
assert!(json.contains("line1"), "JSON should contain context line1");
assert!(json.contains("line4"), "JSON should contain context line4");
}
#[test]
fn test_search_context_multiple_matches() {
let mut workspace = TestWorkspace::new();
workspace.add_file("multi.rs", "x\nfoo\ny\nz\nfoo\nw\n");
let options = SearchOptions {
context: 1,
..Default::default()
};
let results = search_code("foo", workspace.path(), &options).expect("Search failed");
assert_eq!(results.len(), 2, "Should find two matches");
assert_eq!(results[0].before_context, vec!["x"]);
assert_eq!(results[0].after_context, vec!["y"]);
assert_eq!(results[1].before_context, vec!["z"]);
assert_eq!(results[1].after_context, vec!["w"]);
}