use magellan::graph::ambiguity::AmbiguityOps;
use magellan::graph::query;
use magellan::CodeGraph;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_create_ambiguous_group_single_symbol() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let test_file = temp_dir.path().join("handler.rs");
fs::write(&test_file, "fn Handler() {}").unwrap();
let path_str = test_file.to_string_lossy().to_string();
let source = fs::read(&test_file).unwrap();
graph.index_file(&path_str, &source).unwrap();
let (_node_id, _fact, _symbol_id_option) =
query::symbol_nodes_in_file_with_ids(&mut graph, &path_str)
.unwrap()
.into_iter()
.next()
.expect("Should have one symbol");
let entity_id = query::symbol_id_by_name(&mut graph, &path_str, "Handler")
.unwrap()
.expect("Handler should exist");
let result = graph.create_ambiguous_group("Handler", &[entity_id]);
assert!(
result.is_ok(),
"Should successfully create ambiguity group with single symbol"
);
}
#[test]
fn test_create_ambiguous_group_multiple_symbols() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let file1 = temp_dir.path().join("handler.rs");
fs::write(&file1, "fn Handler() {}").unwrap();
let file2 = temp_dir.path().join("parser.rs");
fs::write(&file2, "fn Handler() {}").unwrap();
let path1 = file1.to_string_lossy().to_string();
let path2 = file2.to_string_lossy().to_string();
let source1 = fs::read(&file1).unwrap();
let source2 = fs::read(&file2).unwrap();
graph.index_file(&path1, &source1).unwrap();
graph.index_file(&path2, &source2).unwrap();
let entity_id1 = query::symbol_id_by_name(&mut graph, &path1, "Handler")
.unwrap()
.expect("Handler in handler.rs should exist");
let entity_id2 = query::symbol_id_by_name(&mut graph, &path2, "Handler")
.unwrap()
.expect("Handler in parser.rs should exist");
let (_node_id1, fact1, _) = query::symbol_nodes_in_file_with_ids(&mut graph, &path1)
.unwrap()
.into_iter()
.find(|(_, fact, _)| fact.name.as_deref() == Some("Handler"))
.expect("Handler should exist");
let display_fqn = fact1.display_fqn.as_deref().unwrap_or("Handler");
let result = graph.create_ambiguous_group(display_fqn, &[entity_id1, entity_id2]);
assert!(
result.is_ok(),
"Should successfully create ambiguity group with multiple symbols"
);
let candidates = graph.get_candidates(display_fqn).unwrap();
assert!(
candidates.len() >= 2,
"Should have at least 2 candidates for ambiguous display FQN (may be more due to index_references)"
);
}
#[test]
fn test_resolve_by_symbol_id_found() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let test_file = temp_dir.path().join("handler.rs");
fs::write(&test_file, "fn Handler() {}").unwrap();
let path_str = test_file.to_string_lossy().to_string();
let source = fs::read(&test_file).unwrap();
graph.index_file(&path_str, &source).unwrap();
let (_node_id, fact, symbol_id_option) =
query::symbol_nodes_in_file_with_ids(&mut graph, &path_str)
.unwrap()
.into_iter()
.find(|(_, fact, _)| fact.name.as_deref() == Some("Handler"))
.expect("Handler should exist");
if let Some(symbol_id) = symbol_id_option {
let display_fqn = fact.display_fqn.as_deref().unwrap_or("Handler");
let result = graph.resolve_by_symbol_id(display_fqn, &symbol_id).unwrap();
assert!(
result.is_some(),
"Should find symbol when SymbolId exists and matches display FQN"
);
let found = result.unwrap();
assert_eq!(
found.symbol_id.unwrap(),
symbol_id,
"Returned symbol should have matching SymbolId"
);
}
}
#[test]
fn test_resolve_by_symbol_id_not_found() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let test_file = temp_dir.path().join("dummy.rs");
fs::write(&test_file, "fn dummy() {}").unwrap();
let path_str = test_file.to_string_lossy().to_string();
let source = fs::read(&test_file).unwrap();
graph.index_file(&path_str, &source).unwrap();
let result = graph
.resolve_by_symbol_id("Handler", "nonexistent_id_123456789012")
.unwrap();
assert!(
result.is_none(),
"Should return None when SymbolId doesn't exist"
);
}
#[test]
fn test_resolve_by_symbol_id_display_fqn_mismatch() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let test_file = temp_dir.path().join("handler.rs");
fs::write(&test_file, "fn Handler() {}").unwrap();
let path_str = test_file.to_string_lossy().to_string();
let source = fs::read(&test_file).unwrap();
graph.index_file(&path_str, &source).unwrap();
let (_node_id, _fact, symbol_id_option) =
query::symbol_nodes_in_file_with_ids(&mut graph, &path_str)
.unwrap()
.into_iter()
.find(|(_, fact, _)| fact.name.as_deref() == Some("Handler"))
.expect("Handler should exist");
if let Some(symbol_id) = symbol_id_option {
let result = graph
.resolve_by_symbol_id("DifferentHandler", &symbol_id)
.unwrap();
assert!(
result.is_none(),
"Should return None when SymbolId exists but display_fqn doesn't match"
);
}
}
#[test]
fn test_get_candidates_empty() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let test_file = temp_dir.path().join("dummy.rs");
fs::write(&test_file, "fn dummy() {}").unwrap();
let path_str = test_file.to_string_lossy().to_string();
let source = fs::read(&test_file).unwrap();
graph.index_file(&path_str, &source).unwrap();
let candidates = graph.get_candidates("nonexistent_fqn").unwrap();
assert_eq!(
candidates.len(),
0,
"Should return empty Vec for non-existent display FQN"
);
}
#[test]
fn test_get_candidates_multiple() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
for name in &["handler.rs", "parser.rs", "auth.rs"] {
let file = temp_dir.path().join(name);
fs::write(&file, "fn Handler() {}").unwrap();
let path_str = file.to_string_lossy().to_string();
let source = fs::read(&file).unwrap();
graph.index_file(&path_str, &source).unwrap();
}
let _candidates = graph.get_candidates("Handler").unwrap();
assert!(true, "Query should succeed without error");
}
#[test]
fn test_ambiguous_group_idempotent() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let file1 = temp_dir.path().join("handler.rs");
fs::write(&file1, "fn Handler() {}").unwrap();
let file2 = temp_dir.path().join("parser.rs");
fs::write(&file2, "fn Handler() {}").unwrap();
let path1 = file1.to_string_lossy().to_string();
let path2 = file2.to_string_lossy().to_string();
let source1 = fs::read(&file1).unwrap();
let source2 = fs::read(&file2).unwrap();
graph.index_file(&path1, &source1).unwrap();
graph.index_file(&path2, &source2).unwrap();
let entity_id1 = query::symbol_id_by_name(&mut graph, &path1, "Handler")
.unwrap()
.expect("Handler in handler.rs should exist");
let entity_id2 = query::symbol_id_by_name(&mut graph, &path2, "Handler")
.unwrap()
.expect("Handler in parser.rs should exist");
let (_node_id1, fact1, _) = query::symbol_nodes_in_file_with_ids(&mut graph, &path1)
.unwrap()
.into_iter()
.find(|(_, fact, _)| fact.name.as_deref() == Some("Handler"))
.expect("Handler should exist");
let display_fqn = fact1.display_fqn.as_deref().unwrap_or("Handler");
graph
.create_ambiguous_group(display_fqn, &[entity_id1, entity_id2])
.unwrap();
graph
.create_ambiguous_group(display_fqn, &[entity_id1, entity_id2])
.unwrap();
let candidates = graph.get_candidates(display_fqn).unwrap();
assert!(
candidates.len() >= 2,
"Should have at least 2 candidates (idempotent call doesn't create duplicates)"
);
}
#[test]
fn test_cli_find_by_symbol_id() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let test_file = temp_dir.path().join("lib.rs");
fs::write(&test_file, "fn function_name() {}").unwrap();
let path_str = test_file.to_string_lossy().to_string();
let source = fs::read(&test_file).unwrap();
graph.index_file(&path_str, &source).unwrap();
let (_node_id, _fact, symbol_id_option) =
query::symbol_nodes_in_file_with_ids(&mut graph, &path_str)
.unwrap()
.into_iter()
.find(|(_, fact, _)| fact.name.as_deref() == Some("function_name"))
.expect("function_name should exist");
if let Some(symbol_id) = symbol_id_option {
let output = std::process::Command::new(env!("CARGO_BIN_EXE_magellan"))
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--symbol-id")
.arg(&symbol_id)
.output()
.unwrap();
assert!(output.status.success(), "CLI should succeed");
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains(&symbol_id) || stdout.contains("function_name"),
"Output should contain symbol_id or function_name"
);
}
}
#[test]
fn test_cli_find_ambiguous() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let file1 = temp_dir.path().join("handler.rs");
fs::write(&file1, "fn Handler() {}").unwrap();
let file2 = temp_dir.path().join("parser.rs");
fs::write(&file2, "fn Handler() {}").unwrap();
let path1 = file1.to_string_lossy().to_string();
let path2 = file2.to_string_lossy().to_string();
let source1 = fs::read(&file1).unwrap();
let source2 = fs::read(&file2).unwrap();
graph.index_file(&path1, &source1).unwrap();
graph.index_file(&path2, &source2).unwrap();
let (_node_id, fact, _) = query::symbol_nodes_in_file_with_ids(&mut graph, &path1)
.unwrap()
.into_iter()
.find(|(_, fact, _)| fact.name.as_deref() == Some("Handler"))
.expect("Handler should exist");
let display_fqn = fact.display_fqn.as_deref().unwrap_or("Handler");
let output = std::process::Command::new(env!("CARGO_BIN_EXE_magellan"))
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--ambiguous")
.arg(display_fqn)
.output()
.unwrap();
assert!(output.status.success(), "CLI should succeed");
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.contains("candidate") || stderr.contains("Symbol") || stderr.contains("Canonical"),
"stderr should show candidates information"
);
}
#[test]
fn test_cli_find_first_deprecation_warning() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let file1 = temp_dir.path().join("handler.rs");
fs::write(&file1, "fn Handler() {}").unwrap();
let file2 = temp_dir.path().join("parser.rs");
fs::write(&file2, "fn Handler() {}").unwrap();
let path1 = file1.to_string_lossy().to_string();
let path2 = file2.to_string_lossy().to_string();
let source1 = fs::read(&file1).unwrap();
let source2 = fs::read(&file2).unwrap();
graph.index_file(&path1, &source1).unwrap();
graph.index_file(&path2, &source2).unwrap();
let output = std::process::Command::new(env!("CARGO_BIN_EXE_magellan"))
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("Handler")
.arg("--first")
.output()
.unwrap();
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.contains("deprecated"),
"stderr should contain deprecation warning for --first flag"
);
}
#[test]
fn test_cli_find_ambiguous_with_display_fqn() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let file1 = temp_dir.path().join("handler.rs");
fs::write(&file1, "fn Handler() {}").unwrap();
let file2 = temp_dir.path().join("parser.rs");
fs::write(&file2, "fn Handler() {}").unwrap();
let path1 = file1.to_string_lossy().to_string();
let path2 = file2.to_string_lossy().to_string();
let source1 = fs::read(&file1).unwrap();
let source2 = fs::read(&file2).unwrap();
graph.index_file(&path1, &source1).unwrap();
graph.index_file(&path2, &source2).unwrap();
let (_node_id, fact, _) = query::symbol_nodes_in_file_with_ids(&mut graph, &path1)
.unwrap()
.into_iter()
.find(|(_, fact, _)| fact.name.as_deref() == Some("Handler"))
.expect("Handler should exist");
let display_fqn = fact.display_fqn.as_deref().unwrap_or("Handler");
let output = std::process::Command::new(env!("CARGO_BIN_EXE_magellan"))
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--ambiguous")
.arg(display_fqn) .output()
.unwrap();
assert!(
output.status.success(),
"CLI should succeed with --ambiguous flag"
);
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.contains("Symbol ID") || stderr.contains("Canonical") || stderr.contains("Name"),
"stderr should show candidate details (Symbol ID, Canonical, or Name). Got: {}",
stderr
);
assert!(
stderr.contains("Symbol ID"),
"stderr should list Symbol IDs for all candidates"
);
}