Files
poimen-memory/tests/it_pg_repo.rs.disabled.rs.disabled
T

477 lines
14 KiB
Plaintext
Raw Normal View History

use mem_store::{Level, VectorKind, MemoryNode, PgRepo, Scope};
use sha2::{Sha256, Digest};
/// Deterministic embedder: sha256(text) → 768-dim vector
/// Allows exact distance assertions without external API calls
fn hash_to_embedding(text: &str) -> Vec<f32> {
let mut hasher = Sha256::new();
hasher.update(text.as_bytes());
let hash = hasher.finalize();
// Convert 32 bytes to 768 floats deterministically
let mut embedding = vec![0.0_f32; 768];
for (i, byte) in hash.iter().enumerate() {
let idx = i % 768;
embedding[idx] += (*byte as f32) / 256.0;
}
// Normalize to unit vector (cosine distance)
let mag: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
if mag > 0.0 {
for x in &mut embedding {
*x /= mag;
}
}
embedding
}
/// Compute cosine distance between two normalized vectors
fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
// Distance = 1 - similarity (for normalized vectors)
(1.0 - dot).max(0.0)
}
/// Setup: Postgres via testcontainers (if available in CI/local)
/// For now, skip tests if DB not available to avoid CI dependencies
#[tokio::test]
#[ignore] // Run with: cargo test it_pg_repo -- --ignored
async fn a1_upsert_idempotent() {
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://postgres:password@localhost:5432/poimen_test".to_string()
});
// Skip if DB not available
if !is_postgres_available(&db_url).await {
println!("Skipping: Postgres not available at {}", db_url);
return;
}
let repo = PgRepo::connect(&db_url).await.expect("connect");
repo.clear_project("test_p1").await.ok();
let node = MemoryNode {
sha256: "abc123def456".to_string(),
level: Level::L1,
project: "test_p1".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 0,
source: None,
text: "test memory".to_string(),
};
// First insert
repo.upsert_node(&node).await.expect("upsert 1");
let count1 = repo.node_count().await.expect("count 1");
assert_eq!(count1, 1, "First insert should create 1 node");
// Upsert same node again
repo.upsert_node(&node).await.expect("upsert 2");
let count2 = repo.node_count().await.expect("count 2");
assert_eq!(count2, 1, "Duplicate upsert must not create duplicate row");
repo.clear_project("test_p1").await.ok();
}
#[tokio::test]
#[ignore]
async fn a2_two_pass_required() {
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://postgres:password@localhost:5432/poimen_test".to_string()
});
if !is_postgres_available(&db_url).await {
println!("Skipping: Postgres not available");
return;
}
let repo = PgRepo::connect(&db_url).await.expect("connect");
repo.clear_project("test_p2").await.ok();
let parent = MemoryNode {
sha256: "parent_sha".to_string(),
level: Level::L0,
project: "test_p2".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 0,
source: Some("pi".to_string()),
text: "parent memory".to_string(),
};
let child = MemoryNode {
sha256: "child_sha".to_string(),
level: Level::L1,
project: "test_p2".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 1,
source: None,
text: "child memory".to_string(),
};
// Insert child first (before parent)
repo.upsert_node(&child).await.expect("insert child");
// Try edge before parent exists — should fail
let edge_result = repo
.insert_edges(&child.sha256, &[parent.sha256.clone()])
.await;
assert!(
edge_result.is_err(),
"Edge insert should fail when parent not found (FK constraint)"
);
// Now insert parent
repo.upsert_node(&parent).await.expect("insert parent");
// Now edge should succeed
repo.insert_edges(&child.sha256, &[parent.sha256.clone()])
.await
.expect("insert edge after parent exists");
let edge_count = repo.edge_count().await.expect("edge count");
assert_eq!(edge_count, 1, "Edge should be created in second pass");
repo.clear_project("test_p2").await.ok();
}
#[tokio::test]
#[ignore]
async fn a3_search_orders_by_distance() {
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://postgres:password@localhost:5432/poimen_test".to_string()
});
if !is_postgres_available(&db_url).await {
println!("Skipping: Postgres not available");
return;
}
let repo = PgRepo::connect(&db_url).await.expect("connect");
repo.clear_project("test_p3").await.ok();
// Create three nodes with known embeddings
let texts = vec!["apple", "application", "banana"];
let embeddings: Vec<Vec<f32>> = texts
.iter()
.map(|t| hash_to_embedding(t))
.collect();
for (i, text) in texts.iter().enumerate() {
let node = MemoryNode {
sha256: format!("node_{}", i),
level: Level::L1,
project: "test_p3".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: i as i32,
source: None,
text: text.to_string(),
};
repo.upsert_node(&node).await.expect("upsert");
repo.upsert_vector(&node.sha256, VectorKind::Text, &embeddings[i])
.await
.expect("upsert vector");
}
// Query for "apple" — should rank apple closest
let query_embedding = hash_to_embedding("apple");
let results = repo
.search(
&query_embedding,
VectorKind::Text,
&[Level::L1],
&Scope::Project("test_p3".to_string()),
3,
)
.await
.expect("search");
assert_eq!(results.len(), 3, "Should return all 3 nodes");
// First result should be "apple" itself (distance ~0)
assert_eq!(
results[0].node.text, "apple",
"Closest match should be 'apple' itself"
);
assert!(results[0].distance < 0.01, "Distance to self should be ~0");
// Verify ordering matches hand-computed distances
for i in 0..results.len() - 1 {
assert!(
results[i].distance <= results[i + 1].distance + 1e-5,
"Results should be ordered by distance (ascending)"
);
}
repo.clear_project("test_p3").await.ok();
}
#[tokio::test]
#[ignore]
async fn a4_level_filter() {
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://postgres:password@localhost:5432/poimen_test".to_string()
});
if !is_postgres_available(&db_url).await {
println!("Skipping: Postgres not available");
return;
}
let repo = PgRepo::connect(&db_url).await.expect("connect");
repo.clear_project("test_p4").await.ok();
let levels = vec![Level::L0, Level::L1, Level::L2];
let query_vec = hash_to_embedding("query");
for (i, level) in levels.iter().enumerate() {
let node = MemoryNode {
sha256: format!("node_{}", i),
level: *level,
project: "test_p4".to_string(),
query_id: if *level == Level::L2 { None } else { Some("q1".to_string()) },
run_id: "r1".to_string(),
t: i as i32,
source: None,
text: format!("memory at {:?}", level),
};
repo.upsert_node(&node).await.expect("upsert");
repo.upsert_vector(&node.sha256, VectorKind::Text, &query_vec)
.await
.expect("vector");
}
// Search with L1 only
let results = repo
.search(
&query_vec,
VectorKind::Text,
&[Level::L1],
&Scope::Project("test_p4".to_string()),
10,
)
.await
.expect("search");
assert_eq!(results.len(), 1, "Should return only L1");
assert_eq!(results[0].node.level, Level::L1);
repo.clear_project("test_p4").await.ok();
}
#[tokio::test]
#[ignore]
async fn a5_project_isolation() {
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://postgres:password@localhost:5432/poimen_test".to_string()
});
if !is_postgres_available(&db_url).await {
println!("Skipping: Postgres not available");
return;
}
let repo = PgRepo::connect(&db_url).await.expect("connect");
repo.clear_project("test_p5a").await.ok();
repo.clear_project("test_p5b").await.ok();
let query_vec = hash_to_embedding("test");
// Create identical nodes in two projects
for proj in &["test_p5a", "test_p5b"] {
let node = MemoryNode {
sha256: format!("{}_node", proj),
level: Level::L1,
project: proj.to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 0,
source: None,
text: "same memory".to_string(),
};
repo.upsert_node(&node).await.expect("upsert");
repo.upsert_vector(&node.sha256, VectorKind::Text, &query_vec)
.await
.expect("vector");
}
// Search in p5a — should NOT return p5b
let results = repo
.search(
&query_vec,
VectorKind::Text,
&[Level::L1],
&Scope::Project("test_p5a".to_string()),
10,
)
.await
.expect("search");
assert_eq!(results.len(), 1, "Should return only 1 result (from p5a)");
assert_eq!(results[0].node.project, "test_p5a");
repo.clear_project("test_p5a").await.ok();
repo.clear_project("test_p5b").await.ok();
}
#[tokio::test]
#[ignore]
async fn a6_clear_project_scoped() {
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://postgres:password@localhost:5432/poimen_test".to_string()
});
if !is_postgres_available(&db_url).await {
println!("Skipping: Postgres not available");
return;
}
let repo = PgRepo::connect(&db_url).await.expect("connect");
repo.clear_project("test_p6a").await.ok();
repo.clear_project("test_p6b").await.ok();
let parent_node = MemoryNode {
sha256: "parent".to_string(),
level: Level::L0,
project: "test_p6a".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 0,
source: None,
text: "parent".to_string(),
};
let child_node = MemoryNode {
sha256: "child".to_string(),
level: Level::L1,
project: "test_p6a".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 1,
source: None,
text: "child".to_string(),
};
let other_node = MemoryNode {
sha256: "other".to_string(),
level: Level::L1,
project: "test_p6b".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 0,
source: None,
text: "other".to_string(),
};
// Setup: parent + child in p6a, edge between them; separate node in p6b
repo.upsert_node(&parent_node).await.expect("insert parent");
repo.upsert_node(&child_node).await.expect("insert child");
repo.upsert_node(&other_node).await.expect("insert other");
repo.insert_edges("child", &["parent".to_string()])
.await
.expect("edge");
let edge_count_before = repo.edge_count().await.expect("count");
assert_eq!(edge_count_before, 1);
// Clear p6a
repo.clear_project("test_p6a").await.expect("clear");
// Verify p6b is intact
let other_count = sqlx::query_scalar::<_, i64>(
"SELECT COUNT(*) FROM memory_node WHERE project = 'test_p6b'",
)
.fetch_one(&repo.pool)
.await
.expect("query");
assert_eq!(other_count, 1, "Other project should be intact");
// Verify edges are gone (cascade delete)
let edge_count_after = repo.edge_count().await.expect("count");
assert_eq!(edge_count_after, 0, "Edges should cascade-delete with nodes");
repo.clear_project("test_p6b").await.ok();
}
#[tokio::test]
#[ignore]
async fn a8_parents_of() {
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://postgres:password@localhost:5432/poimen_test".to_string()
});
if !is_postgres_available(&db_url).await {
println!("Skipping: Postgres not available");
return;
}
let repo = PgRepo::connect(&db_url).await.expect("connect");
repo.clear_project("test_p8").await.ok();
let parent1 = MemoryNode {
sha256: "p1".to_string(),
level: Level::L0,
project: "test_p8".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 0,
source: None,
text: "p1".to_string(),
};
let parent2 = MemoryNode {
sha256: "p2".to_string(),
level: Level::L0,
project: "test_p8".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 1,
source: None,
text: "p2".to_string(),
};
let child = MemoryNode {
sha256: "c1".to_string(),
level: Level::L1,
project: "test_p8".to_string(),
query_id: Some("q1".to_string()),
run_id: "r1".to_string(),
t: 2,
source: None,
text: "c1".to_string(),
};
repo.upsert_node(&parent1).await.expect("insert p1");
repo.upsert_node(&parent2).await.expect("insert p2");
repo.upsert_node(&child).await.expect("insert c1");
repo.insert_edges("c1", &["p1".to_string(), "p2".to_string()])
.await
.expect("edges");
let parents = repo.parents_of("c1").await.expect("parents");
assert_eq!(parents.len(), 2, "Should return 2 parents");
let parent_shas: Vec<String> = parents.iter().map(|p| p.sha256.clone()).collect();
assert!(parent_shas.contains(&"p1".to_string()));
assert!(parent_shas.contains(&"p2".to_string()));
repo.clear_project("test_p8").await.ok();
}
/// Helper: Check if Postgres is available (for CI compatibility)
async fn is_postgres_available(url: &str) -> bool {
match sqlx::postgres::PgPoolOptions::new()
.max_connections(1)
.connect(url)
.await
{
Ok(_) => true,
Err(_) => false,
}
}