use super::*;
#[cfg(test)]
mod declared_type_annotation_tests {
use super::*;
use crate::datatypes::values::Value;
use crate::graph::property_types::DeclaredType;
use crate::graph::schema::NodeData;
use crate::graph::storage::GraphWrite;
use std::collections::HashMap;
fn person_graph() -> DirGraph {
let mut graph = DirGraph::new();
for (id, age) in [(1u32, 30i64), (2, 25)] {
let node = NodeData::new(
Value::UniqueId(id),
Value::String(format!("p{id}")),
"Person".to_string(),
HashMap::from([("age".to_string(), Value::Int64(age))]),
&mut graph.interner,
);
let idx = graph.graph.add_node(node);
graph
.type_indices
.entry_or_default("Person".to_string())
.push(idx);
}
graph
}
fn describe(graph: &DirGraph) -> String {
compute_description(
graph,
None,
&ConnectionDetail::Off,
&CypherDetail::Off,
&FluentDetail::Off,
None,
None,
None,
)
.unwrap()
}
#[test]
fn describe_annotates_a_declared_property_type() {
let mut graph = person_graph();
assert!(
!describe(&graph).contains("declared_type"),
"an unconstrained property must carry no annotation"
);
graph
.create_property_type_constraint("Person", "age", DeclaredType::Integer)
.unwrap();
let described = describe(&graph);
assert!(
described.contains("declared_type=\"INTEGER\""),
"got: {described}"
);
}
#[test]
fn describe_annotates_a_declared_relationship_constraint() {
use crate::graph::algorithms::Interrupt;
use crate::graph::languages::cypher::executor::write::execute_mutable;
use crate::graph::languages::cypher::parser::parse_cypher;
let mut graph = DirGraph::new();
let parsed = parse_cypher(
"CREATE (a:Person {person_id: 1})-[:KNOWS {since: 2020}]->(b:Person {person_id: 2})",
)
.unwrap();
execute_mutable(&mut graph, &parsed, HashMap::new(), Interrupt::default()).unwrap();
let with_connections = |graph: &DirGraph| {
compute_description(
graph,
None,
&ConnectionDetail::Topics(vec!["KNOWS".to_string()]),
&CypherDetail::Off,
&FluentDetail::Off,
None,
None,
None,
)
.unwrap()
};
assert!(
!with_connections(&graph).contains("constraint="),
"an unconstrained edge property must carry no annotation"
);
graph
.create_rel_not_null_constraint("KNOWS", "since", &Interrupt::default())
.unwrap();
graph
.create_rel_property_type_constraint(
"KNOWS",
"since",
DeclaredType::Integer,
&Interrupt::default(),
)
.unwrap();
let described = with_connections(&graph);
assert!(
described.contains("constraint=\"not_null\""),
"got: {described}"
);
assert!(
described.contains("declared_type=\"INTEGER\""),
"got: {described}"
);
}
#[test]
fn a_typed_property_keeps_its_uniqueness_annotation() {
let mut graph = person_graph();
graph.create_unique_constraint("Person", &["age"]).unwrap();
graph
.create_property_type_constraint("Person", "age", DeclaredType::Integer)
.unwrap();
let described = describe(&graph);
assert!(
described.contains("constraint=\"unique\""),
"got: {described}"
);
assert!(
described.contains("declared_type=\"INTEGER\""),
"got: {described}"
);
}
}
#[cfg(test)]
mod mcp_quickstart_tests {
use super::mcp_quickstart;
#[test]
fn names_only_current_install_and_extension_contracts() {
let quickstart = mcp_quickstart();
for expected in [
"pip install kglite",
"cargo install kglite-mcp-server",
"trust.allow_embedder: true",
"library: sentence-transformers",
"--features fastembed",
] {
assert!(quickstart.contains(expected), "missing {expected:?}");
}
for retired in ["kglite[mcp]", "--embedder", "--trust-tools", "python:"] {
assert!(
!quickstart.contains(retired),
"retired contract returned: {retired:?}"
);
}
}
}
#[cfg(test)]
mod focused_detail_error_tests {
use super::*;
use crate::graph::session::{execute_mut, ExecuteOptions};
use std::collections::HashMap;
fn vessel_graph() -> DirGraph {
let params = HashMap::new();
let opts = ExecuteOptions::eager(¶ms);
let mut graph = DirGraph::new();
execute_mut(&mut graph, "CREATE (:Vessel {id: 1})", &opts).expect("seed");
graph
}
#[test]
fn a_near_miss_type_name_is_suggested() {
let graph = vessel_graph();
let error = build_focused_detail(&graph, &["vessel".to_string()], None)
.expect_err("unknown type must error");
assert!(error.contains("Did you mean 'Vessel'?"), "{error}");
assert!(error.contains("Available: Vessel"), "{error}");
}
#[test]
fn a_far_type_name_gets_no_invented_suggestion() {
let graph = vessel_graph();
let error = build_focused_detail(&graph, &["Xyzzy".to_string()], None)
.expect_err("unknown type must error");
assert!(!error.contains("Did you mean"), "{error}");
}
}
#[cfg(test)]
mod index_annotation_tests {
use super::*;
use crate::datatypes::values::Value;
use crate::graph::storage::backend::GraphBackend;
use tempfile::TempDir;
fn city_graph() -> DirGraph {
let frame = crate::datatypes::DataFrame::from_cypher_rows(
vec!["id".into(), "title".into(), "city".into(), "pop".into()],
vec![
vec![
Value::Int64(1),
Value::String("p1".into()),
Value::String("Oslo".into()),
Value::Int64(700),
],
vec![
Value::Int64(2),
Value::String("p2".into()),
Value::String("Bergen".into()),
Value::Int64(280),
],
],
)
.unwrap();
let mut graph = DirGraph::new();
crate::graph::mutation::maintain::add_nodes(
&mut graph,
frame,
"Person".to_string(),
"id".to_string(),
Some("title".to_string()),
None,
)
.unwrap();
graph
}
fn describe(graph: &DirGraph) -> String {
compute_description(
graph,
None,
&ConnectionDetail::Off,
&CypherDetail::Off,
&FluentDetail::Off,
None,
None,
None,
)
.unwrap()
}
fn attr_of(described: &str, property: &str) -> String {
let needle = format!("name=\"{property}\"");
let line = described
.lines()
.find(|l| l.contains(&needle))
.unwrap_or_else(|| panic!("no <prop {needle}> in: {described}"));
line.trim().to_string()
}
#[test]
fn a_memory_hash_index_advertises_equality_only() {
let mut graph = city_graph();
graph.create_index("Person", "city");
let line = attr_of(&describe(&graph), "city");
assert!(line.contains("indexed=\"eq\""), "got: {line}");
}
#[test]
fn a_range_index_is_reported_under_its_own_name() {
let mut graph = city_graph();
graph.create_range_index("Person", "pop");
let line = attr_of(&describe(&graph), "pop");
assert!(line.contains("indexed=\"range\""), "got: {line}");
}
#[test]
fn equality_and_range_on_one_property_report_both() {
let mut graph = city_graph();
graph.create_index("Person", "pop");
graph.create_range_index("Person", "pop");
let line = attr_of(&describe(&graph), "pop");
assert!(line.contains("indexed=\"eq,range\""), "got: {line}");
}
#[test]
fn a_disk_string_index_advertises_prefix() {
let dir = TempDir::new().unwrap();
let mut graph = city_graph();
graph.enable_disk_mode().unwrap();
graph.save_disk(dir.path().to_str().unwrap()).unwrap();
match &mut graph.graph {
GraphBackend::Disk(disk) => {
assert_eq!(disk.build_property_index("Person", "city").unwrap(), 2);
}
_ => panic!("expected disk backend"),
}
let line = attr_of(&describe(&graph), "city");
assert!(line.contains("indexed=\"eq,prefix\""), "got: {line}");
}
#[test]
fn capture_wrapping_does_not_hide_the_disk_index() {
let dir = TempDir::new().unwrap();
let mut graph = city_graph();
graph.enable_disk_mode().unwrap();
graph.save_disk(dir.path().to_str().unwrap()).unwrap();
match &mut graph.graph {
GraphBackend::Disk(disk) => {
assert_eq!(disk.build_property_index("Person", "city").unwrap(), 2);
}
_ => panic!("expected disk backend"),
}
graph.graph.wrap_for_capture();
assert!(
graph.has_any_index("Person", "city"),
"a wrapped disk graph still has its persistent index"
);
let line = attr_of(&describe(&graph), "city");
assert!(line.contains("indexed=\"eq,prefix\""), "got: {line}");
}
}