use opengrep::{search::SearchEngine, Config};
use std::path::PathBuf;
use tempfile::TempDir;
use tokio;
#[tokio::test]
async fn test_basic_search() {
let config = Config::default();
let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
std::fs::write(&test_file, "fn main() {\n println!(\"Hello\");\n}").unwrap();
let results = engine.search("main", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].matches.len(), 1);
assert_eq!(results[0].matches[0].line_number, 1);
}
#[tokio::test]
async fn test_ast_context() {
let mut config = Config::default();
config.output.show_ast_context = true;
let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
std::fs::write(&test_file, r#"
struct Foo {
bar: i32,
}
impl Foo {
fn new() -> Self {
Self { bar: 42 }
}
}
"#).unwrap();
let results = engine.search("42", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].matches.len(), 1);
let ast_context = results[0].matches[0].ast_context.as_ref().unwrap();
assert!(!ast_context.nodes.is_empty());
}
#[tokio::test]
async fn test_regex_search() {
let mut config = Config::default();
config.search.regex = true;
let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.rs");
std::fs::write(&test_file, "fn test_one() {}\nfn test_two() {}").unwrap();
let results = engine.search(r"test_\w+", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].matches.len(), 2);
}
#[tokio::test]
async fn test_case_insensitive_search() {
let mut config = Config::default();
config.search.ignore_case = true;
let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "Hello World\nhello world\nHELLO WORLD").unwrap();
let results = engine.search("hello", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].matches.len(), 3);
}
#[tokio::test]
async fn test_context_lines() {
let mut config = Config::default();
config.output.before_context = 1;
config.output.after_context = 1;
let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "line 1\nline 2\nTARGET\nline 4\nline 5").unwrap();
let results = engine.search("TARGET", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].matches.len(), 1);
let match_item = &results[0].matches[0];
assert_eq!(match_item.before_context.len(), 1);
assert_eq!(match_item.after_context.len(), 1);
assert_eq!(match_item.before_context[0], "line 2");
assert_eq!(match_item.after_context[0], "line 4");
}
#[tokio::test]
async fn test_multiple_files() {
let config = Config::default();
let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
std::fs::write(temp_dir.path().join("file1.rs"), "fn main() {}").unwrap();
std::fs::write(temp_dir.path().join("file2.rs"), "fn test() {}").unwrap();
std::fs::write(temp_dir.path().join("file3.rs"), "struct Foo {}").unwrap();
let results = engine.search("fn", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 2); assert_eq!(results.iter().map(|r| r.matches.len()).sum::<usize>(), 2);
}
#[tokio::test]
async fn test_hidden_files() {
let mut config = Config::default();
config.search.hidden = true;
let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
std::fs::write(temp_dir.path().join(".hidden"), "secret content").unwrap();
let results = engine.search("secret", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].matches.len(), 1);
}
#[tokio::test]
async fn test_max_file_size() {
let mut config = Config::default();
config.search.max_file_size = Some(10); let engine = SearchEngine::new(config);
let temp_dir = TempDir::new().unwrap();
let large_content = "a".repeat(100);
std::fs::write(temp_dir.path().join("large.txt"), &large_content).unwrap();
let results = engine.search("a", &[temp_dir.path().to_path_buf()]).await.unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_language_detection() {
use opengrep::parsers::detect_language;
assert_eq!(detect_language(&PathBuf::from("test.rs")), Some("rust".to_string()));
assert_eq!(detect_language(&PathBuf::from("test.py")), Some("python".to_string()));
assert_eq!(detect_language(&PathBuf::from("test.js")), Some("javascript".to_string()));
assert_eq!(detect_language(&PathBuf::from("test.unknown")), None);
}
#[test]
fn test_config_from_cli() {
use opengrep::cli::{Cli, OutputFormat};
use clap::Parser;
let cli = Cli::parse_from(&[
"opengrep",
"-i",
"-n",
"-B", "3",
"-A", "3",
"pattern"
]);
let config = opengrep::Config::from_cli(&cli).unwrap();
assert!(config.search.ignore_case);
assert!(config.output.line_numbers);
assert_eq!(config.output.before_context, 3);
assert_eq!(config.output.after_context, 3);
}
#[cfg(feature = "ai")]
#[tokio::test]
async fn test_ai_insights() {
use opengrep::ai::AiService;
use opengrep::AiConfig;
if std::env::var("OPENAI_API_KEY").is_err() {
return;
}
let config = AiConfig {
api_key: std::env::var("OPENAI_API_KEY").unwrap(),
model: "gpt-4o-mini".to_string(),
enable_insights: true,
enable_explanation: true,
max_tokens: 100,
};
let ai_service = AiService::new(&config).unwrap();
let patterns = ai_service.suggest_search_patterns("find memory leaks").await.unwrap();
assert!(!patterns.is_empty());
let explanation = ai_service.explain_code("unsafe { ptr::read(ptr) }", Some("rust")).await.unwrap();
assert!(!explanation.is_empty());
}