use std::collections::HashMap;
use std::sync::Arc;
use crate::core::PropertyMapBuilder;
use crate::db::AletheiaDB;
use super::server::AletheiaMcpServer;
use super::tools::*;
fn create_test_db() -> Arc<AletheiaDB> {
let db = AletheiaDB::new().expect("Failed to create database");
Arc::new(db)
}
#[allow(unused_imports)]
use rmcp::ServerHandler;
fn create_test_server() -> AletheiaMcpServer {
AletheiaMcpServer::new(create_test_db())
}
fn parse_response<T: serde::de::DeserializeOwned>(response: &str) -> Result<T, String> {
let value: serde_json::Value =
serde_json::from_str(response).map_err(|e| format!("Failed to parse JSON: {}", e))?;
if let Some(error) = value.get("error") {
return Err(error.as_str().unwrap_or("Unknown error").to_string());
}
serde_json::from_value(value).map_err(|e| format!("Failed to deserialize: {}", e))
}
#[test]
fn test_server_creation() {
let server = create_test_server();
assert!(server.db().node_count() == 0);
assert!(server.db().edge_count() == 0);
}
#[test]
fn test_server_with_existing_db() {
let db = create_test_db();
let _node_id = db
.create_node(
"Person",
PropertyMapBuilder::new().insert("name", "Alice").build(),
)
.expect("Failed to create node");
let server = AletheiaMcpServer::new(db);
assert_eq!(server.db().node_count(), 1);
}
mod node_tests {
use super::*;
#[test]
fn test_create_node_basic() {
let server = create_test_server();
let req = CreateNodeRequest {
label: "Person".to_string(),
properties: None,
};
let response = server.create_node(req);
let node: NodeResponse = parse_response(&response).expect("Failed to parse response");
assert_eq!(node.label, "Person");
}
#[test]
fn test_create_node_with_properties() {
let server = create_test_server();
let mut props = HashMap::new();
props.insert("name".to_string(), serde_json::json!("Alice"));
props.insert("age".to_string(), serde_json::json!(30));
let req = CreateNodeRequest {
label: "Person".to_string(),
properties: Some(props),
};
let response = server.create_node(req);
let node: NodeResponse = parse_response(&response).expect("Failed to parse response");
assert_eq!(node.label, "Person");
assert_eq!(
node.properties.get("name"),
Some(&serde_json::json!("Alice"))
);
assert_eq!(node.properties.get("age"), Some(&serde_json::json!(30)));
}
#[test]
fn test_get_node() {
let server = create_test_server();
let mut props = HashMap::new();
props.insert("name".to_string(), serde_json::json!("Bob"));
let create_req = CreateNodeRequest {
label: "Person".to_string(),
properties: Some(props),
};
let create_response = server.create_node(create_req);
let created: NodeResponse = parse_response(&create_response).expect("Failed to create");
let get_req = GetNodeRequest {
node_id: created.id,
};
let get_response = server.get_node(get_req);
let retrieved: NodeResponse = parse_response(&get_response).expect("Failed to get");
assert_eq!(retrieved.id, created.id);
assert_eq!(retrieved.label, "Person");
assert_eq!(
retrieved.properties.get("name"),
Some(&serde_json::json!("Bob"))
);
}
#[test]
fn test_get_nonexistent_node() {
let server = create_test_server();
let req = GetNodeRequest { node_id: 999999 };
let response = server.get_node(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_update_node() {
let server = create_test_server();
let mut props = HashMap::new();
props.insert("name".to_string(), serde_json::json!("Charlie"));
props.insert("age".to_string(), serde_json::json!(25));
let create_req = CreateNodeRequest {
label: "Person".to_string(),
properties: Some(props),
};
let create_response = server.create_node(create_req);
let created: NodeResponse = parse_response(&create_response).expect("Failed to create");
let mut new_props = HashMap::new();
new_props.insert("age".to_string(), serde_json::json!(26));
new_props.insert("city".to_string(), serde_json::json!("London"));
let update_req = UpdateNodeRequest {
node_id: created.id,
properties: new_props,
};
let update_response = server.update_node(update_req);
let updated: NodeResponse = parse_response(&update_response).expect("Failed to update");
assert_eq!(updated.properties.get("age"), Some(&serde_json::json!(26)));
assert_eq!(
updated.properties.get("city"),
Some(&serde_json::json!("London"))
);
}
#[test]
fn test_delete_node() {
let server = create_test_server();
let create_req = CreateNodeRequest {
label: "ToDelete".to_string(),
properties: None,
};
let create_response = server.create_node(create_req);
let created: NodeResponse = parse_response(&create_response).expect("Failed to create");
let node_id = created.id;
let delete_req = DeleteNodeRequest { node_id };
let delete_response = server.delete_node(delete_req);
let value: serde_json::Value = serde_json::from_str(&delete_response).unwrap();
assert_eq!(value.get("success"), Some(&serde_json::json!(true)));
let get_req = GetNodeRequest { node_id };
let get_response = server.get_node(get_req);
let get_value: serde_json::Value = serde_json::from_str(&get_response).unwrap();
assert!(get_value.get("error").is_some());
}
#[test]
fn test_list_nodes() {
let server = create_test_server();
for i in 0..5 {
let mut props = HashMap::new();
props.insert("index".to_string(), serde_json::json!(i));
let req = CreateNodeRequest {
label: "ListTest".to_string(),
properties: Some(props),
};
server.create_node(req);
}
let list_req = ListNodesRequest {
label: Some("ListTest".to_string()),
property_key: None,
property_value: None,
limit: None,
offset: None,
};
let response = server.list_nodes(list_req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(5)));
}
#[test]
fn test_list_nodes_with_label_filter() {
let server = create_test_server();
for _ in 0..3 {
server.create_node(CreateNodeRequest {
label: "TypeA".to_string(),
properties: None,
});
}
for _ in 0..2 {
server.create_node(CreateNodeRequest {
label: "TypeB".to_string(),
properties: None,
});
}
let list_req = ListNodesRequest {
label: Some("TypeA".to_string()),
property_key: None,
property_value: None,
limit: None,
offset: None,
};
let response = server.list_nodes(list_req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(3)));
}
#[test]
fn test_list_nodes_with_pagination() {
let server = create_test_server();
for i in 0..10 {
let mut props = HashMap::new();
props.insert("index".to_string(), serde_json::json!(i));
server.create_node(CreateNodeRequest {
label: "Paginated".to_string(),
properties: Some(props),
});
}
let page1_req = ListNodesRequest {
label: Some("Paginated".to_string()),
property_key: None,
property_value: None,
limit: Some(5),
offset: Some(0),
};
let page1_response = server.list_nodes(page1_req);
let page1: serde_json::Value = serde_json::from_str(&page1_response).unwrap();
assert_eq!(page1.get("count"), Some(&serde_json::json!(5)));
let page2_req = ListNodesRequest {
label: Some("Paginated".to_string()),
property_key: None,
property_value: None,
limit: Some(5),
offset: Some(5),
};
let page2_response = server.list_nodes(page2_req);
let page2: serde_json::Value = serde_json::from_str(&page2_response).unwrap();
assert_eq!(page2.get("count"), Some(&serde_json::json!(5)));
}
#[test]
fn test_count_nodes() {
let server = create_test_server();
let count_req = CountNodesRequest { label: None };
let response = server.count_nodes(count_req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(0)));
for _ in 0..3 {
server.create_node(CreateNodeRequest {
label: "Counted".to_string(),
properties: None,
});
}
let count_req = CountNodesRequest { label: None };
let response = server.count_nodes(count_req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(3)));
}
#[test]
fn test_count_nodes_with_label() {
let server = create_test_server();
for _ in 0..3 {
server.create_node(CreateNodeRequest {
label: "CountA".to_string(),
properties: None,
});
}
for _ in 0..2 {
server.create_node(CreateNodeRequest {
label: "CountB".to_string(),
properties: None,
});
}
let count_a = server.count_nodes(CountNodesRequest {
label: Some("CountA".to_string()),
});
let value_a: serde_json::Value = serde_json::from_str(&count_a).unwrap();
assert_eq!(value_a.get("count"), Some(&serde_json::json!(3)));
let count_b = server.count_nodes(CountNodesRequest {
label: Some("CountB".to_string()),
});
let value_b: serde_json::Value = serde_json::from_str(&count_b).unwrap();
assert_eq!(value_b.get("count"), Some(&serde_json::json!(2)));
}
}
mod edge_tests {
use super::*;
fn create_two_nodes(server: &AletheiaMcpServer) -> (u64, u64) {
let node1 = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Alice"));
m
}),
});
let n1: NodeResponse = parse_response(&node1).unwrap();
let node2 = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Bob"));
m
}),
});
let n2: NodeResponse = parse_response(&node2).unwrap();
(n1.id, n2.id)
}
#[test]
fn test_create_edge_basic() {
let server = create_test_server();
let (source_id, target_id) = create_two_nodes(&server);
let req = CreateEdgeRequest {
source_id,
target_id,
label: "KNOWS".to_string(),
properties: None,
};
let response = server.create_edge(req);
let edge: EdgeResponse = parse_response(&response).expect("Failed to create edge");
assert_eq!(edge.source_id, source_id);
assert_eq!(edge.target_id, target_id);
assert_eq!(edge.label, "KNOWS");
}
#[test]
fn test_create_edge_with_properties() {
let server = create_test_server();
let (source_id, target_id) = create_two_nodes(&server);
let mut props = HashMap::new();
props.insert("since".to_string(), serde_json::json!("2024-01-01"));
props.insert("strength".to_string(), serde_json::json!(0.9));
let req = CreateEdgeRequest {
source_id,
target_id,
label: "KNOWS".to_string(),
properties: Some(props),
};
let response = server.create_edge(req);
let edge: EdgeResponse = parse_response(&response).expect("Failed to create edge");
assert_eq!(
edge.properties.get("since"),
Some(&serde_json::json!("2024-01-01"))
);
assert_eq!(
edge.properties.get("strength"),
Some(&serde_json::json!(0.9))
);
}
#[test]
fn test_get_edge() {
let server = create_test_server();
let (source_id, target_id) = create_two_nodes(&server);
let create_response = server.create_edge(CreateEdgeRequest {
source_id,
target_id,
label: "KNOWS".to_string(),
properties: None,
});
let created: EdgeResponse = parse_response(&create_response).unwrap();
let get_response = server.get_edge(GetEdgeRequest {
edge_id: created.id,
});
let retrieved: EdgeResponse = parse_response(&get_response).unwrap();
assert_eq!(retrieved.id, created.id);
assert_eq!(retrieved.source_id, source_id);
assert_eq!(retrieved.target_id, target_id);
}
#[test]
fn test_update_edge() {
let server = create_test_server();
let (source_id, target_id) = create_two_nodes(&server);
let create_response = server.create_edge(CreateEdgeRequest {
source_id,
target_id,
label: "KNOWS".to_string(),
properties: None,
});
let created: EdgeResponse = parse_response(&create_response).unwrap();
let mut new_props = HashMap::new();
new_props.insert("weight".to_string(), serde_json::json!(0.5));
let update_response = server.update_edge(UpdateEdgeRequest {
edge_id: created.id,
properties: new_props,
});
let updated: EdgeResponse = parse_response(&update_response).unwrap();
assert_eq!(
updated.properties.get("weight"),
Some(&serde_json::json!(0.5))
);
}
#[test]
fn test_delete_edge() {
let server = create_test_server();
let (source_id, target_id) = create_two_nodes(&server);
let create_response = server.create_edge(CreateEdgeRequest {
source_id,
target_id,
label: "KNOWS".to_string(),
properties: None,
});
let created: EdgeResponse = parse_response(&create_response).unwrap();
let delete_response = server.delete_edge(DeleteEdgeRequest {
edge_id: created.id,
});
let value: serde_json::Value = serde_json::from_str(&delete_response).unwrap();
assert_eq!(value.get("success"), Some(&serde_json::json!(true)));
let get_response = server.get_edge(GetEdgeRequest {
edge_id: created.id,
});
let get_value: serde_json::Value = serde_json::from_str(&get_response).unwrap();
assert!(get_value.get("error").is_some());
}
#[test]
fn test_list_edges() {
let server = create_test_server();
let (n1, n2) = create_two_nodes(&server);
let node3 = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n3: NodeResponse = parse_response(&node3).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1,
target_id: n2,
label: "KNOWS".to_string(),
properties: None,
});
server.create_edge(CreateEdgeRequest {
source_id: n2,
target_id: n3.id,
label: "KNOWS".to_string(),
properties: None,
});
let list_response = server.list_edges(ListEdgesRequest {
label: None,
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&list_response).unwrap();
assert_eq!(value.get("total_count"), Some(&serde_json::json!(2)));
assert!(value.get("message").is_some());
}
#[test]
fn test_count_edges() {
let server = create_test_server();
let (n1, n2) = create_two_nodes(&server);
let count_response = server.count_edges(CountEdgesRequest { label: None });
let value: serde_json::Value = serde_json::from_str(&count_response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(0)));
server.create_edge(CreateEdgeRequest {
source_id: n1,
target_id: n2,
label: "KNOWS".to_string(),
properties: None,
});
let count_response = server.count_edges(CountEdgesRequest { label: None });
let value: serde_json::Value = serde_json::from_str(&count_response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(1)));
}
#[test]
fn test_get_outgoing_edges() {
let server = create_test_server();
let (n1, n2) = create_two_nodes(&server);
let node3 = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n3: NodeResponse = parse_response(&node3).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1,
target_id: n2,
label: "KNOWS".to_string(),
properties: None,
});
server.create_edge(CreateEdgeRequest {
source_id: n1,
target_id: n3.id,
label: "WORKS_WITH".to_string(),
properties: None,
});
let response = server.get_outgoing_edges(GetOutgoingEdgesRequest {
node_id: n1,
label: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(2)));
let response = server.get_outgoing_edges(GetOutgoingEdgesRequest {
node_id: n1,
label: Some("KNOWS".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(1)));
}
#[test]
fn test_get_incoming_edges() {
let server = create_test_server();
let (n1, n2) = create_two_nodes(&server);
let node3 = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n3: NodeResponse = parse_response(&node3).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1,
target_id: n2,
label: "KNOWS".to_string(),
properties: None,
});
server.create_edge(CreateEdgeRequest {
source_id: n3.id,
target_id: n2,
label: "KNOWS".to_string(),
properties: None,
});
let response = server.get_incoming_edges(GetIncomingEdgesRequest {
node_id: n2,
label: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(2)));
}
}
mod traversal_tests {
use super::*;
fn create_graph(server: &AletheiaMcpServer) -> Vec<u64> {
let nodes: Vec<u64> = (0..4)
.map(|i| {
let response = server.create_node(CreateNodeRequest {
label: "Node".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!(format!("Node{}", i)));
m
}),
});
let node: NodeResponse = parse_response(&response).unwrap();
node.id
})
.collect();
for i in 0..3 {
server.create_edge(CreateEdgeRequest {
source_id: nodes[i],
target_id: nodes[i + 1],
label: "NEXT".to_string(),
properties: None,
});
}
nodes
}
#[test]
fn test_traverse_single_hop() {
let server = create_test_server();
let nodes = create_graph(&server);
let response = server.traverse(TraverseRequest {
start_node_id: nodes[0],
edge_label: "NEXT".to_string(),
direction: Some("outgoing".to_string()),
depth: Some(1),
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(count >= 1, "Should find at least one node in traversal");
}
#[test]
fn test_traverse_multi_hop() {
let server = create_test_server();
let nodes = create_graph(&server);
let response = server.traverse(TraverseRequest {
start_node_id: nodes[0],
edge_label: "NEXT".to_string(),
direction: Some("outgoing".to_string()),
depth: Some(3),
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(count >= 1, "Should find nodes in multi-hop traversal");
}
#[test]
fn test_traverse_incoming() {
let server = create_test_server();
let nodes = create_graph(&server);
let response = server.traverse(TraverseRequest {
start_node_id: nodes[3],
edge_label: "NEXT".to_string(),
direction: Some("incoming".to_string()),
depth: Some(1),
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(count >= 1, "Should find incoming node");
}
#[test]
fn test_traverse_with_limit() {
let server = create_test_server();
let nodes = create_graph(&server);
let response = server.traverse(TraverseRequest {
start_node_id: nodes[0],
edge_label: "NEXT".to_string(),
direction: None,
depth: Some(3),
limit: Some(2),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(count <= 2, "Should respect limit");
}
}
mod vector_tests {
use super::*;
#[test]
fn test_enable_vector_index() {
let server = create_test_server();
let response = server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding".to_string(),
dimensions: 128,
distance_metric: Some("cosine".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("success"), Some(&serde_json::json!(true)));
}
#[test]
fn test_list_vector_indexes() {
let server = create_test_server();
server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding".to_string(),
dimensions: 128,
distance_metric: None,
});
let response = server.list_vector_indexes(ListVectorIndexesRequest {});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let indexes = value.get("indexes").unwrap().as_array().unwrap();
assert!(indexes.iter().any(|i| {
i.get("property_name")
.and_then(|p| p.as_str())
.map(|p| p == "embedding")
.unwrap_or(false)
}));
}
#[test]
fn test_find_similar_without_index() {
let server = create_test_server();
let response = server.find_similar(FindSimilarRequest {
property_name: "embedding".to_string(),
embedding: vec![0.1, 0.2, 0.3, 0.4],
k: Some(5),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some(), "Should error without index");
}
#[test]
fn test_find_similar_with_index() {
let server = create_test_server();
server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding".to_string(),
dimensions: 4,
distance_metric: Some("cosine".to_string()),
});
for i in 0..5 {
let mut props = HashMap::new();
props.insert("name".to_string(), serde_json::json!(format!("Doc{}", i)));
props.insert(
"embedding".to_string(),
serde_json::json!([
(i as f32) * 0.1,
(i as f32) * 0.2,
(i as f32) * 0.3,
(i as f32) * 0.4
]),
);
server.create_node(CreateNodeRequest {
label: "Document".to_string(),
properties: Some(props),
});
}
let response = server.find_similar(FindSimilarRequest {
property_name: "embedding".to_string(),
embedding: vec![0.1, 0.2, 0.3, 0.4],
k: Some(3),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("results").is_some() || value.get("error").is_some());
}
}
mod temporal_tests {
use super::*;
#[test]
fn test_get_node_at_time_invalid_timestamp() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: "invalid-timestamp".to_string(),
transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_get_node_at_time_valid_timestamp() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Alice"));
m
}),
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let now_micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as i64;
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: now_micros.to_string(),
transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("node").is_some() || value.get("error").is_some());
}
#[test]
fn test_get_edge_at_time() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
let edge_response = server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "KNOWS".to_string(),
properties: None,
});
let edge: EdgeResponse = parse_response(&edge_response).unwrap();
let now_micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as i64;
let response = server.get_edge_at_time(GetEdgeAtTimeRequest {
edge_id: edge.id,
valid_time: now_micros.to_string(),
transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("edge").is_some() || value.get("error").is_some());
}
}
mod hybrid_tests {
use super::*;
#[test]
fn test_hybrid_query_with_start_node() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Alice"));
m
}),
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Bob"));
m
}),
});
let _n2: NodeResponse = parse_response(&n2_response).unwrap();
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: Some(n1.id),
traverse_edge: None,
traverse_depth: None,
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: None,
transaction_time: None,
filter_label: None,
limit: Some(10),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("results").is_some() || value.get("error").is_some());
}
#[test]
fn test_hybrid_query_with_label_filter() {
let server = create_test_server();
for _ in 0..3 {
server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
}
for _ in 0..2 {
server.create_node(CreateNodeRequest {
label: "Document".to_string(),
properties: None,
});
}
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: None,
traverse_edge: None,
traverse_depth: None,
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: None,
transaction_time: None,
filter_label: Some("Person".to_string()),
limit: Some(100),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if let Some(results) = value.get("results").and_then(|r| r.as_array()) {
for result in results {
let label = result
.get("node")
.and_then(|n| n.get("label"))
.and_then(|l| l.as_str());
assert_eq!(label, Some("Person"));
}
}
}
#[test]
fn test_hybrid_query_requires_criteria() {
let server = create_test_server();
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: None,
traverse_edge: None,
traverse_depth: None,
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: None,
transaction_time: None,
filter_label: None,
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_hybrid_query_with_traversal() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "KNOWS".to_string(),
properties: None,
});
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: Some(n1.id),
traverse_edge: Some("KNOWS".to_string()),
traverse_depth: Some(1),
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: None,
transaction_time: None,
filter_label: None,
limit: Some(10),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("results").is_some() || value.get("error").is_some());
}
}
mod conversion_tests {
use super::*;
#[test]
fn test_property_types_roundtrip() {
let server = create_test_server();
let mut props = HashMap::new();
props.insert("string_val".to_string(), serde_json::json!("hello"));
props.insert("int_val".to_string(), serde_json::json!(42));
props.insert("float_val".to_string(), serde_json::json!(1.5));
props.insert("bool_val".to_string(), serde_json::json!(true));
props.insert("null_val".to_string(), serde_json::Value::Null);
props.insert("array_val".to_string(), serde_json::json!([1, 2, 3]));
let response = server.create_node(CreateNodeRequest {
label: "Test".to_string(),
properties: Some(props),
});
let node: NodeResponse = parse_response(&response).expect("Failed to create node");
assert_eq!(
node.properties.get("string_val"),
Some(&serde_json::json!("hello"))
);
assert_eq!(node.properties.get("int_val"), Some(&serde_json::json!(42)));
assert_eq!(
node.properties.get("bool_val"),
Some(&serde_json::json!(true))
);
assert_eq!(
node.properties.get("null_val"),
Some(&serde_json::Value::Null)
);
}
#[test]
fn test_vector_property() {
let server = create_test_server();
let mut props = HashMap::new();
props.insert(
"embedding".to_string(),
serde_json::json!([0.1, 0.2, 0.3, 0.4]),
);
let response = server.create_node(CreateNodeRequest {
label: "Document".to_string(),
properties: Some(props),
});
let node: NodeResponse = parse_response(&response).expect("Failed to create node");
let embedding = node.properties.get("embedding").unwrap();
assert!(embedding.is_array());
}
}
mod coverage_tests {
use super::*;
#[test]
fn test_vector_dimension_validation() {
let server = create_test_server();
let response = server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding".to_string(),
dimensions: 4,
distance_metric: Some("cosine".to_string()),
});
assert!(
!response.contains("error"),
"Failed to enable index: {}",
response
);
let response = server.find_similar(FindSimilarRequest {
property_name: "embedding".to_string(),
embedding: vec![0.1, 0.2, 0.3], k: Some(5),
});
assert!(response.contains("error"), "Expected error response");
assert!(
response.contains("dimension mismatch") || response.contains("Embedding dimension"),
"Expected dimension mismatch error, got: {}",
response
);
}
#[test]
fn test_hybrid_query_dimension_validation() {
let server = create_test_server();
let response = server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding".to_string(),
dimensions: 4,
distance_metric: Some("cosine".to_string()),
});
assert!(
!response.contains("error"),
"Failed to enable index: {}",
response
);
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: None,
traverse_edge: None,
traverse_depth: None,
query_embedding: Some(vec![0.1, 0.2, 0.3]), vector_property: Some("embedding".to_string()),
top_k: Some(5),
filter_label: None,
limit: None,
valid_time: None,
transaction_time: None,
});
assert!(response.contains("error"), "Expected error response");
assert!(
response.contains("dimension mismatch") || response.contains("Embedding dimension"),
"Expected dimension mismatch error, got: {}",
response
);
}
#[test]
fn test_iso8601_timestamp_parsing() {
let server = create_test_server();
let response = server.create_node(CreateNodeRequest {
label: "Event".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&response).expect("Failed to create node");
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: "2024-01-15T10:30:00Z".to_string(),
transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if let Some(error) = value.get("error") {
let err_str = error.as_str().unwrap_or("");
assert!(
!err_str.contains("Invalid timestamp format"),
"ISO 8601 timestamp should be parsed correctly, got error: {}",
err_str
);
}
}
#[test]
fn test_iso8601_timestamp_without_timezone() {
let server = create_test_server();
let response = server.create_node(CreateNodeRequest {
label: "Event".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&response).expect("Failed to create node");
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: "2024-01-15T10:30:00".to_string(),
transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if let Some(error) = value.get("error") {
let err_str = error.as_str().unwrap_or("");
assert!(
!err_str.contains("Invalid timestamp format"),
"ISO 8601 timestamp without TZ should be parsed correctly, got error: {}",
err_str
);
}
}
#[test]
fn test_transaction_time_now_response() {
let server = create_test_server();
let response = server.create_node(CreateNodeRequest {
label: "Event".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&response).expect("Failed to create node");
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: "0".to_string(), transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if value.get("error").is_none() {
let tx_time = value.get("transaction_time");
assert_eq!(
tx_time,
Some(&serde_json::json!("now")),
"Expected 'now' for unspecified transaction_time"
);
}
}
#[test]
fn test_count_nodes_with_nonexistent_label() {
let server = create_test_server();
let response = server.count_nodes(CountNodesRequest {
label: Some("NonexistentLabel".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("error").is_none(),
"Should not error for nonexistent label"
);
assert_eq!(
value.get("count"),
Some(&serde_json::json!(0)),
"Count should be 0 for nonexistent label"
);
}
#[test]
fn test_list_nodes_offset_cap() {
let server = create_test_server();
for i in 0..5 {
server.create_node(CreateNodeRequest {
label: "OffsetTest".to_string(),
properties: Some({
let mut props = HashMap::new();
props.insert("index".to_string(), serde_json::json!(i));
props
}),
});
}
let response = server.list_nodes(ListNodesRequest {
label: Some("OffsetTest".to_string()),
property_key: None,
property_value: None,
limit: Some(10),
offset: Some(100_000), });
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("error").is_none(),
"Large offset should not cause error: {}",
response
);
}
#[test]
fn test_traversal_depth_limit() {
let server = create_test_server();
let mut prev_id: Option<u64> = None;
for i in 0..5 {
let response = server.create_node(CreateNodeRequest {
label: "ChainNode".to_string(),
properties: Some({
let mut props = HashMap::new();
props.insert("level".to_string(), serde_json::json!(i));
props
}),
});
let node: NodeResponse = parse_response(&response).unwrap();
if let Some(source_id) = prev_id {
server.create_edge(CreateEdgeRequest {
source_id,
target_id: node.id,
label: "NEXT".to_string(),
properties: None,
});
}
prev_id = Some(node.id);
}
let response = server.traverse(TraverseRequest {
start_node_id: 0,
edge_label: "NEXT".to_string(),
depth: Some(100), direction: Some("outgoing".to_string()),
limit: Some(50),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("error").is_none(),
"Large depth should be capped, not error: {}",
response
);
}
}
mod server_handler_tests {
use super::*;
use rmcp::ServerHandler;
#[test]
fn test_get_info() {
let server = create_test_server();
let info = server.get_info();
assert!(info.instructions.is_some());
let instructions = info.instructions.unwrap();
assert!(instructions.contains("AletheiaDB"));
assert!(instructions.contains("bi-temporal"));
}
#[test]
fn test_get_info_instructions_content() {
let server = create_test_server();
let info = server.get_info();
let instructions = info.instructions.expect("Instructions should be set");
assert!(instructions.contains("graph"));
assert!(instructions.contains("temporal") || instructions.contains("Temporal"));
assert!(instructions.contains("vector") || instructions.contains("Vector"));
}
}
mod error_handling_tests {
use super::*;
#[test]
fn test_update_node_nonexistent() {
let server = create_test_server();
let req = UpdateNodeRequest {
node_id: 999999,
properties: HashMap::new(),
};
let response = server.update_node(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_delete_node_nonexistent() {
let server = create_test_server();
let req = DeleteNodeRequest { node_id: 999999 };
let response = server.delete_node(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_get_edge_nonexistent() {
let server = create_test_server();
let req = GetEdgeRequest { edge_id: 999999 };
let response = server.get_edge(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_update_edge_nonexistent() {
let server = create_test_server();
let req = UpdateEdgeRequest {
edge_id: 999999,
properties: HashMap::new(),
};
let response = server.update_edge(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_delete_edge_nonexistent() {
let server = create_test_server();
let req = DeleteEdgeRequest { edge_id: 999999 };
let response = server.delete_edge(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_create_edge_invalid_source() {
let server = create_test_server();
let target_resp = server.create_node(CreateNodeRequest {
label: "Target".to_string(),
properties: None,
});
let target: NodeResponse = parse_response(&target_resp).unwrap();
let req = CreateEdgeRequest {
source_id: 999999,
target_id: target.id,
label: "KNOWS".to_string(),
properties: None,
};
let response = server.create_edge(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_create_edge_invalid_target() {
let server = create_test_server();
let source_resp = server.create_node(CreateNodeRequest {
label: "Source".to_string(),
properties: None,
});
let source: NodeResponse = parse_response(&source_resp).unwrap();
let req = CreateEdgeRequest {
source_id: source.id,
target_id: 999999,
label: "KNOWS".to_string(),
properties: None,
};
let response = server.create_edge(req);
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
}
mod vector_distance_tests {
use super::*;
#[test]
fn test_enable_vector_index_euclidean() {
let server = create_test_server();
let response = server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding_euclidean".to_string(),
dimensions: 64,
distance_metric: Some("euclidean".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("success"), Some(&serde_json::json!(true)));
assert_eq!(
value.get("distance_metric"),
Some(&serde_json::json!("euclidean"))
);
}
#[test]
fn test_enable_vector_index_dot_product() {
let server = create_test_server();
let response = server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding_dot".to_string(),
dimensions: 64,
distance_metric: Some("dot".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("success"), Some(&serde_json::json!(true)));
assert_eq!(
value.get("distance_metric"),
Some(&serde_json::json!("dot"))
);
}
#[test]
fn test_enable_vector_index_dot_product_alias() {
let server = create_test_server();
let response = server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding_dotprod".to_string(),
dimensions: 64,
distance_metric: Some("dot_product".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("success"), Some(&serde_json::json!(true)));
}
#[test]
fn test_enable_vector_index_unknown_metric_defaults_to_cosine() {
let server = create_test_server();
let response = server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding_unknown".to_string(),
dimensions: 64,
distance_metric: Some("unknown_metric".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("success"), Some(&serde_json::json!(true)));
}
#[test]
fn test_find_similar_k_capping() {
let server = create_test_server();
server.enable_vector_index(EnableVectorIndexRequest {
property_name: "embedding".to_string(),
dimensions: 4,
distance_metric: None,
});
let response = server.find_similar(FindSimilarRequest {
property_name: "embedding".to_string(),
embedding: vec![0.1, 0.2, 0.3, 0.4],
k: Some(10000), });
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("results").is_some(),
"Should handle large k gracefully without an error, but got: {:?}",
value.get("error")
);
}
}
mod temporal_extended_tests {
use super::*;
#[test]
fn test_get_node_at_time_with_transaction_time() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Event".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("title".to_string(), serde_json::json!("Conference"));
m
}),
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let now_micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as i64;
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: now_micros.to_string(),
transaction_time: Some(now_micros.to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if value.get("error").is_none() {
assert!(
value.get("transaction_time").is_some(),
"Should include transaction_time in response"
);
assert_ne!(
value.get("transaction_time"),
Some(&serde_json::json!("now"))
);
}
}
#[test]
fn test_get_edge_at_time_with_transaction_time() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
let edge_response = server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "KNOWS".to_string(),
properties: None,
});
let edge: EdgeResponse = parse_response(&edge_response).unwrap();
let now_micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as i64;
let response = server.get_edge_at_time(GetEdgeAtTimeRequest {
edge_id: edge.id,
valid_time: now_micros.to_string(),
transaction_time: Some(now_micros.to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if value.get("error").is_none() {
assert!(value.get("edge").is_some());
assert!(value.get("transaction_time").is_some());
}
}
#[test]
fn test_get_node_at_time_invalid_transaction_time() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Test".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: "0".to_string(),
transaction_time: Some("not-a-valid-timestamp".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
let error_str = value["error"].as_str().unwrap();
assert!(
error_str.contains("Invalid timestamp format"),
"Should report invalid timestamp: {}",
error_str
);
}
#[test]
fn test_get_edge_at_time_invalid_valid_time() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
let edge_response = server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "KNOWS".to_string(),
properties: None,
});
let edge: EdgeResponse = parse_response(&edge_response).unwrap();
let response = server.get_edge_at_time(GetEdgeAtTimeRequest {
edge_id: edge.id,
valid_time: "invalid-time".to_string(),
transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
}
#[test]
fn test_iso8601_with_offset_timezone() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Event".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let response = server.get_node_at_time(GetNodeAtTimeRequest {
node_id: node.id,
valid_time: "2024-01-15T10:30:00+00:00".to_string(),
transaction_time: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if let Some(error) = value.get("error") {
let err_str = error.as_str().unwrap_or("");
assert!(
!err_str.contains("Invalid timestamp format"),
"Offset timezone should be parsed correctly, got error: {}",
err_str
);
}
}
}
mod traversal_extended_tests {
use super::*;
fn create_bidirectional_graph(server: &AletheiaMcpServer) -> Vec<u64> {
let nodes: Vec<u64> = (0..3)
.map(|i| {
let response = server.create_node(CreateNodeRequest {
label: "BiNode".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!(format!("Node{}", i)));
m
}),
});
let node: NodeResponse = parse_response(&response).unwrap();
node.id
})
.collect();
for i in 0..2 {
server.create_edge(CreateEdgeRequest {
source_id: nodes[i],
target_id: nodes[i + 1],
label: "CONNECTED".to_string(),
properties: None,
});
server.create_edge(CreateEdgeRequest {
source_id: nodes[i + 1],
target_id: nodes[i],
label: "CONNECTED".to_string(),
properties: None,
});
}
nodes
}
#[test]
fn test_traverse_bidirectional() {
let server = create_test_server();
let nodes = create_bidirectional_graph(&server);
let response = server.traverse(TraverseRequest {
start_node_id: nodes[1], edge_label: "CONNECTED".to_string(),
direction: Some("both".to_string()),
depth: Some(1),
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(
count >= 2,
"Bidirectional traversal should find both neighbors"
);
}
#[test]
fn test_traverse_nonexistent_edge_label() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Lonely".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let response = server.traverse(TraverseRequest {
start_node_id: node.id,
edge_label: "NONEXISTENT".to_string(),
direction: None,
depth: Some(3),
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_none());
assert_eq!(value.get("count"), Some(&serde_json::json!(0)));
}
#[test]
fn test_traverse_default_direction() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Node".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Node".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "NEXT".to_string(),
properties: None,
});
let response = server.traverse(TraverseRequest {
start_node_id: n1.id,
edge_label: "NEXT".to_string(),
direction: None, depth: Some(1),
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(count >= 1, "Default direction should find outgoing nodes");
}
#[test]
fn test_traverse_result_limit() {
let server = create_test_server();
let center_response = server.create_node(CreateNodeRequest {
label: "Center".to_string(),
properties: None,
});
let center: NodeResponse = parse_response(¢er_response).unwrap();
for i in 0..10 {
let spoke_response = server.create_node(CreateNodeRequest {
label: "Spoke".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("index".to_string(), serde_json::json!(i));
m
}),
});
let spoke: NodeResponse = parse_response(&spoke_response).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: center.id,
target_id: spoke.id,
label: "SPOKE".to_string(),
properties: None,
});
}
let response = server.traverse(TraverseRequest {
start_node_id: center.id,
edge_label: "SPOKE".to_string(),
direction: Some("outgoing".to_string()),
depth: Some(1),
limit: Some(5),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(count <= 5, "Traversal should respect limit");
}
}
mod hybrid_extended_tests {
use super::*;
#[test]
fn test_hybrid_query_with_temporal() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Alice"));
m
}),
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let now_micros = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as i64;
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: Some(node.id),
traverse_edge: None,
traverse_depth: None,
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: Some(now_micros.to_string()),
transaction_time: Some(now_micros.to_string()),
filter_label: None,
limit: Some(10),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if value.get("error").is_none() {
assert!(
value.get("temporal_query").is_some() && value.get("results").is_some(),
"A successful temporal hybrid query should return both 'temporal_query' and 'results' fields."
);
}
}
#[test]
fn test_hybrid_query_multi_depth_traversal() {
let server = create_test_server();
let nodes: Vec<u64> = (0..4)
.map(|i| {
let response = server.create_node(CreateNodeRequest {
label: "ChainNode".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("level".to_string(), serde_json::json!(i));
m
}),
});
let node: NodeResponse = parse_response(&response).unwrap();
node.id
})
.collect();
for i in 0..3 {
server.create_edge(CreateEdgeRequest {
source_id: nodes[i],
target_id: nodes[i + 1],
label: "CHAIN".to_string(),
properties: None,
});
}
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: Some(nodes[0]),
traverse_edge: Some("CHAIN".to_string()),
traverse_depth: Some(3),
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: None,
transaction_time: None,
filter_label: None,
limit: Some(10),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
if value.get("error").is_none() {
let count = value.get("count").and_then(|c| c.as_u64()).unwrap_or(0);
assert!(count >= 1, "Multi-depth traversal should find nodes");
}
}
#[test]
fn test_hybrid_query_invalid_valid_time() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Test".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: Some(node.id),
traverse_edge: None,
traverse_depth: None,
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: Some("invalid-time".to_string()),
transaction_time: None,
filter_label: None,
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
let error = value["error"].as_str().unwrap();
assert!(
error.contains("Invalid valid_time"),
"Should report invalid valid_time"
);
}
#[test]
fn test_hybrid_query_invalid_transaction_time() {
let server = create_test_server();
let node_response = server.create_node(CreateNodeRequest {
label: "Test".to_string(),
properties: None,
});
let node: NodeResponse = parse_response(&node_response).unwrap();
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: Some(node.id),
traverse_edge: None,
traverse_depth: None,
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: Some("0".to_string()),
transaction_time: Some("bad-tx-time".to_string()),
filter_label: None,
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
let error = value["error"].as_str().unwrap();
assert!(
error.contains("Invalid transaction_time"),
"Should report invalid transaction_time"
);
}
#[test]
fn test_hybrid_query_limit_capping() {
let server = create_test_server();
for i in 0..5 {
server.create_node(CreateNodeRequest {
label: "LimitTest".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("index".to_string(), serde_json::json!(i));
m
}),
});
}
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: None,
traverse_edge: None,
traverse_depth: None,
vector_property: None,
query_embedding: None,
top_k: None,
valid_time: None,
transaction_time: None,
filter_label: Some("LimitTest".to_string()),
limit: Some(100000), });
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("results").is_some(),
"Large limit should be capped, not error"
);
}
#[test]
fn test_hybrid_query_vector_without_index() {
let server = create_test_server();
let response = server.hybrid_query(HybridQueryRequest {
start_node_id: None,
traverse_edge: None,
traverse_depth: None,
vector_property: Some("embedding".to_string()),
query_embedding: Some(vec![0.1, 0.2, 0.3, 0.4]),
top_k: Some(5),
valid_time: None,
transaction_time: None,
filter_label: None,
limit: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("error").is_some());
let error = value["error"].as_str().unwrap();
assert!(
error.contains("Vector index not enabled"),
"Should report missing vector index"
);
}
}
mod edge_extended_tests {
use super::*;
#[test]
fn test_list_edges_with_label() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "KNOWS".to_string(),
properties: None,
});
server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "WORKS_WITH".to_string(),
properties: None,
});
let response = server.list_edges(ListEdgesRequest {
label: Some("KNOWS".to_string()),
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("label_filter").is_some());
assert_eq!(value.get("label_filter"), Some(&serde_json::json!("KNOWS")));
}
#[test]
fn test_count_edges_with_label() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "KNOWS".to_string(),
properties: None,
});
let response = server.count_edges(CountEdgesRequest {
label: Some("KNOWS".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("message").is_some());
assert!(
value
.get("message")
.unwrap()
.as_str()
.unwrap()
.contains("not supported")
);
}
#[test]
fn test_get_incoming_edges_with_label() {
let server = create_test_server();
let n1_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n1: NodeResponse = parse_response(&n1_response).unwrap();
let n2_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n2: NodeResponse = parse_response(&n2_response).unwrap();
let n3_response = server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: None,
});
let n3: NodeResponse = parse_response(&n3_response).unwrap();
server.create_edge(CreateEdgeRequest {
source_id: n1.id,
target_id: n2.id,
label: "KNOWS".to_string(),
properties: None,
});
server.create_edge(CreateEdgeRequest {
source_id: n3.id,
target_id: n2.id,
label: "WORKS_WITH".to_string(),
properties: None,
});
let response = server.get_incoming_edges(GetIncomingEdgesRequest {
node_id: n2.id,
label: Some("KNOWS".to_string()),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(value.get("count"), Some(&serde_json::json!(1)));
let edges = value.get("edges").unwrap().as_array().unwrap();
assert_eq!(edges.len(), 1);
assert_eq!(edges[0].get("label"), Some(&serde_json::json!("KNOWS")));
}
}
mod list_nodes_extended_tests {
use super::*;
#[test]
fn test_list_nodes_without_label() {
let server = create_test_server();
for i in 0..3 {
server.create_node(CreateNodeRequest {
label: format!("Type{}", i),
properties: None,
});
}
let response = server.list_nodes(ListNodesRequest {
label: None,
property_key: None,
property_value: None,
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(value.get("message").is_some());
assert!(value.get("total_count").is_some());
assert_eq!(value.get("total_count"), Some(&serde_json::json!(3)));
assert_eq!(value.get("count"), Some(&serde_json::json!(0)));
}
#[test]
fn test_list_nodes_limit_capping() {
let server = create_test_server();
for i in 0..5 {
server.create_node(CreateNodeRequest {
label: "LimitCap".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("index".to_string(), serde_json::json!(i));
m
}),
});
}
let response = server.list_nodes(ListNodesRequest {
label: Some("LimitCap".to_string()),
property_key: None,
property_value: None,
limit: Some(100000), offset: Some(0),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("nodes").is_some(),
"Large limit should be capped, not error"
);
}
#[test]
fn test_list_nodes_by_property_string() {
let server = create_test_server();
server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Alice"));
m
}),
});
server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Bob"));
m
}),
});
server.create_node(CreateNodeRequest {
label: "Person".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("name".to_string(), serde_json::json!("Alice"));
m
}),
});
let response = server.list_nodes(ListNodesRequest {
label: Some("Person".to_string()),
property_key: Some("name".to_string()),
property_value: Some(serde_json::json!("Alice")),
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let nodes = value["nodes"].as_array().unwrap();
assert_eq!(nodes.len(), 2, "Should find exactly 2 Alice nodes");
}
#[test]
fn test_list_nodes_by_property_int() {
let server = create_test_server();
server.create_node(CreateNodeRequest {
label: "Sensor".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("reading".to_string(), serde_json::json!(42));
m
}),
});
server.create_node(CreateNodeRequest {
label: "Sensor".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("reading".to_string(), serde_json::json!(99));
m
}),
});
let response = server.list_nodes(ListNodesRequest {
label: Some("Sensor".to_string()),
property_key: Some("reading".to_string()),
property_value: Some(serde_json::json!(42)),
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let nodes = value["nodes"].as_array().unwrap();
assert_eq!(
nodes.len(),
1,
"Should find exactly 1 sensor with reading=42"
);
}
#[test]
fn test_list_nodes_by_property_no_match() {
let server = create_test_server();
server.create_node(CreateNodeRequest {
label: "Item".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("color".to_string(), serde_json::json!("red"));
m
}),
});
let response = server.list_nodes(ListNodesRequest {
label: Some("Item".to_string()),
property_key: Some("color".to_string()),
property_value: Some(serde_json::json!("blue")),
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let nodes = value["nodes"].as_array().unwrap();
assert_eq!(nodes.len(), 0, "Should find no matching nodes");
}
#[test]
fn test_list_nodes_by_property_missing_label() {
let server = create_test_server();
let response = server.list_nodes(ListNodesRequest {
label: None,
property_key: Some("name".to_string()),
property_value: Some(serde_json::json!("Alice")),
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("error").is_some(),
"Should error when label is missing for property filter"
);
}
#[test]
fn test_list_nodes_by_property_key_without_value() {
let server = create_test_server();
let response = server.list_nodes(ListNodesRequest {
label: Some("Person".to_string()),
property_key: Some("name".to_string()),
property_value: None,
limit: None,
offset: None,
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
assert!(
value.get("error").is_some(),
"Should error when property_value is missing"
);
}
#[test]
fn test_list_nodes_by_property_with_pagination() {
let server = create_test_server();
for _ in 0..5 {
server.create_node(CreateNodeRequest {
label: "Widget".to_string(),
properties: Some({
let mut m = HashMap::new();
m.insert("status".to_string(), serde_json::json!("active"));
m
}),
});
}
let response = server.list_nodes(ListNodesRequest {
label: Some("Widget".to_string()),
property_key: Some("status".to_string()),
property_value: Some(serde_json::json!("active")),
limit: Some(2),
offset: Some(0),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let nodes = value["nodes"].as_array().unwrap();
assert_eq!(nodes.len(), 2, "Page 1 should have 2 nodes");
let response = server.list_nodes(ListNodesRequest {
label: Some("Widget".to_string()),
property_key: Some("status".to_string()),
property_value: Some(serde_json::json!("active")),
limit: Some(2),
offset: Some(2),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let nodes = value["nodes"].as_array().unwrap();
assert_eq!(nodes.len(), 2, "Page 2 should have 2 nodes");
let response = server.list_nodes(ListNodesRequest {
label: Some("Widget".to_string()),
property_key: Some("status".to_string()),
property_value: Some(serde_json::json!("active")),
limit: Some(2),
offset: Some(4),
});
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
let nodes = value["nodes"].as_array().unwrap();
assert_eq!(nodes.len(), 1, "Page 3 should have 1 node");
}
}