#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code — panics are acceptable failures"
)]
#![cfg(feature = "testing")]
use std::sync::Arc;
use cognee_cognify::memify::{MemifyConfig, memify};
use cognee_core::{CpuPool, RayonThreadPool};
use cognee_database::{DatabaseConnection, SearchHistoryDb, connect, initialize};
use cognee_embedding::EmbeddingEngine;
use cognee_graph::{GraphDBTrait, LadybugAdapter};
use cognee_llm::Llm;
use cognee_search::{
SearchBuilder, SearchRequest, SearchType,
types::{SearchOutput, SearchResponse},
};
use cognee_test_utils::{MockLlm, MockVectorDB};
use cognee_vector::VectorDB;
use serde_json::json;
use tempfile::TempDir;
use uuid::Uuid;
async fn add_node(graph_db: &dyn GraphDBTrait, id: Uuid, name: &str, description: &str) {
let mut node_json = serde_json::Map::new();
node_json.insert("id".to_string(), json!(id.to_string()));
node_json.insert("name".to_string(), json!(name));
node_json.insert("description".to_string(), json!(description));
graph_db
.add_node_raw(serde_json::Value::Object(node_json))
.await
.expect("add_node_raw must succeed on a fresh LadybugAdapter");
}
async fn add_edge(graph_db: &dyn GraphDBTrait, source: Uuid, target: Uuid, relationship: &str) {
graph_db
.add_edge(&source.to_string(), &target.to_string(), relationship, None)
.await
.expect("add_edge must succeed on a fresh LadybugAdapter");
}
fn make_request(query: &str, search_type: SearchType) -> SearchRequest {
SearchRequest {
query_text: query.to_string(),
search_type,
top_k: Some(10),
datasets: None,
dataset_ids: None,
system_prompt: None,
system_prompt_path: None,
only_context: Some(true),
use_combined_context: None,
session_id: None,
node_type: None,
node_name: None,
node_name_filter_operator: None,
wide_search_top_k: None,
triplet_distance_penalty: None,
save_interaction: Some(false),
user_id: None,
verbose: None,
feedback_influence: None,
retriever_specific_config: None,
response_schema: None,
custom_search_type: None,
auto_feedback_detection: None,
neighborhood_depth: None,
neighborhood_seed_top_k: None,
summarize_context: None,
}
}
fn response_payload_text(response: &SearchResponse) -> String {
match &response.result {
SearchOutput::Items(items) => items
.iter()
.map(|item| item.payload.to_string())
.collect::<Vec<_>>()
.join(" ")
.to_lowercase(),
SearchOutput::Texts(texts) => texts.join(" ").to_lowercase(),
SearchOutput::Text(text) => text.to_lowercase(),
_ => String::new(),
}
}
#[tokio::test]
async fn test_memify_e2e_real_embedding_real_qdrant() {
if !cognee_test_utils::llm_env_available() {
eprintln!("skipping: live LLM credentials (OPENAI_URL/OPENAI_TOKEN) not set");
return;
}
let temp_dir = TempDir::new().expect("temp dir");
let Some((embedding_engine, _embedding_dims)) =
cognee_test_utils::create_test_embedding_engine().await
else {
return;
};
let embedding_engine: Arc<dyn EmbeddingEngine> = embedding_engine;
let vector_db: Arc<dyn VectorDB> = Arc::new(MockVectorDB::new());
let graph_path = temp_dir.path().join("graph").to_string_lossy().to_string();
let graph_db: Arc<dyn GraphDBTrait> = Arc::new(
LadybugAdapter::new(&graph_path)
.await
.expect("LadybugAdapter::new"),
);
graph_db.initialize().await.expect("graph_db.initialize");
let db_path = temp_dir.path().join("cognee.db");
std::fs::File::create(&db_path).expect("create sqlite db file");
let db_url = format!("sqlite://{}", db_path.display());
let db = connect(&db_url).await.expect("connect");
initialize(&db).await.expect("initialize");
let database: Arc<DatabaseConnection> = Arc::new(db);
let llm: Arc<dyn Llm> = Arc::new(MockLlm::empty());
let alice = Uuid::new_v4();
let techcorp = Uuid::new_v4();
let bob = Uuid::new_v4();
let carol = Uuid::new_v4();
add_node(graph_db.as_ref(), alice, "Alice", "Software engineer").await;
add_node(
graph_db.as_ref(),
techcorp,
"TechCorp",
"Technology company",
)
.await;
add_node(graph_db.as_ref(), bob, "Bob", "Product manager").await;
add_node(graph_db.as_ref(), carol, "Carol", "Designer").await;
add_edge(graph_db.as_ref(), alice, techcorp, "works_at").await;
add_edge(graph_db.as_ref(), alice, bob, "knows").await;
add_edge(graph_db.as_ref(), bob, techcorp, "works_at").await;
let memify_config = MemifyConfig::default();
let pool: Arc<dyn CpuPool> =
Arc::new(RayonThreadPool::with_default_threads().expect("rayon pool"));
let result = memify(
Arc::clone(&graph_db),
Arc::clone(&vector_db),
Arc::clone(&embedding_engine),
pool,
Arc::clone(&database),
Arc::new(cognee_database::NoopPipelineRunRepository::new())
as Arc<dyn cognee_database::PipelineRunRepository>,
Some(Uuid::new_v4()), Some(Uuid::new_v4()), None, &memify_config,
)
.await
.expect("memify should succeed on the seeded graph");
assert!(
result.triplet_count > 0,
"memify should produce at least one triplet for 3 seeded edges"
);
assert_eq!(
result.index_result.indexed_count, result.triplet_count,
"all extracted triplets must be indexed when no embedding errors occur \
(indexed={}, triplet_count={})",
result.index_result.indexed_count, result.triplet_count,
);
assert!(
vector_db
.has_collection("Triplet", "text")
.await
.expect("has_collection"),
"the Triplet:text collection must exist after memify"
);
let orchestrator = SearchBuilder::new(
Arc::clone(&vector_db),
Arc::clone(&embedding_engine),
Arc::clone(&graph_db),
Arc::clone(&llm),
database.clone() as Arc<dyn SearchHistoryDb>,
)
.build();
let query = "Who works at TechCorp?";
let response = orchestrator
.search(&make_request(query, SearchType::TripletCompletion))
.await
.expect("TripletCompletion search should succeed");
match &response.result {
SearchOutput::Items(items) => {
assert!(
!items.is_empty(),
"TripletCompletion should return at least one context item \
after memifying 3 seeded edges"
);
}
other => panic!("only_context=true should yield SearchOutput::Items, got {other:?}"),
}
let haystack = response_payload_text(&response);
let seeds = ["works_at", "knows", "alice", "bob", "techcorp", "carol"];
let hit = seeds.iter().any(|needle| haystack.contains(needle));
assert!(
hit,
"at least one returned payload must reference a seeded relationship \
or entity name (seeds={seeds:?}); payload text: {haystack}"
);
}