#![cfg(feature = "sqlite")]
use splice::graph::magellan_integration::MagellanIntegration;
use splice::output::{JsonResponse, LabelQueryResponse, Span, SymbolMatch};
use sqlitegraph::SnapshotId;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn create_test_magellan_integration() -> (MagellanIntegration, TempDir) {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let db_path = temp_dir.path().join("test_magellan.db");
let integration =
MagellanIntegration::open(&db_path).expect("Failed to create MagellanIntegration");
(integration, temp_dir)
}
fn create_test_file(dir: &Path, name: &str, content: &str) -> std::path::PathBuf {
let file_path = dir.join(name);
fs::write(&file_path, content).expect("Failed to write test file");
file_path
}
#[test]
fn test_magellan_integration_open() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let db_path = temp_dir.path().join("test_magellan.db");
let integration = MagellanIntegration::open(&db_path);
assert!(
integration.is_ok(),
"MagellanIntegration::open should succeed"
);
let integration = integration.unwrap();
assert!(db_path.exists(), "Database file should be created");
let results = integration.query_by_labels(&["rust"]);
assert!(results.is_ok(), "Query on empty database should succeed");
assert!(
results.unwrap().is_empty(),
"Empty database should have no results"
);
println!(
"MagellanIntegration opened successfully at: {}",
db_path.display()
);
}
#[cfg(feature = "sqlite")]
#[test]
fn test_magellan_index_file() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
/// Adds two numbers together.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Multiplies two numbers.
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
/// A simple struct for testing.
pub struct Calculator {
value: i32,
}
impl Calculator {
pub fn new(value: i32) -> Self {
Self { value }
}
}
"#;
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
let result = integration.index_file(&file_path);
assert!(
result.is_ok(),
"index_file should succeed: {:?}",
result.err()
);
let indexed_count = result.unwrap();
assert!(indexed_count > 0, "Should index at least one symbol");
println!("Indexed {} symbols from test.rs", indexed_count);
let rust_symbols = integration.query_by_labels(&["rust"]);
assert!(rust_symbols.is_ok());
assert!(
!rust_symbols.unwrap().is_empty(),
"Should find indexed Rust symbols"
);
}
#[test]
fn test_magellan_query_by_labels() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
pub fn function_one() -> i32 {
42
}
pub fn function_two() -> String {
"hello".to_string()
}
pub struct TestStruct {
field: i32,
}
"#;
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let result = integration.query_by_labels(&["rust"]);
assert!(
result.is_ok(),
"query_by_labels should succeed: {:?}",
result.err()
);
let symbols = result.unwrap();
assert!(symbols.len() > 0, "Should retrieve at least one symbol");
println!("Retrieved {} symbols by labels", symbols.len());
for symbol in &symbols {
assert!(!symbol.name.is_empty(), "Symbol name should not be empty");
assert!(
!symbol.file_path.is_empty(),
"Symbol file_path should not be empty"
);
assert!(!symbol.kind.is_empty(), "Symbol kind should not be empty");
assert!(
symbol.byte_start < symbol.byte_end,
"Byte range should be valid: {} < {}",
symbol.byte_start,
symbol.byte_end
);
assert!(symbol.entity_id > 0, "Entity ID should be positive");
}
}
#[test]
fn test_magellan_db_read_compatibility() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
/// A demo function to test compatibility
pub fn demo_function(x: i32) -> i32 {
x + 1
}
/// A demo struct
pub struct DemoStruct {
value: i32,
}
impl DemoStruct {
pub fn new(value: i32) -> Self {
Self { value }
}
}
"#;
let file_path = create_test_file(temp_dir.path(), "compat_test.rs", content);
integration.index_file(&file_path).unwrap();
let rust_symbols = integration.query_by_labels(&["rust"]).unwrap();
assert!(
!rust_symbols.is_empty(),
"Should find symbols with 'rust' label"
);
let fn_symbols = integration.query_by_labels(&["rust", "fn"]).unwrap();
assert!(
!fn_symbols.is_empty(),
"Should find functions with 'rust' AND 'fn' labels"
);
let demo_fn = fn_symbols.iter().find(|s| s.name == "demo_function");
assert!(demo_fn.is_some(), "Should find demo_function");
let demo_struct = fn_symbols.iter().find(|s| s.name == "new");
assert!(demo_struct.is_some(), "Should find new method");
let struct_symbols = integration.query_by_labels(&["struct"]).unwrap();
assert!(!struct_symbols.is_empty(), "Should find structs");
let demo_struct = struct_symbols.iter().find(|s| s.name == "DemoStruct");
assert!(demo_struct.is_some(), "Should find DemoStruct");
let db_path = temp_dir.path().join("test_magellan.db");
let graph = splice::graph::CodeGraph::open(db_path.as_path())
.expect("CodeGraph should open Magellan-created DB");
if let Some(symbol) = fn_symbols.first() {
let snapshot_id = SnapshotId::current();
let backend = graph.inner().expect("Should get backend");
let node = backend
.get_node(snapshot_id, symbol.entity_id)
.expect("Should retrieve node by entity_id");
assert_eq!(node.name, "demo_function");
}
}
#[test]
fn test_magellan_call_relationships() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
pub fn callee() {}
pub fn caller() {
callee();
}
"#;
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let symbols = integration.query_by_labels(&["rust", "fn"]).unwrap();
let caller = symbols
.iter()
.find(|symbol| symbol.name == "caller")
.expect("caller symbol should be indexed");
let callee = symbols
.iter()
.find(|symbol| symbol.name == "callee")
.expect("callee symbol should be indexed");
let graph = splice::graph::CodeGraph::open(temp_dir.path().join("test_magellan.db").as_path())
.expect("Failed to open graph for relationships");
let mut cache = splice::relationships::RelationshipCache::new();
let callees = splice::relationships::get_callees(
&graph,
sqlitegraph::NodeId::from(caller.entity_id),
&mut cache,
)
.unwrap();
assert!(
callees.iter().any(|rel| rel.name == "callee"),
"caller should list callee"
);
let callers = splice::relationships::get_callers(
&graph,
sqlitegraph::NodeId::from(callee.entity_id),
&mut cache,
)
.unwrap();
assert!(
callers.iter().any(|rel| rel.name == "caller"),
"callee should list caller"
);
}
#[test]
fn test_magellan_get_code_chunk() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
pub fn example_function() -> i32 {
100
}
"#;
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let symbols = integration.query_by_labels(&["rust"]).unwrap();
assert!(!symbols.is_empty(), "Should have indexed symbols");
let symbol = &symbols[0];
let result = integration.get_code_chunk(&file_path, symbol.byte_start, symbol.byte_end);
assert!(
result.is_ok(),
"get_code_chunk should succeed: {:?}",
result.err()
);
let chunk_content = result.unwrap();
assert!(
chunk_content.is_some(),
"Should have code chunk at the symbol's span"
);
let chunk = chunk_content.unwrap();
assert!(!chunk.is_empty(), "Chunk content should not be empty");
println!(
"Retrieved code chunk: {} bytes for symbol '{}' at {}..{}",
chunk.len(),
symbol.name,
symbol.byte_start,
symbol.byte_end
);
}
#[test]
fn test_unified_json_schema_fields() {
let span = Span::new("src/lib.rs".to_string(), 10, 20, 1, 0, 1, 10);
let symbol = SymbolMatch::new("demo".to_string(), "fn".to_string(), span, None, None);
let query = LabelQueryResponse {
labels: vec!["rust".to_string()],
symbols: vec![symbol],
count: 1,
total_count: None,
offset: None,
limit: None,
max_symbols: None,
max_bytes: None,
next_offset: None,
truncation_reasons: None,
};
let result = JsonResponse::new(query, "test-exec");
let value = serde_json::to_value(&result).unwrap();
assert!(
value.get("schema_version").is_some(),
"missing schema_version"
);
assert!(value.get("execution_id").is_some(), "missing execution_id");
assert!(
value.get("version").is_none(),
"legacy version should be removed"
);
assert!(
value.get("operation_id").is_none(),
"legacy operation_id should be removed"
);
let symbols = &value["data"]["symbols"];
assert!(symbols.is_array(), "expected symbols array");
let first = symbols.as_array().unwrap()[0].clone();
let span = first.get("span").expect("span should be present");
assert!(span.get("start_line").is_some(), "missing start_line");
assert!(span.get("start_col").is_some(), "missing start_col");
assert!(
span.get("line_start").is_none(),
"legacy line_start should be removed"
);
assert!(
span.get("col_start").is_none(),
"legacy col_start should be removed"
);
}
#[test]
fn test_magellan_get_code_chunks_for_symbol() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
pub struct MyStruct {
value: i32,
}
impl MyStruct {
pub fn new(value: i32) -> Self {
Self { value }
}
}
"#;
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let result = integration.get_code_chunks_for_symbol(&file_path, "MyStruct");
assert!(
result.is_ok(),
"get_code_chunks_for_symbol should succeed: {:?}",
result.err()
);
let chunks = result.unwrap();
println!(
"Retrieved {} code chunks for symbol 'MyStruct'",
chunks.len()
);
for chunk in &chunks {
assert!(
!chunk.content.is_empty(),
"Chunk content should not be empty"
);
assert!(
chunk.byte_start < chunk.byte_end,
"Chunk byte range should be valid"
);
let _ = chunk.symbol_name;
}
}
#[test]
fn test_magellan_symbol_info_format_compatibility() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn compatibility_test() -> i32 { 123 }";
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let symbols = integration.query_by_labels(&["rust"]).unwrap();
assert!(!symbols.is_empty(), "Should have indexed symbols");
let symbol = &symbols[0];
assert!(!symbol.name.is_empty(), "Symbol must have 'name' field");
assert!(
!symbol.file_path.is_empty(),
"Symbol must have 'file_path' field"
);
assert!(!symbol.kind.is_empty(), "Symbol must have 'kind' field");
assert!(
symbol.byte_start < symbol.byte_end,
"Symbol must have valid 'byte_start' < 'byte_end'"
);
assert!(symbol.entity_id != 0, "Symbol must have 'entity_id' field");
println!(
"SymbolInfo format compatible: name={}, kind={}, file={}, bytes={}..{}, id={}",
symbol.name,
symbol.kind,
symbol.file_path,
symbol.byte_start,
symbol.byte_end,
symbol.entity_id
);
}
#[test]
fn test_magellan_code_chunk_format_compatibility() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn chunk_test() -> i32 { 42 }";
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let symbols = integration.query_by_labels(&["rust"]).unwrap();
let symbol = &symbols[0];
let chunk_opt = integration
.get_code_chunk(&file_path, symbol.byte_start, symbol.byte_end)
.unwrap();
assert!(chunk_opt.is_some(), "Should retrieve code chunk");
let chunk_content = chunk_opt.unwrap();
assert!(!chunk_content.is_empty(), "Code chunk must have content");
let chunks = integration
.get_code_chunks_for_symbol(&file_path, &symbol.name)
.unwrap();
if !chunks.is_empty() {
let chunk = &chunks[0];
assert!(
!chunk.content.is_empty(),
"CodeChunk must have 'content' field"
);
assert!(
chunk.byte_start < chunk.byte_end,
"CodeChunk must have valid byte range"
);
let _ = chunk.symbol_name;
println!(
"CodeChunk format compatible: bytes={}..{}, symbol_name={:?}",
chunk.byte_start, chunk.byte_end, chunk.symbol_name
);
}
}
#[test]
fn test_magellan_absolute_path_compatibility() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn path_test() {}";
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
assert!(
file_path.is_absolute(),
"Test fixture should create absolute paths"
);
integration.index_file(&file_path).unwrap();
let symbols = integration.query_by_labels(&["rust"]).unwrap();
assert!(!symbols.is_empty());
let symbol = &symbols[0];
let stored_path = Path::new(&symbol.file_path);
assert!(
stored_path.is_absolute(),
"Stored file_path should be absolute for cross-tool compatibility, got: {}",
symbol.file_path
);
println!("Absolute path compatible: {}", symbol.file_path);
}
#[test]
fn test_magellan_query_nonexistent_labels() {
let (integration, _temp_dir) = create_test_magellan_integration();
let result = integration.query_by_labels(&["nonexistent_function_xyz"]);
assert!(
result.is_ok(),
"query_by_labels should succeed even with no matches"
);
let symbols = result.unwrap();
assert_eq!(
symbols.len(),
0,
"Should return empty array for non-existent labels"
);
println!("Correctly returned empty result for non-existent labels");
}
#[test]
fn test_magellan_query_multiple_labels_and() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
pub fn test_fn() {}
pub struct TestStruct {}
"#;
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let result = integration.query_by_labels(&["rust", "fn"]);
assert!(result.is_ok(), "Multi-label query should succeed");
let symbols = result.unwrap();
println!(
"Multi-label query (rust AND fn) returned {} symbols",
symbols.len()
);
for symbol in &symbols {
assert!(
symbol.file_path.contains(".rs") || symbol.file_path.contains("rust"),
"Multi-label results should match all labels"
);
}
}
#[test]
fn test_magellan_get_code_chunk_nonexistent_file() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn test() {}";
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let nonexistent_path = temp_dir.path().join("does_not_exist.rs");
let result = integration.get_code_chunk(&nonexistent_path, 0, 100);
assert!(
result.is_ok(),
"get_code_chunk should succeed for non-existent file"
);
assert!(
result.unwrap().is_none(),
"Should return None for non-existent file"
);
println!("Correctly returned None for non-existent file path");
}
#[test]
fn test_magellan_get_code_chunk_invalid_range() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn test() {}";
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let result = integration.get_code_chunk(&file_path, 0, 100000);
assert!(
result.is_ok(),
"get_code_chunk should succeed for invalid range"
);
let chunk_opt = result.unwrap();
let _ = chunk_opt;
println!(
"get_code_chunk handled invalid byte range: {:?}",
chunk_opt.is_some()
);
}
#[test]
fn test_magellan_get_code_chunk_inverted_range() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn test() {}";
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let result = integration.get_code_chunk(&file_path, 100, 50);
assert!(
result.is_ok(),
"get_code_chunk should handle inverted range"
);
println!("get_code_chunk handled inverted range gracefully");
}
#[test]
fn test_magellan_get_code_chunks_nonexistent_symbol() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn existing_fn() {}";
let file_path = create_test_file(temp_dir.path(), "test.rs", content);
integration.index_file(&file_path).unwrap();
let result = integration.get_code_chunks_for_symbol(&file_path, "NonExistentSymbol");
assert!(
result.is_ok(),
"Query should succeed for non-existent symbol"
);
let chunks = result.unwrap();
assert_eq!(
chunks.len(),
0,
"Non-existent symbol should return empty chunks"
);
println!("Correctly returned empty chunks for non-existent symbol");
}
#[test]
fn test_magellan_index_python() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
def calculate_sum(a: int, b: int) -> int:
"""Calculate the sum of two numbers."""
return a + b
class Calculator:
"""A simple calculator class."""
def __init__(self, initial_value: int = 0):
self.value = initial_value
def add(self, x: int) -> int:
self.value += x
return self.value
"#;
let file_path = create_test_file(temp_dir.path(), "test.py", content);
let count = integration.index_file(&file_path);
assert!(count.is_ok(), "Should index Python file");
let indexed_count = count.unwrap();
assert!(indexed_count > 0, "Should index Python symbols");
let python_symbols = integration.query_by_labels(&["python"]);
assert!(python_symbols.is_ok());
assert!(
!python_symbols.unwrap().is_empty(),
"Should find Python symbols"
);
println!("Indexed {} Python symbols", indexed_count);
}
#[test]
fn test_magellan_index_typescript() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
interface IUser {
id: number;
name: string;
}
class User implements IUser {
id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
greet(): string {
return `Hello, ${this.name}`;
}
}
function createUser(id: number, name: string): IUser {
return new User(id, name);
}
"#;
let file_path = create_test_file(temp_dir.path(), "test.ts", content);
let count = integration.index_file(&file_path);
assert!(count.is_ok(), "Should index TypeScript file");
let indexed_count = count.unwrap();
assert!(indexed_count > 0, "Should index TypeScript symbols");
let ts_symbols = integration.query_by_labels(&["typescript"]);
assert!(ts_symbols.is_ok());
assert!(
!ts_symbols.unwrap().is_empty(),
"Should find TypeScript symbols"
);
println!("Indexed {} TypeScript symbols", indexed_count);
}
#[test]
fn test_magellan_index_multiple_languages() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let rust_content = "pub fn rust_function() -> i32 { 42 }";
let python_content = "def python_function(): return 42";
let typescript_content = "function typescriptFunction() { return 42; }";
let rust_file = create_test_file(temp_dir.path(), "test.rs", rust_content);
let python_file = create_test_file(temp_dir.path(), "test.py", python_content);
let typescript_file = create_test_file(temp_dir.path(), "test.ts", typescript_content);
let rust_count = integration.index_file(&rust_file).unwrap();
let python_count = integration.index_file(&python_file).unwrap();
let typescript_count = integration.index_file(&typescript_file).unwrap();
assert!(rust_count > 0, "Should index Rust symbols");
assert!(python_count > 0, "Should index Python symbols");
assert!(typescript_count > 0, "Should index TypeScript symbols");
println!(
"Indexed {} Rust, {} Python, {} TypeScript symbols",
rust_count, python_count, typescript_count
);
let rust_symbols = integration.query_by_labels(&["rust"]).unwrap();
let python_symbols = integration.query_by_labels(&["python"]).unwrap();
let ts_symbols = integration.query_by_labels(&["typescript"]).unwrap();
assert!(!rust_symbols.is_empty(), "Should find Rust symbols");
assert!(!python_symbols.is_empty(), "Should find Python symbols");
assert!(!ts_symbols.is_empty(), "Should find TypeScript symbols");
println!(
"Queried: {} Rust, {} Python, {} TypeScript symbols",
rust_symbols.len(),
python_symbols.len(),
ts_symbols.len()
);
}
#[test]
fn test_magellan_unicode_content() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = r#"
/// 日本語のコメント - Japanese comment
/// This function demonstrates Unicode handling
pub fn 日本語の関数() -> i32 {
42
}
/// Emoji test 🚀🎉
pub fn emoji_test() -> String {
"🎉 Success! 成功!".to_string()
}
/// Mixed Unicode: العربية עברית 한국어
pub fn mixed_unicode() -> String {
"Mixed: Hello 世界 مرحبا בעברית".to_string()
}
"#;
let file_path = create_test_file(temp_dir.path(), "unicode.rs", content);
let result = integration.index_file(&file_path);
assert!(result.is_ok(), "index_file should handle Unicode content");
let indexed_count = result.unwrap();
assert!(indexed_count > 0, "Should index Unicode symbols");
println!("Indexed {} Unicode symbols", indexed_count);
let symbols = integration.query_by_labels(&["rust"]).unwrap();
let unicode_symbols: Vec<_> = symbols
.iter()
.filter(|s| s.name.contains("日本語") || s.name.contains("emoji"))
.collect();
println!("Found {} symbols with Unicode names", unicode_symbols.len());
for symbol in unicode_symbols {
let chunk = integration
.get_code_chunk(&file_path, symbol.byte_start, symbol.byte_end)
.unwrap();
if let Some(content) = chunk {
assert!(!content.is_empty(), "Unicode content should be retrievable");
println!(
"Retrieved Unicode chunk for '{}': {} bytes",
symbol.name,
content.len()
);
}
}
}
#[test]
fn test_magellan_unicode_file_path() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let content = "pub fn test() {}";
let file_path = create_test_file(temp_dir.path(), "test_日本語.rs", content);
let result = integration.index_file(&file_path);
assert!(result.is_ok(), "Should handle Unicode in file path");
let symbols = integration.query_by_labels(&["rust"]).unwrap();
assert!(!symbols.is_empty());
println!("Unicode file path handled: {}", symbols[0].file_path);
}
#[test]
fn test_magellan_get_all_labels() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let labels = integration.get_all_labels().unwrap();
assert!(labels.is_empty(), "New database should have no labels");
let rust_content = "pub fn test() {}";
let rust_file = create_test_file(temp_dir.path(), "test.rs", rust_content);
integration.index_file(&rust_file).unwrap();
let python_content = "def test(): pass";
let python_file = create_test_file(temp_dir.path(), "test.py", python_content);
integration.index_file(&python_file).unwrap();
let labels = integration.get_all_labels().unwrap();
assert!(!labels.is_empty(), "Should have labels after indexing");
println!("Available labels: {:?}", labels);
}
#[test]
fn test_magellan_count_by_label() {
let (mut integration, temp_dir) = create_test_magellan_integration();
let count = integration.count_by_label("rust").unwrap();
assert_eq!(count, 0, "New database should have 0 symbols for any label");
let rust_content = r#"
pub fn first() {}
pub fn second() {}
pub struct TestStruct {}
"#;
let rust_file = create_test_file(temp_dir.path(), "test.rs", rust_content);
integration.index_file(&rust_file).unwrap();
let count = integration.count_by_label("rust").unwrap();
assert!(count > 0, "Should have symbols with 'rust' label");
println!("Counted {} symbols with 'rust' label", count);
}
#[test]
fn test_magellan_inner_access() {
let (mut integration, _temp_dir) = create_test_magellan_integration();
let inner = integration.inner();
assert!(inner.get_all_labels().is_ok(), "Should access inner graph");
let _inner_mut = integration.inner_mut();
}