#[cfg(feature = "embeddings")]
mod embedding_graph_api_tests {
use nopaldb::types::{Edge, Node, PropertyValue};
use nopaldb::Graph;
async fn setup_graph_with_embeddings() -> Graph {
let graph = Graph::in_memory().await.unwrap();
let alice = Node::new("Article")
.with_property("title", PropertyValue::String("Graph Databases".into()))
.with_property("score", PropertyValue::Float(0.95));
let bob = Node::new("Article")
.with_property("title", PropertyValue::String("Machine Learning".into()))
.with_property("score", PropertyValue::Float(0.87));
let carol = Node::new("Article")
.with_property("title", PropertyValue::String("Embedded Systems".into()))
.with_property("score", PropertyValue::Float(0.72));
for n in [&alice, &bob, &carol] {
graph.add_node(n.clone()).await.unwrap();
}
graph.add_edge(Edge::new(alice.id, bob.id, "CITES")).await.unwrap();
graph.add_node_embedding(alice.id, vec![1.0, 0.0, 0.0], "minilm").await.unwrap();
graph.add_node_embedding(bob.id, vec![0.9, 0.1, 0.0], "minilm").await.unwrap();
graph.add_node_embedding(carol.id, vec![0.0, 0.0, 1.0], "minilm").await.unwrap();
graph
}
#[tokio::test]
async fn test_add_and_get_node_embedding() {
let graph = setup_graph_with_embeddings().await;
let node = Node::new("Test");
let node_id = graph.add_node(node).await.unwrap();
graph.add_node_embedding(node_id, vec![0.5, 0.5, 0.5], "test-model").await.unwrap();
let emb = graph.get_node_embedding(node_id, "test-model").await.unwrap();
assert_eq!(emb.vector, vec![0.5, 0.5, 0.5]);
assert_eq!(emb.model, "test-model");
}
#[tokio::test]
async fn test_get_embedding_wrong_model_errors() {
let graph = setup_graph_with_embeddings().await;
let node = Node::new("Test");
let node_id = graph.add_node(node).await.unwrap();
graph.add_node_embedding(node_id, vec![1.0, 0.0], "modelA").await.unwrap();
let result = graph.get_node_embedding(node_id, "modelB").await;
assert!(result.is_err(), "Se esperaba error para modelo inexistente");
}
#[cfg(feature = "embeddings-index")]
#[tokio::test]
async fn test_knn_nodes_returns_nearest() {
let graph = setup_graph_with_embeddings().await;
let idx = graph.build_embedding_index("minilm").await.unwrap();
let results = idx.search_knn(&[0.95, 0.05, 0.0], 2).unwrap();
assert_eq!(results.len(), 2, "Debe retornar 2 vecinos");
assert!(results[0].1 <= results[1].1, "Resultados deben estar ordenados por distancia");
}
#[cfg(feature = "embeddings-index")]
#[tokio::test]
async fn test_knn_returns_at_most_k() {
let graph = setup_graph_with_embeddings().await;
let idx = graph.build_embedding_index("minilm").await.unwrap();
let results = idx.search_knn(&[1.0, 0.0, 0.0], 10).unwrap();
assert!(results.len() <= 3, "No puede retornar más nodos de los que hay");
assert!(!results.is_empty(), "Debe retornar al menos 1 resultado");
}
}
#[cfg(all(feature = "embeddings", feature = "ml"))]
mod to_pyg_with_embeddings_tests {
use nopaldb::types::{Edge, Node, PropertyValue};
use nopaldb::Graph;
use nopaldb::ml::pyg::PyGData;
async fn setup() -> Graph {
let graph = Graph::in_memory().await.unwrap();
let a = Node::new("Item").with_property("value", PropertyValue::Float(1.0));
let b = Node::new("Item").with_property("value", PropertyValue::Float(2.0));
let c = Node::new("Item").with_property("value", PropertyValue::Float(3.0));
for n in [&a, &b, &c] {
graph.add_node(n.clone()).await.unwrap();
}
graph.add_edge(Edge::new(a.id, b.id, "LINK")).await.unwrap();
graph.add_edge(Edge::new(b.id, c.id, "LINK")).await.unwrap();
graph.add_node_embedding(a.id, vec![1.0, 0.0, 0.0, 0.0], "bert").await.unwrap();
graph.add_node_embedding(b.id, vec![0.0, 1.0, 0.0, 0.0], "bert").await.unwrap();
graph
}
#[tokio::test]
async fn test_from_graph_no_embeddings() {
let graph = setup().await;
let data = PyGData::from_graph(&graph, "Item", None).await.unwrap();
assert_eq!(data.num_nodes, 3);
assert_eq!(data.num_edges, 2);
}
#[tokio::test]
async fn test_from_graph_with_embeddings_adds_tensor() {
let graph = setup().await;
let data = PyGData::from_graph_with_embeddings(&graph, "Item", None, Some("bert"))
.await.unwrap();
assert_eq!(data.num_nodes, 3);
let emb_tensor = data.x.last().expect("Debe haber tensor de embeddings");
assert_eq!(emb_tensor.shape, vec![3, 4]);
assert_eq!(emb_tensor.data.len(), 3 * 4 * 4);
}
#[tokio::test]
async fn test_from_graph_with_unknown_model_returns_base() {
let graph = setup().await;
let base = PyGData::from_graph(&graph, "Item", None).await.unwrap();
let with_emb = PyGData::from_graph_with_embeddings(&graph, "Item", None, Some("nonexistent"))
.await.unwrap();
assert_eq!(base.x.len(), with_emb.x.len(),
"Con modelo inexistente no se debe agregar tensor de embeddings");
}
#[tokio::test]
async fn test_from_graph_no_embedding_model_returns_base() {
let graph = setup().await;
let base = PyGData::from_graph(&graph, "Item", None).await.unwrap();
let same = PyGData::from_graph_with_embeddings(&graph, "Item", None, None)
.await.unwrap();
assert_eq!(base.x.len(), same.x.len());
assert_eq!(base.num_nodes, same.num_nodes);
}
}