use anyhow::Result;
use link_cli::{LinkStorage, QueryProcessor};
use tempfile::NamedTempFile;
fn auto_processor() -> QueryProcessor {
QueryProcessor::new(false).with_auto_create_missing_references(true)
}
#[test]
fn test_query_processor_create() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
let changes = processor.process_query(&mut storage, "(()((1 2)))")?;
assert!(!changes.is_empty());
assert!(changes[0].0.is_none()); assert!(changes[0].1.is_some());
Ok(())
}
#[test]
fn test_query_processor_empty() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
let changes = processor.process_query(&mut storage, "")?;
assert!(changes.is_empty());
Ok(())
}
#[test]
fn test_missing_numeric_reference_fails_without_auto_create() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = QueryProcessor::new(false);
let error = processor
.process_query(&mut storage, "(() ((1: 10 20)))")
.expect_err("missing numeric references should fail validation");
assert!(error.to_string().contains("10"));
assert!(error
.to_string()
.contains("--auto-create-missing-references"));
Ok(())
}
#[test]
fn test_future_numeric_references_succeed_without_auto_create() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = QueryProcessor::new(false);
processor.process_query(&mut storage, "(() ((1: 1 2) (2: 2 1)))")?;
assert_eq!(storage.get(1).unwrap().source, 1);
assert_eq!(storage.get(1).unwrap().target, 2);
assert_eq!(storage.get(2).unwrap().source, 2);
assert_eq!(storage.get(2).unwrap().target, 1);
Ok(())
}
#[test]
fn test_auto_create_missing_numeric_reference_creates_point_link() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() ((20: 10 20)))")?;
let point = storage
.get(10)
.expect("missing numeric reference should be created");
assert_eq!(point.source, 10);
assert_eq!(point.target, 10);
let link = storage.get(20).expect("defined link should be created");
assert_eq!(link.source, 10);
assert_eq!(link.target, 20);
Ok(())
}
#[test]
fn test_auto_create_missing_numeric_reference_fills_existing_gap() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() ((3: 3 3)))")?;
processor.process_query(&mut storage, "(() ((4: 1 4)))")?;
let point = storage
.get(1)
.expect("missing lower numeric reference should be created");
assert_eq!(point.source, 1);
assert_eq!(point.target, 1);
let link = storage.get(4).expect("defined link should be created");
assert_eq!(link.source, 1);
assert_eq!(link.target, 4);
Ok(())
}
#[test]
fn test_missing_named_reference_fails_without_auto_create() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = QueryProcessor::new(false);
let error = processor
.process_query(&mut storage, "(() ((child: father mother)))")
.expect_err("missing named references should fail validation");
assert!(error.to_string().contains("father"));
assert!(error
.to_string()
.contains("--auto-create-missing-references"));
Ok(())
}
#[test]
fn test_auto_create_missing_named_references_creates_point_links() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() ((child: father mother)))")?;
let father_id = storage.get_by_name("father").expect("father should exist");
let mother_id = storage.get_by_name("mother").expect("mother should exist");
let child_id = storage.get_by_name("child").expect("child should exist");
let father = storage.get(father_id).unwrap();
assert_eq!(father.source, father_id);
assert_eq!(father.target, father_id);
let mother = storage.get(mother_id).unwrap();
assert_eq!(mother.source, mother_id);
assert_eq!(mother.target, mother_id);
let child = storage.get(child_id).unwrap();
assert_eq!(child.source, father_id);
assert_eq!(child.target, mother_id);
Ok(())
}
#[test]
fn test_deduplicate_duplicate_pair_with_named_links() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() (((m a) (m a))))")?;
let all_links = storage.all();
assert_eq!(all_links.len(), 4);
let m_id = storage.get_by_name("m").expect("m should exist");
let a_id = storage.get_by_name("a").expect("a should exist");
let m_link = storage.get(m_id).unwrap();
assert_eq!(m_link.source, m_id);
assert_eq!(m_link.target, m_id);
let a_link = storage.get(a_id).unwrap();
assert_eq!(a_link.source, a_id);
assert_eq!(a_link.target, a_id);
let ma_id = storage.search(m_id, a_id).expect("(m a) link should exist");
let outer_id = storage
.search(ma_id, ma_id)
.expect("((m a) (m a)) link should exist");
let outer_link = storage.get(outer_id).unwrap();
assert_eq!(
outer_link.source, outer_link.target,
"Outer link should reference the same deduplicated sub-link"
);
Ok(())
}
#[test]
fn test_deduplicate_duplicate_pair_with_numeric_links() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() (((1 2) (1 2))))")?;
let all_links = storage.all();
assert_eq!(all_links.len(), 2);
let link1 = storage.get(1).expect("Link 1 should exist");
assert_eq!(link1.source, 1);
assert_eq!(link1.target, 2);
let link2 = storage.get(2).expect("Link 2 should exist");
assert_eq!(link2.source, 1);
assert_eq!(link2.target, 1);
Ok(())
}
#[test]
fn test_deduplicate_triple_duplicate_pair() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() (((a b) ((a b) (a b)))))")?;
let all_links = storage.all();
assert_eq!(all_links.len(), 5);
let a_id = storage.get_by_name("a").expect("a should exist");
let b_id = storage.get_by_name("b").expect("b should exist");
let a_link = storage.get(a_id).unwrap();
assert_eq!(a_link.source, a_id);
assert_eq!(a_link.target, a_id);
let b_link = storage.get(b_id).unwrap();
assert_eq!(b_link.source, b_id);
assert_eq!(b_link.target, b_id);
let ab_id = storage.search(a_id, b_id).expect("(a b) link should exist");
let inner_id = storage
.search(ab_id, ab_id)
.expect("((a b) (a b)) link should exist");
let outer_id = storage
.search(ab_id, inner_id)
.expect("outer link should exist");
assert!(outer_id > 0);
Ok(())
}
#[test]
fn test_deduplicate_with_different_pairs() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() (((a b) (b a))))")?;
let all_links = storage.all();
assert_eq!(all_links.len(), 5);
let a_id = storage.get_by_name("a").expect("a should exist");
let b_id = storage.get_by_name("b").expect("b should exist");
let a_link = storage.get(a_id).unwrap();
assert_eq!(a_link.source, a_id);
assert_eq!(a_link.target, a_id);
let b_link = storage.get(b_id).unwrap();
assert_eq!(b_link.source, b_id);
assert_eq!(b_link.target, b_id);
let ab_id = storage.search(a_id, b_id).expect("(a b) link should exist");
let ba_id = storage.search(b_id, a_id).expect("(b a) link should exist");
let outer_id = storage
.search(ab_id, ba_id)
.expect("outer link should exist");
let outer_link = storage.get(outer_id).unwrap();
assert_ne!(outer_link.source, outer_link.target);
Ok(())
}
#[test]
fn test_deduplicate_nested_duplicates() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() ((((x y) (x y)) ((x y) (x y)))))")?;
let all_links = storage.all();
assert_eq!(all_links.len(), 5);
let x_id = storage.get_by_name("x").expect("x should exist");
let y_id = storage.get_by_name("y").expect("y should exist");
let x_link = storage.get(x_id).unwrap();
assert_eq!(x_link.source, x_id);
assert_eq!(x_link.target, x_id);
let y_link = storage.get(y_id).unwrap();
assert_eq!(y_link.source, y_id);
assert_eq!(y_link.target, y_id);
let xy_id = storage.search(x_id, y_id).expect("(x y) link should exist");
let level1_id = storage
.search(xy_id, xy_id)
.expect("((x y) (x y)) link should exist");
let level2_id = storage
.search(level1_id, level1_id)
.expect("outer link should exist");
assert!(level2_id > 0);
Ok(())
}
#[test]
fn test_deduplicate_named_links_multiple_queries() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() ((p: p p)))")?;
processor.process_query(&mut storage, "(() ((a: a a)))")?;
let p_id = storage.get_by_name("p").expect("p should exist");
let a_id = storage.get_by_name("a").expect("a should exist");
processor.process_query(&mut storage, "(() (((p a) (p a))))")?;
assert_eq!(storage.get_by_name("p"), Some(p_id));
assert_eq!(storage.get_by_name("a"), Some(a_id));
let p_link = storage.get(p_id).unwrap();
assert_eq!(p_link.source, p_id);
assert_eq!(p_link.target, p_id);
let a_link = storage.get(a_id).unwrap();
assert_eq!(a_link.source, a_id);
assert_eq!(a_link.target, a_id);
let pa_id = storage.search(p_id, a_id).expect("(p a) link should exist");
let outer_id = storage
.search(pa_id, pa_id)
.expect("((p a) (p a)) link should exist");
let outer_link = storage.get(outer_id).unwrap();
assert_eq!(outer_link.source, pa_id);
assert_eq!(outer_link.target, pa_id);
Ok(())
}
#[test]
fn test_deduplicate_mixed_named_and_numeric() -> Result<()> {
let temp_file = NamedTempFile::new()?;
let db_path = temp_file.path().to_str().unwrap();
let mut storage = LinkStorage::new(db_path, false)?;
let processor = auto_processor();
processor.process_query(&mut storage, "(() ((m a)))")?;
let m_id = storage.get_by_name("m").expect("m should exist");
let a_id = storage.get_by_name("a").expect("a should exist");
processor.process_query(&mut storage, "(() (((m a) (m a))))")?;
assert_eq!(storage.get_by_name("m"), Some(m_id));
assert_eq!(storage.get_by_name("a"), Some(a_id));
let all_links = storage.all();
assert_eq!(all_links.len(), 4);
Ok(())
}