use magellan::CodeGraph;
use tempfile::TempDir;
#[test]
fn test_delete_single_file_no_orphans() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "test_single.rs";
let source = br#"
fn main() {
println!("hello");
}
"#;
graph.index_file(path, source).unwrap();
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
let report_before = graph.validate_graph();
assert!(
report_before.passed,
"Graph should be valid before delete: {:?}",
report_before.errors
);
graph.delete_file(path).unwrap();
let report_after = graph.validate_graph();
assert!(
report_after.passed,
"Graph should have no orphans after delete: {:?}",
report_after.errors
);
assert!(
report_after.errors.is_empty(),
"Should have no orphan errors after delete"
);
}
#[test]
fn test_delete_referenced_file_no_orphans() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path_a = "lib_a.rs";
let source_a = br#"
pub fn foo() -> i32 {
42
}
pub fn bar() -> i32 {
43
}
"#;
let path_b = "lib_b.rs";
let source_b = br#"
fn call_foo() -> i32 {
crate::lib_a::foo()
}
"#;
graph.index_file(path_a, source_a).unwrap();
graph.index_references(path_a, source_a).unwrap();
graph.index_calls(path_a, source_a).unwrap();
graph.index_file(path_b, source_b).unwrap();
graph.index_references(path_b, source_b).unwrap();
graph.index_calls(path_b, source_b).unwrap();
let report_before = graph.validate_graph();
assert!(
report_before.passed,
"Graph should be valid before delete: {:?}",
report_before.errors
);
graph.delete_file(path_a).unwrap();
let report_after = graph.validate_graph();
let _orphan_refs_from_b: Vec<_> = report_after
.errors
.iter()
.filter(|e| e.code == "ORPHAN_REFERENCE" && e.details["file"] == "lib_b.rs")
.collect();
let symbols_b = graph.symbols_in_file(path_b).unwrap();
assert!(!symbols_b.is_empty(), "File B should still have symbols");
let file_a = graph.get_file_node(path_a).unwrap();
assert!(file_a.is_none(), "File A should be deleted");
}
#[test]
fn test_delete_calling_file_no_orphans() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path_a = "caller.rs";
let source_a = br#"
fn foo() -> i32 {
bar()
}
fn bar() -> i32 {
42
}
"#;
let path_b = "independent.rs";
let source_b = br#"
pub fn other_function() -> i32 {
100
}
"#;
graph.index_file(path_a, source_a).unwrap();
graph.index_references(path_a, source_a).unwrap();
graph.index_calls(path_a, source_a).unwrap();
graph.index_file(path_b, source_b).unwrap();
graph.index_references(path_b, source_b).unwrap();
graph.index_calls(path_b, source_b).unwrap();
let report_before = graph.validate_graph();
assert!(
report_before.passed,
"Graph should be valid before delete: {:?}",
report_before.errors
);
graph.delete_file(path_a).unwrap();
let report_after = graph.validate_graph();
assert!(
report_after.passed,
"Graph should have no orphan calls after deleting caller file: {:?}",
report_after.errors
);
let symbols_b = graph.symbols_in_file(path_b).unwrap();
assert!(!symbols_b.is_empty(), "File B should still have symbols");
}
#[test]
fn test_delete_multiple_files_no_orphans() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let paths = vec!["file1.rs", "file2.rs", "file3.rs"];
for (i, path) in paths.iter().enumerate() {
let source = format!(
r#"
// File {}
pub fn function_{i}() -> i32 {{
{i}
}}
"#,
i + 1
);
let source_bytes = source.as_bytes();
graph.index_file(path, source_bytes).unwrap();
graph.index_references(path, source_bytes).unwrap();
graph.index_calls(path, source_bytes).unwrap();
}
let report_before = graph.validate_graph();
assert!(
report_before.passed,
"Graph should be valid before delete: {:?}",
report_before.errors
);
for path in &paths {
graph.delete_file(path).unwrap();
let report = graph.validate_graph();
assert!(
report.passed,
"Graph should have no orphans after deleting {}: {:?}",
path, report.errors
);
}
let report_final = graph.validate_graph();
assert!(
report_final.passed,
"Graph should have no orphans after deleting all files: {:?}",
report_final.errors
);
for path in &paths {
let file_node = graph.get_file_node(path).unwrap();
assert!(file_node.is_none(), "{} should be deleted", path);
}
}
#[test]
fn test_delete_reindex_no_orphans() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "reindex_test.rs";
let source = br#"
fn test_function() -> i32 {
42
}
struct TestStruct {
value: i32,
}
impl TestStruct {
fn new(value: i32) -> Self {
Self { value }
}
}
"#;
let symbols_first = graph.index_file(path, source).unwrap();
assert!(symbols_first > 0, "Should have indexed symbols");
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
let report_before = graph.validate_graph();
assert!(
report_before.passed,
"Graph should be valid before delete: {:?}",
report_before.errors
);
graph.delete_file(path).unwrap();
let file_node = graph.get_file_node(path).unwrap();
assert!(file_node.is_none(), "File should be deleted");
let report_after_delete = graph.validate_graph();
assert!(
report_after_delete.passed,
"Graph should have no orphans after delete: {:?}",
report_after_delete.errors
);
let symbols_second = graph.index_file(path, source).unwrap();
assert_eq!(
symbols_second, symbols_first,
"Should index same number of symbols"
);
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
let report_after_reindex = graph.validate_graph();
assert!(
report_after_reindex.passed,
"Graph should have no orphans after re-index: {:?}",
report_after_reindex.errors
);
}
#[test]
fn test_delete_complex_file_no_orphans() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "complex.rs";
let source = br#"
// Module-level function
fn module_function() -> i32 {
42
}
// Struct definition
struct ComplexStruct {
field1: i32,
field2: String,
}
// impl block with methods
impl ComplexStruct {
fn new(field1: i32, field2: String) -> Self {
Self { field1, field2 }
}
fn get_field1(&self) -> i32 {
self.field1
}
fn set_field1(&mut self, value: i32) {
self.field1 = value;
}
}
// Trait definition
trait MyTrait {
fn trait_method(&self) -> i32;
}
// Trait implementation
impl MyTrait for ComplexStruct {
fn trait_method(&self) -> i32 {
self.field1
}
}
// Enum
enum MyEnum {
VariantA(i32),
VariantB(String),
}
impl MyEnum {
fn value(&self) -> i32 {
match self {
MyEnum::VariantA(v) => *v,
MyEnum::VariantB(_) => 0,
}
}
}
// Function that uses the complex types
fn use_complex_types() -> i32 {
let mut s = ComplexStruct::new(10, String::from("test"));
s.set_field1(20);
s.get_field1()
}
"#;
let symbol_count = graph.index_file(path, source).unwrap();
assert!(
symbol_count >= 10,
"Should have indexed many symbols (got {})",
symbol_count
);
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
let report_before = graph.validate_graph();
assert!(
report_before.passed,
"Graph should be valid before delete: {:?}",
report_before.errors
);
let delete_result = graph.delete_file(path).unwrap();
assert!(
delete_result.symbols_deleted > 0,
"Should have deleted symbols"
);
let report_after = graph.validate_graph();
assert!(
report_after.passed,
"Graph should have no orphans after deleting complex file: {:?}",
report_after.errors
);
let orphan_refs: Vec<_> = report_after
.errors
.iter()
.filter(|e| e.code == "ORPHAN_REFERENCE")
.collect();
assert!(
orphan_refs.is_empty(),
"Should have no ORPHAN_REFERENCE errors: {:?}",
orphan_refs
);
let orphan_calls: Vec<_> = report_after
.errors
.iter()
.filter(|e| e.code == "ORPHAN_CALL_NO_CALLER" || e.code == "ORPHAN_CALL_NO_CALLEE")
.collect();
assert!(
orphan_calls.is_empty(),
"Should have no ORPHAN_CALL errors: {:?}",
orphan_calls
);
}
#[test]
fn test_delete_file_code_chunks_removed() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "chunks_test.rs";
let source = br#"
fn function_one() -> i32 {
10
}
fn function_two() -> i32 {
20
}
fn function_three() -> i32 {
30
}
"#;
graph.index_file(path, source).unwrap();
let chunks_before = graph.get_code_chunks(path).unwrap();
let chunk_count = chunks_before.len();
assert!(
chunk_count > 0,
"Should have code chunks (got {})",
chunk_count
);
graph.delete_file(path).unwrap();
let chunks_after = graph.get_code_chunks(path).unwrap();
assert_eq!(chunks_after.len(), 0, "No chunks should remain");
}
#[test]
fn test_delete_file_edges_removed() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "edges_test.rs";
let source = br#"
fn test_func() -> i32 {
42
}
"#;
graph.index_file(path, source).unwrap();
let symbol_nodes = graph.symbol_nodes_in_file(path).unwrap();
assert!(!symbol_nodes.is_empty(), "Should have symbols");
graph.delete_file(path).unwrap();
let file_node = graph.get_file_node(path).unwrap();
assert!(file_node.is_none(), "File should be deleted");
let symbols_after = graph.symbols_in_file(path).unwrap();
assert_eq!(symbols_after.len(), 0, "All symbols should be deleted");
}
#[test]
fn test_no_orphan_reference_after_delete() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "ref_test.rs";
let source = br#"
fn foo() -> i32 {
42
}
fn bar() -> i32 {
foo() + 1
}
"#;
graph.index_file(path, source).unwrap();
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
graph.delete_file(path).unwrap();
let report = graph.validate_graph();
let orphan_refs: Vec<_> = report
.errors
.iter()
.filter(|e| e.code == "ORPHAN_REFERENCE")
.collect();
assert!(
orphan_refs.is_empty(),
"Should have no ORPHAN_REFERENCE errors after delete. Found: {:?}",
orphan_refs
);
}
#[test]
fn test_no_orphan_call_after_delete() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "call_test.rs";
let source = br#"
fn caller() -> i32 {
callee()
}
fn callee() -> i32 {
42
}
"#;
graph.index_file(path, source).unwrap();
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
graph.delete_file(path).unwrap();
let report = graph.validate_graph();
let orphan_caller: Vec<_> = report
.errors
.iter()
.filter(|e| e.code == "ORPHAN_CALL_NO_CALLER")
.collect();
let orphan_callee: Vec<_> = report
.errors
.iter()
.filter(|e| e.code == "ORPHAN_CALL_NO_CALLEE")
.collect();
assert!(
orphan_caller.is_empty(),
"Should have no ORPHAN_CALL_NO_CALLER errors after delete. Found: {:?}",
orphan_caller
);
assert!(
orphan_callee.is_empty(),
"Should have no ORPHAN_CALL_NO_CALLEE errors after delete. Found: {:?}",
orphan_callee
);
}
#[test]
fn test_empty_graph_valid_after_delete_all() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let files = vec!["file1.rs", "file2.rs", "file3.rs"];
for path in &files {
let source = br#"fn test() -> i32 { 42 }"#;
graph.index_file(path, source).unwrap();
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
}
for path in &files {
graph.delete_file(path).unwrap();
}
let report = graph.validate_graph();
assert!(
report.passed,
"Empty graph should be valid after deleting all files: {:?}",
report.errors
);
assert!(report.errors.is_empty(), "Should have no errors");
}
#[test]
fn test_validate_graph_after_delete_returns_clean_report() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "clean_test.rs";
let source = br#"
fn main() {
let x = 42;
println!("{}", x);
}
"#;
graph.index_file(path, source).unwrap();
graph.index_references(path, source).unwrap();
graph.index_calls(path, source).unwrap();
graph.delete_file(path).unwrap();
let report = graph.validate_graph();
assert!(report.passed, "Report should indicate passed");
assert!(
report.is_clean(),
"Report should indicate clean (no errors or warnings)"
);
assert_eq!(report.total_issues(), 0, "Total issues should be 0");
assert!(report.errors.is_empty(), "Errors list should be empty");
}