use std::path::PathBuf;
use dk_core::*;
use dk_engine::graph::SearchIndex;
use tempfile::TempDir;
use uuid::Uuid;
fn make_symbol(
name: &str,
qualified_name: &str,
kind: SymbolKind,
file: &str,
signature: Option<&str>,
doc_comment: Option<&str>,
) -> Symbol {
Symbol {
id: Uuid::new_v4(),
name: name.to_string(),
qualified_name: qualified_name.to_string(),
kind,
visibility: Visibility::Public,
file_path: PathBuf::from(file),
span: Span {
start_byte: 0,
end_byte: 100,
},
signature: signature.map(|s| s.to_string()),
doc_comment: doc_comment.map(|s| s.to_string()),
parent: None,
last_modified_by: None,
last_modified_intent: None,
}
}
#[test]
fn test_index_and_search_symbols() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_id = Uuid::new_v4();
let sym = Symbol {
id: Uuid::new_v4(),
name: "authenticate_user".into(),
qualified_name: "src/auth.rs::authenticate_user".into(),
kind: SymbolKind::Function,
visibility: Visibility::Public,
file_path: PathBuf::from("src/auth.rs"),
span: Span {
start_byte: 0,
end_byte: 100,
},
signature: Some("fn authenticate_user(req: &Request) -> Result<User>".into()),
doc_comment: Some("Authenticates a user by validating their token".into()),
parent: None,
last_modified_by: None,
last_modified_intent: None,
};
index.index_symbol(repo_id, &sym).unwrap();
index.commit().unwrap();
let results = index.search(repo_id, "authenticate", 10).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], sym.id);
let results = index.search(repo_id, "token", 10).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], sym.id);
}
#[test]
fn test_search_by_signature() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_id = Uuid::new_v4();
let sym = make_symbol(
"process_data",
"crate::pipeline::process_data",
SymbolKind::Function,
"src/pipeline.rs",
Some("fn process_data(input: Vec<u8>) -> Result<Output>"),
Some("Processes raw byte data into structured output"),
);
index.index_symbol(repo_id, &sym).unwrap();
index.commit().unwrap();
let results = index.search(repo_id, "Vec", 10).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], sym.id);
let results = index.search(repo_id, "Output", 10).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], sym.id);
}
#[test]
fn test_search_no_results_different_repo() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_a = Uuid::new_v4();
let repo_b = Uuid::new_v4();
let sym = make_symbol(
"exclusive_func",
"crate::exclusive_func",
SymbolKind::Function,
"src/lib.rs",
Some("fn exclusive_func()"),
None,
);
index.index_symbol(repo_a, &sym).unwrap();
index.commit().unwrap();
let results = index.search(repo_a, "exclusive", 10).unwrap();
assert_eq!(results.len(), 1);
let results = index.search(repo_b, "exclusive", 10).unwrap();
assert!(results.is_empty());
}
#[test]
fn test_multiple_symbols_ranking() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_id = Uuid::new_v4();
let sym_strong = make_symbol(
"parse_json",
"crate::parser::parse_json",
SymbolKind::Function,
"src/parser.rs",
Some("fn parse_json(input: &str) -> ParseResult"),
Some("Parse a JSON string into a value"),
);
let sym_weak = make_symbol(
"validate_input",
"crate::validator::validate_input",
SymbolKind::Function,
"src/validator.rs",
Some("fn validate_input(s: &str) -> bool"),
Some("Validates input before parse step"),
);
let sym_unrelated = make_symbol(
"send_email",
"crate::mailer::send_email",
SymbolKind::Function,
"src/mailer.rs",
Some("fn send_email(to: &str, body: &str)"),
Some("Sends an email notification"),
);
index.index_symbol(repo_id, &sym_strong).unwrap();
index.index_symbol(repo_id, &sym_weak).unwrap();
index.index_symbol(repo_id, &sym_unrelated).unwrap();
index.commit().unwrap();
let results = index.search(repo_id, "parse", 10).unwrap();
assert!(results.len() >= 2);
assert!(!results.contains(&sym_unrelated.id));
assert!(results.contains(&sym_strong.id));
assert!(results.contains(&sym_weak.id));
assert_eq!(results[0], sym_strong.id);
}
#[test]
fn test_remove_symbol() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_id = Uuid::new_v4();
let sym = make_symbol(
"temporary_func",
"crate::temporary_func",
SymbolKind::Function,
"src/lib.rs",
Some("fn temporary_func()"),
Some("A temporary function to be removed"),
);
index.index_symbol(repo_id, &sym).unwrap();
index.commit().unwrap();
let results = index.search(repo_id, "temporary", 10).unwrap();
assert_eq!(results.len(), 1);
index.remove_symbol(sym.id).unwrap();
index.commit().unwrap();
let results = index.search(repo_id, "temporary", 10).unwrap();
assert!(results.is_empty());
}
#[test]
fn test_search_by_file_path() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_id = Uuid::new_v4();
let sym = make_symbol(
"handler",
"crate::routes::handler",
SymbolKind::Function,
"src/routes/api/v2/handler.rs",
Some("fn handler()"),
None,
);
index.index_symbol(repo_id, &sym).unwrap();
index.commit().unwrap();
let results = index.search(repo_id, "handler", 10).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], sym.id);
}
#[test]
fn test_search_empty_query_returns_nothing() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_id = Uuid::new_v4();
let sym = make_symbol(
"some_func",
"crate::some_func",
SymbolKind::Function,
"src/lib.rs",
None,
None,
);
index.index_symbol(repo_id, &sym).unwrap();
index.commit().unwrap();
let results = index.search(repo_id, "", 10);
assert!(results.is_ok());
}
#[test]
fn test_limit_is_respected() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_id = Uuid::new_v4();
for i in 0..5 {
let sym = make_symbol(
&format!("widget_{i}"),
&format!("crate::widget_{i}"),
SymbolKind::Function,
"src/widgets.rs",
None,
None,
);
index.index_symbol(repo_id, &sym).unwrap();
}
index.commit().unwrap();
let results = index.search(repo_id, "widget", 3).unwrap();
assert_eq!(results.len(), 3);
let results = index.search(repo_id, "widget", 10).unwrap();
assert_eq!(results.len(), 5);
}
#[test]
fn test_delete_by_repo() {
let tmp = TempDir::new().unwrap();
let mut index = SearchIndex::open(tmp.path()).unwrap();
let repo_a = Uuid::new_v4();
let repo_b = Uuid::new_v4();
let sym_a1 = make_symbol(
"repo_a_func",
"crate::repo_a_func",
SymbolKind::Function,
"src/lib.rs",
Some("fn repo_a_func()"),
None,
);
let sym_a2 = make_symbol(
"repo_a_struct",
"crate::repo_a_struct",
SymbolKind::Struct,
"src/types.rs",
None,
Some("A struct in repo A"),
);
let sym_b = make_symbol(
"repo_b_func",
"crate::repo_b_func",
SymbolKind::Function,
"src/lib.rs",
Some("fn repo_b_func()"),
None,
);
index.index_symbol(repo_a, &sym_a1).unwrap();
index.index_symbol(repo_a, &sym_a2).unwrap();
index.index_symbol(repo_b, &sym_b).unwrap();
index.commit().unwrap();
let results_a = index.search(repo_a, "repo_a", 10).unwrap();
assert_eq!(results_a.len(), 2);
let results_b = index.search(repo_b, "repo_b", 10).unwrap();
assert_eq!(results_b.len(), 1);
index.delete_by_repo(repo_a).unwrap();
index.commit().unwrap();
let results_a = index.search(repo_a, "repo_a", 10).unwrap();
assert!(results_a.is_empty());
let results_b = index.search(repo_b, "repo_b", 10).unwrap();
assert_eq!(results_b.len(), 1);
assert_eq!(results_b[0], sym_b.id);
}