use magellan::CodeGraph;
use tempfile::TempDir;
fn setup_test_db() -> TempDir {
TempDir::new().expect("Failed to create temp directory")
}
fn create_test_source() -> &'static str {
r#"
/// Process data with validation
pub fn process_data(input: i32) -> i32 {
if input > 0 {
input * 2
} else {
0
}
}
/// Data processor struct
pub struct DataProcessor {
threshold: i32,
}
impl DataProcessor {
/// Create new processor
pub fn new(threshold: i32) -> Self {
Self { threshold }
}
/// Process value
pub fn process(&self, value: i32) -> Option<i32> {
if value >= self.threshold {
Some(value * 2)
} else {
None
}
}
}
"#
}
#[test]
fn test_chunk_storage_during_indexing() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "test_storage.rs";
let source = create_test_source().as_bytes();
let symbol_count = graph.index_file(path, source).unwrap();
assert!(symbol_count > 0, "Should have indexed symbols");
let chunks = graph.get_code_chunks(path).unwrap();
assert!(!chunks.is_empty(), "Should have stored chunks");
let source_str = create_test_source();
for chunk in &chunks {
assert_eq!(chunk.file_path, path, "File path should match");
assert!(
chunk.byte_start < chunk.byte_end,
"Byte span should be valid: {} < {}",
chunk.byte_start,
chunk.byte_end
);
let expected_content = &source_str[chunk.byte_start..chunk.byte_end];
assert_eq!(
chunk.content, expected_content,
"Content should match source at byte span {}..{}",
chunk.byte_start, chunk.byte_end
);
assert!(chunk.symbol_name.is_some(), "Symbol name should be set");
assert!(chunk.symbol_kind.is_some(), "Symbol kind should be set");
assert_eq!(
chunk.content_hash.len(),
64,
"Content hash should be SHA-256 (64 hex chars)"
);
assert!(
chunk.content_hash.chars().all(|c| c.is_ascii_hexdigit()),
"Content hash should be valid hex"
);
}
let symbol_names: Vec<_> = chunks
.iter()
.filter_map(|c| c.symbol_name.as_ref())
.collect();
assert!(
symbol_names.contains(&&"process_data".to_string()),
"Should have chunk for process_data function"
);
assert!(
symbol_names.contains(&&"DataProcessor".to_string()),
"Should have chunk for DataProcessor struct"
);
}
#[test]
fn test_chunk_deletion_on_file_delete() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let path = "test_deletion.rs";
let source = create_test_source().as_bytes();
graph.index_file(path, source).unwrap();
let chunks_before = graph.get_code_chunks(path).unwrap();
let chunk_count_before = chunks_before.len();
assert!(chunk_count_before > 0, "Should have chunks before deletion");
let result = graph.delete_file_facts(path).unwrap();
let chunks_after = graph.get_code_chunks(path).unwrap();
assert_eq!(
chunks_after.len(),
0,
"All chunks should be deleted after file deletion"
);
assert_eq!(
result.chunks_deleted, chunk_count_before,
"DeleteResult should report correct chunks_deleted count"
);
}
#[test]
fn test_content_hash_deduplication() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let identical_function = r#"
pub fn helper_function(x: i32) -> i32 {
x + 42
}
"#;
let path1 = "file1.rs";
let path2 = "file2.rs";
graph
.index_file(path1, identical_function.as_bytes())
.unwrap();
graph
.index_file(path2, identical_function.as_bytes())
.unwrap();
let chunks1 = graph.get_code_chunks(path1).unwrap();
let chunks2 = graph.get_code_chunks(path2).unwrap();
assert!(!chunks1.is_empty(), "File1 should have chunks");
assert!(!chunks2.is_empty(), "File2 should have chunks");
let chunk1 = chunks1
.iter()
.find(|c| c.symbol_name.as_deref() == Some("helper_function"))
.expect("Should find helper_function in file1");
let chunk2 = chunks2
.iter()
.find(|c| c.symbol_name.as_deref() == Some("helper_function"))
.expect("Should find helper_function in file2");
assert_eq!(
chunk1.content_hash, chunk2.content_hash,
"Identical content should have identical content_hash"
);
assert_eq!(
chunk1.content, chunk2.content,
"Content should be identical"
);
assert_eq!(chunk1.file_path, path1, "Chunk1 should have path1");
assert_eq!(chunk2.file_path, path2, "Chunk2 should have path2");
assert_ne!(
chunk1.id, chunk2.id,
"Chunks should have different IDs (stored separately)"
);
}
#[test]
fn test_chunk_by_symbol_query() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let source = r#"
pub fn process(input: i32) -> i32 {
input + 1
}
pub mod utils {
pub fn process(input: &str) -> String {
input.to_uppercase()
}
}
pub struct Processor {
value: i32,
}
impl Processor {
pub fn process(&self) -> i32 {
self.value
}
}
"#;
let path = "test_symbol_query.rs";
graph.index_file(path, source.as_bytes()).unwrap();
let process_chunks = graph.get_code_chunks_for_symbol(path, "process").unwrap();
assert!(
!process_chunks.is_empty(),
"Should find chunks for 'process'"
);
for chunk in &process_chunks {
assert_eq!(
chunk.symbol_name.as_deref(),
Some("process"),
"All chunks should have symbol_name 'process'"
);
assert_eq!(
chunk.file_path, path,
"All chunks should be from the same file"
);
}
let nonexistent_chunks = graph
.get_code_chunks_for_symbol(path, "nonexistent")
.unwrap();
assert_eq!(
nonexistent_chunks.len(),
0,
"Should return empty vector for non-existent symbol"
);
}
#[test]
fn test_chunk_by_span_query() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let source = create_test_source();
let path = "test_span_query.rs";
graph.index_file(path, source.as_bytes()).unwrap();
let all_chunks = graph.get_code_chunks(path).unwrap();
assert!(!all_chunks.is_empty(), "Should have chunks");
for chunk in &all_chunks {
let found = graph
.get_code_chunk_by_span(path, chunk.byte_start, chunk.byte_end)
.unwrap();
assert!(found.is_some(), "Should find chunk by exact span");
let found_chunk = found.unwrap();
assert_eq!(
found_chunk.id, chunk.id,
"Should return the exact same chunk (same ID)"
);
assert_eq!(found_chunk.content, chunk.content, "Content should match");
assert_eq!(
found_chunk.byte_start, chunk.byte_start,
"Byte start should match"
);
assert_eq!(
found_chunk.byte_end, chunk.byte_end,
"Byte end should match"
);
}
let nonexistent = graph.get_code_chunk_by_span(path, 99999, 100000).unwrap();
assert!(
nonexistent.is_none(),
"Should return None for non-existent span"
);
let invalid_span = graph.get_code_chunk_by_span(path, 100, 50).unwrap();
assert!(
invalid_span.is_none(),
"Should return None for invalid span (start > end)"
);
}
#[test]
fn test_chunk_count_matches_symbol_count() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let source = create_test_source();
let path = "test_count.rs";
let _symbol_count = graph.index_file(path, source.as_bytes()).unwrap();
let symbols = graph.symbols_in_file(path).unwrap();
let chunks = graph.get_code_chunks(path).unwrap();
assert!(!chunks.is_empty(), "Should have at least some chunks");
assert!(
chunks.len() <= symbols.len(),
"Chunks should not exceed symbols"
);
for chunk in &chunks {
assert!(chunk.symbol_name.is_some(), "Chunk should have symbol_name");
assert!(chunk.symbol_kind.is_some(), "Chunk should have symbol_kind");
}
}
#[test]
fn test_chunk_content_hash_deterministic() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let source = r#"
pub fn deterministic_function(x: i32) -> i32 {
x * 2
}
"#;
let path = "test_deterministic.rs";
graph.index_file(path, source.as_bytes()).unwrap();
let chunks1 = graph.get_code_chunks(path).unwrap();
graph.index_file(path, source.as_bytes()).unwrap();
let chunks2 = graph.get_code_chunks(path).unwrap();
assert!(!chunks1.is_empty(), "First index should create chunks");
assert!(!chunks2.is_empty(), "Second index should create chunks");
let chunk1 = chunks1
.iter()
.find(|c| c.symbol_name.as_deref() == Some("deterministic_function"))
.expect("Should find function in first index");
let chunk2 = chunks2
.iter()
.find(|c| c.symbol_name.as_deref() == Some("deterministic_function"))
.expect("Should find function in second index");
assert_eq!(
chunk1.content_hash, chunk2.content_hash,
"Content hash should be deterministic for identical content"
);
}
#[test]
fn test_chunk_byte_spans_within_bounds() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let source = create_test_source();
let source_len = source.len();
let path = "test_bounds.rs";
graph.index_file(path, source.as_bytes()).unwrap();
let chunks = graph.get_code_chunks(path).unwrap();
for chunk in &chunks {
assert!(
chunk.byte_start <= source_len,
"byte_start {} should be within source length {}",
chunk.byte_start,
source_len
);
assert!(
chunk.byte_end <= source_len,
"byte_end {} should be within source length {}",
chunk.byte_end,
source_len
);
assert!(
chunk.byte_start < chunk.byte_end,
"byte_start {} should be less than byte_end {}",
chunk.byte_start,
chunk.byte_end
);
let _ = &source[chunk.byte_start..chunk.byte_end];
}
}
#[test]
fn test_chunk_content_matches_source() {
let temp_dir = setup_test_db();
let db_path = temp_dir.path().join("test.db");
let mut graph = CodeGraph::open(&db_path).unwrap();
let source = create_test_source();
let path = "test_content_match.rs";
graph.index_file(path, source.as_bytes()).unwrap();
let chunks = graph.get_code_chunks(path).unwrap();
for chunk in &chunks {
let expected_content = &source[chunk.byte_start..chunk.byte_end];
assert_eq!(
chunk.content, expected_content,
"Chunk content should match source at span {}..{}",
chunk.byte_start, chunk.byte_end
);
let expected_len = chunk.byte_end - chunk.byte_start;
assert_eq!(
chunk.content.len(),
expected_len,
"Content length should match byte span length"
);
}
}