use std::fs;
use std::process::Command;
use tempfile::TempDir;
#[test]
fn test_query_shows_all_symbols_in_file() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("magellan.db");
let file_path = root_path.join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
struct Point {
x: i32,
y: i32,
}
fn distance(p1: &Point, p2: &Point) -> i32 {
0
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--file")
.arg(&file_path)
.output()
.expect("Failed to execute magellan query");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stdout.contains(&file_path.to_string_lossy().to_string()),
"Output should contain file path, got: {}",
stdout
);
assert!(
stdout.contains("main"),
"Output should contain 'main', got: {}",
stdout
);
assert!(
stdout.contains("Point"),
"Output should contain 'Point', got: {}",
stdout
);
assert!(
stdout.contains("distance"),
"Output should contain 'distance', got: {}",
stdout
);
assert!(
stdout.contains("Function") || stdout.contains("function"),
"Output should contain 'Function', got: {}",
stdout
);
assert!(
stdout.contains("Class") || stdout.contains("class"),
"Output should contain 'Class', got: {}",
stdout
);
}
#[test]
fn test_query_filters_by_kind() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("magellan.db");
let file_path = root_path.join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {}
fn helper() {}
struct Point {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--file")
.arg(&file_path)
.arg("--kind")
.arg("Function")
.output()
.expect("Failed to execute magellan query");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.contains("main"),
"Output should contain 'main', got: {}",
stdout
);
assert!(
stdout.contains("helper"),
"Output should contain 'helper', got: {}",
stdout
);
assert!(
!stdout.contains("Point"),
"Output should NOT contain 'Point', got: {}",
stdout
);
}
#[test]
fn test_query_case_insensitive_kind() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("magellan.db");
let file_path = root_path.join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--file")
.arg(&file_path)
.arg("--kind")
.arg("function") .output()
.expect("Failed to execute magellan query");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.contains("main"),
"Output should contain 'main' with lowercase kind, got: {}",
stdout
);
}
#[test]
fn test_query_nonexistent_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--file")
.arg("/nonexistent/path.rs")
.output()
.expect("Failed to execute magellan query");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should succeed even for unindexed file"
);
assert!(
stdout.contains("no symbols found") || stdout.contains("(no symbols)"),
"Output should indicate no symbols found, got: {}",
stdout
);
}
#[test]
fn test_query_empty_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("empty.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
fs::write(&file_path, "").unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--file")
.arg(&file_path)
.output()
.expect("Failed to execute magellan query");
let _stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should succeed even with no symbols"
);
}
#[test]
fn test_query_output_format() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("format.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = "fn first() {}\nfn second() {}\n";
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--file")
.arg(&file_path)
.output()
.expect("Failed to execute magellan query");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.contains("1") || stdout.contains("Line"),
"Output should contain line numbers, got: {}",
stdout
);
assert!(
!stdout.contains("{") || stdout.contains("Line"),
"Output should be human-readable, not raw JSON"
);
}
#[test]
fn test_query_explain_flag() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--explain")
.output()
.expect("Failed to execute magellan query --explain");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Explain flag should succeed");
assert!(
stdout.contains("Selectors"),
"Explain output should mention selectors: {}",
stdout
);
assert!(
stdout.contains("glob"),
"Explain output should mention glob syntax: {}",
stdout
);
assert!(
stdout.contains("references"),
"Explain output should mention references selector: {}",
stdout
);
}
#[test]
fn test_query_symbol_extent_output() {
let temp_dir = TempDir::new().unwrap();
let root_path = temp_dir.path().to_path_buf();
let db_path = temp_dir.path().join("magellan.db");
let file_path = root_path.join("extent.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn target() {
let value = 1 + 2;
println!("{}", value);
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--file")
.arg(&file_path)
.arg("--symbol")
.arg("target")
.arg("--show-extent")
.output()
.expect("Failed to execute magellan query --show-extent");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Symbol extent flag should succeed: {}",
stdout
);
assert!(
stdout.contains("Byte Range"),
"Output should include byte range: {}",
stdout
);
assert!(
stdout.contains("Line"),
"Output should include line span: {}",
stdout
);
assert!(
stdout.contains("target"),
"Output should mention symbol: {}",
stdout
);
}
#[test]
fn test_find_symbol_by_name() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
struct Point {
x: i32,
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("main")
.output()
.expect("Failed to execute magellan find");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}",
stdout
);
assert!(
stdout.contains("main"),
"Output should contain 'main', got: {}",
stdout
);
assert!(
stdout.contains("Function") || stdout.contains("function"),
"Output should contain kind, got: {}",
stdout
);
assert!(
stdout.contains(&file_path.to_string_lossy().to_string()),
"Output should contain file path, got: {}",
stdout
);
}
#[test]
fn test_find_in_specific_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn helper() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("helper")
.arg("--path")
.arg(&file_path)
.output()
.expect("Failed to execute magellan find");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.contains("helper"),
"Output should contain 'helper', got: {}",
stdout
);
}
#[test]
fn test_find_glob_lists_symbols() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("glob.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn test_alpha() {}
fn test_beta() {}
fn helper() {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--list-glob")
.arg("test_*")
.output()
.expect("Failed to execute magellan find --glob");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Glob lookup should succeed: {}",
stdout
);
assert!(
stdout.contains("test_alpha"),
"Output should include test_alpha: {}",
stdout
);
assert!(
stdout.contains("test_beta"),
"Output should include test_beta: {}",
stdout
);
assert!(
!stdout.contains("helper"),
"Glob output should not include helper: {}",
stdout
);
assert!(
stdout.contains("Node"),
"Output should show node IDs for determinism: {}",
stdout
);
}
#[test]
fn test_find_all_files() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file1 = temp_dir.path().join("file1.rs");
let file2 = temp_dir.path().join("file2.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
fs::write(&file1, "fn config() {}").unwrap();
fs::write(&file2, "struct Config {}").unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let path_str1 = file1.to_string_lossy().to_string();
let path_str2 = file2.to_string_lossy().to_string();
graph
.index_file(&path_str1, fs::read(&file1).unwrap().as_slice())
.unwrap();
graph
.index_file(&path_str2, fs::read(&file2).unwrap().as_slice())
.unwrap();
}
let output = Command::new(&bin_path)
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("config")
.output()
.expect("Failed to execute magellan find");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.to_lowercase().contains("config"),
"Output should contain 'config', got: {}",
stdout
);
}
#[test]
fn test_find_symbol_not_found() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let file_path = temp_dir.path().join("test.rs");
fs::write(&file_path, "fn existing() {}").unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("nonexistent")
.output()
.expect("Failed to execute magellan find");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should succeed even when symbol not found"
);
assert!(
stdout.contains("not found")
|| stdout.contains("No results")
|| stdout.contains("not found"),
"Output should indicate symbol not found, got: {}",
stdout
);
}
#[test]
fn test_find_includes_symbol_id_in_json() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
struct Point {
x: i32,
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("find")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("main")
.arg("--output")
.arg("json")
.output()
.expect("Failed to execute magellan find --output json");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert_eq!(
json["schema_version"], "1.0.0",
"Schema version should be 1.0.0"
);
assert!(json["execution_id"].is_string(), "Should have execution_id");
let data = &json["data"];
assert!(data.is_object(), "Should have data object");
let matches = &data["matches"];
assert!(matches.is_array(), "Should have matches array");
let matches_array = matches.as_array().unwrap();
assert!(
!matches_array.is_empty(),
"Should find at least one match for 'main'"
);
let first_match = &matches_array[0];
assert!(
first_match["symbol_id"].is_string(),
"symbol_id should be a string in JSON output, got: {}",
first_match
);
let symbol_id = first_match["symbol_id"].as_str().unwrap();
assert!(!symbol_id.is_empty(), "symbol_id should be non-empty");
assert_eq!(first_match["name"], "main");
assert!(first_match["span"].is_object());
}
#[test]
fn test_refs_incoming_calls() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn callee() {}
fn caller1() {
callee();
}
fn caller2() {
callee();
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
graph.index_calls(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("refs")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("callee")
.arg("--path")
.arg(&file_path)
.arg("--direction")
.arg("in")
.output()
.expect("Failed to execute magellan refs");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}",
stdout
);
assert!(
stdout.contains("caller1") || stdout.contains("caller2"),
"Output should contain callers, got: {}",
stdout
);
}
#[test]
fn test_refs_outgoing_calls() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn helper() {}
fn other() {}
fn main() {
helper();
other();
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
graph.index_calls(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("refs")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("main")
.arg("--path")
.arg(&file_path)
.arg("--direction")
.arg("out")
.output()
.expect("Failed to execute magellan refs");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.contains("helper") || stdout.contains("other"),
"Output should contain callees, got: {}",
stdout
);
}
#[test]
fn test_refs_symbol_not_found() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn existing() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("refs")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("nonexistent")
.arg("--path")
.arg(&file_path)
.output()
.expect("Failed to execute magellan refs");
let _stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should succeed even when symbol not found"
);
}
#[test]
fn test_refs_includes_target_symbol_id_in_json() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn callee() {}
fn caller1() {
callee();
}
fn caller2() {
callee();
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
graph.index_calls(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("refs")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("callee")
.arg("--path")
.arg(&file_path)
.arg("--direction")
.arg("in")
.arg("--output")
.arg("json")
.output()
.expect("Failed to execute magellan refs");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert_eq!(
json["schema_version"], "1.0.0",
"Schema version should be 1.0.0"
);
assert!(json["execution_id"].is_string(), "Should have execution_id");
let data = &json["data"];
assert!(data.is_object(), "Should have data object");
let references = &data["references"];
assert!(references.is_array(), "Should have references array");
let references_array = references.as_array().unwrap();
assert!(
!references_array.is_empty(),
"Should find at least one reference to 'callee'"
);
let first_ref = &references_array[0];
assert!(
first_ref["target_symbol_id"].is_string(),
"target_symbol_id should be a string in JSON output, got: {}",
first_ref
);
let target_symbol_id = first_ref["target_symbol_id"].as_str().unwrap();
assert!(
!target_symbol_id.is_empty(),
"target_symbol_id should be non-empty"
);
assert_eq!(first_ref["reference_kind"], "call");
assert!(first_ref["span"].is_object());
assert_eq!(data["symbol_name"], "callee");
assert_eq!(data["direction"], "in");
}
#[test]
fn test_refs_callees_includes_symbol_id() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn helper() {}
fn other() {}
fn main() {
helper();
other();
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
graph.index_calls(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("refs")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("main")
.arg("--path")
.arg(&file_path)
.arg("--direction")
.arg("out")
.arg("--output")
.arg("json")
.output()
.expect("Failed to execute magellan refs");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert_eq!(
json["schema_version"], "1.0.0",
"Schema version should be 1.0.0"
);
assert!(json["execution_id"].is_string(), "Should have execution_id");
let data = &json["data"];
assert!(data.is_object(), "Should have data object");
let references = &data["references"];
assert!(references.is_array(), "Should have references array");
let references_array = references.as_array().unwrap();
assert!(
!references_array.is_empty(),
"Should find at least one call from 'main'"
);
let first_ref = &references_array[0];
assert!(
first_ref["target_symbol_id"].is_string(),
"target_symbol_id should be a string in JSON output for out direction, got: {}",
first_ref
);
let target_symbol_id = first_ref["target_symbol_id"].as_str().unwrap();
assert!(
!target_symbol_id.is_empty(),
"target_symbol_id should be non-empty for callees"
);
assert_eq!(first_ref["reference_kind"], "call");
assert!(first_ref["span"].is_object());
assert_eq!(data["symbol_name"], "main");
assert_eq!(data["direction"], "out");
}
#[test]
fn test_refs_command_cross_file_direction_flags() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let caller_path = temp_dir.path().join("caller.rs");
let callee_path = temp_dir.path().join("callee.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let caller_source = r#"
pub fn caller() {
callee();
}
pub fn another_caller() {
callee();
}
"#;
fs::write(&caller_path, caller_source).unwrap();
let callee_source = r#"
pub fn callee() {
println!("Called from another file");
}
"#;
fs::write(&callee_path, callee_source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let caller_bytes = fs::read(&caller_path).unwrap();
let caller_str = caller_path.to_string_lossy().to_string();
graph.index_file(&caller_str, &caller_bytes).unwrap();
let callee_bytes = fs::read(&callee_path).unwrap();
let callee_str = callee_path.to_string_lossy().to_string();
graph.index_file(&callee_str, &callee_bytes).unwrap();
graph.index_calls(&caller_str, &caller_bytes).unwrap();
graph.index_calls(&callee_str, &callee_bytes).unwrap();
}
let output_in = Command::new(&bin_path)
.arg("refs")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("callee")
.arg("--path")
.arg(&callee_path)
.arg("--direction")
.arg("in")
.output()
.expect("Failed to execute magellan refs");
let stdout_in = String::from_utf8_lossy(&output_in.stdout);
assert!(
output_in.status.success(),
"Process should exit successfully\nstdout: {}",
stdout_in
);
assert!(
stdout_in.contains("Calls TO \"callee\""),
"Output should show 'Calls TO \"callee\"'"
);
assert!(
stdout_in.contains("caller"),
"Output should contain 'caller' function"
);
assert!(
stdout_in.contains("another_caller"),
"Output should contain 'another_caller' function"
);
assert!(
stdout_in.contains("caller.rs"),
"Output should show file path 'caller.rs' for cross-file calls"
);
let output_out = Command::new(&bin_path)
.arg("refs")
.arg("--db")
.arg(&db_path)
.arg("--name")
.arg("caller")
.arg("--path")
.arg(&caller_path)
.arg("--direction")
.arg("out")
.output()
.expect("Failed to execute magellan refs");
let stdout_out = String::from_utf8_lossy(&output_out.stdout);
assert!(
output_out.status.success(),
"Process should exit successfully\nstdout: {}",
stdout_out
);
assert!(
stdout_out.contains("Calls FROM \"caller\""),
"Output should show 'Calls FROM \"caller\"'"
);
assert!(
stdout_out.contains("callee"),
"Output should contain 'callee' function"
);
assert!(
stdout_out.contains("caller.rs"),
"Output should show file path where call is made (caller.rs)"
);
}
#[test]
fn test_query_with_relative_path_explicit_root() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let src_dir = temp_dir.path().join("src");
fs::create_dir(&src_dir).unwrap();
let file_path = src_dir.join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
struct Point {
x: i32,
y: i32,
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("query")
.arg("--db")
.arg(&db_path)
.arg("--root")
.arg(temp_dir.path()) .arg("--file")
.arg("src/test.rs") .output()
.expect("Failed to execute magellan query");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stdout.contains("main"),
"Output should contain 'main', got: {}",
stdout
);
assert!(
stdout.contains("Point"),
"Output should contain 'Point', got: {}",
stdout
);
}
#[test]
fn test_files_lists_all() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file1 = temp_dir.path().join("file1.rs");
let file2 = temp_dir.path().join("file2.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
fs::write(&file1, "fn func1() {}").unwrap();
fs::write(&file2, "fn func2() {}").unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let path_str1 = file1.to_string_lossy().to_string();
let path_str2 = file2.to_string_lossy().to_string();
graph
.index_file(&path_str1, fs::read(&file1).unwrap().as_slice())
.unwrap();
graph
.index_file(&path_str2, fs::read(&file2).unwrap().as_slice())
.unwrap();
}
let output = Command::new(&bin_path)
.arg("files")
.arg("--db")
.arg(&db_path)
.output()
.expect("Failed to execute magellan files");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.contains("2") || stdout.contains("file1.rs") || stdout.contains("file2.rs"),
"Output should contain files, got: {}",
stdout
);
}
#[test]
fn test_files_empty_database() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
{
let _graph = magellan::CodeGraph::open(&db_path).unwrap();
}
let output = Command::new(&bin_path)
.arg("files")
.arg("--db")
.arg(&db_path)
.output()
.expect("Failed to execute magellan files");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should succeed");
assert!(
stdout.contains("0") || stdout.contains("no files") || stdout.contains("empty"),
"Output should indicate no files, got: {}",
stdout
);
}
#[test]
fn test_files_with_symbol_counts() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file1 = temp_dir.path().join("file1.rs");
let file2 = temp_dir.path().join("file2.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
fs::write(&file1, "fn func1() {}\nfn func2() {}").unwrap();
fs::write(&file2, "fn func3() {}").unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let path_str1 = file1.to_string_lossy().to_string();
let path_str2 = file2.to_string_lossy().to_string();
graph
.index_file(&path_str1, fs::read(&file1).unwrap().as_slice())
.unwrap();
graph
.index_file(&path_str2, fs::read(&file2).unwrap().as_slice())
.unwrap();
}
let output = Command::new(&bin_path)
.arg("files")
.arg("--db")
.arg(&db_path)
.arg("--symbols")
.arg("--output")
.arg("json")
.output()
.expect("Failed to execute magellan files");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert_eq!(json["schema_version"], "1.0.0");
assert!(json["execution_id"].is_string());
let data = &json["data"];
assert!(data.is_object());
let files = &data["files"];
assert!(files.is_array());
let files_array = files.as_array().unwrap();
assert_eq!(files_array.len(), 2, "Should have 2 files");
let symbol_counts = &data["symbol_counts"];
assert!(
symbol_counts.is_object(),
"symbol_counts should be an object"
);
let counts_obj = symbol_counts.as_object().unwrap();
assert_eq!(counts_obj.len(), 2, "Should have counts for 2 files");
for (_file_path, count) in counts_obj.iter() {
let count_val = count.as_u64().expect("Count should be a number");
assert!(count_val > 0, "Each file should have at least 1 symbol");
}
}