#![cfg(feature = "test-utils")]
use std::time::Duration;
use testcontainers::ImageExt as _;
use testcontainers::runners::AsyncRunner as _;
use testcontainers_modules::postgres::Postgres;
use zeph_config::PlanCacheConfig;
use zeph_db::DbConfig;
use zeph_orchestration::graph::{TaskId, TaskNode};
use zeph_orchestration::{PlanCache, TaskGraph};
async fn start_pg() -> (zeph_db::DbPool, impl Drop) {
let image = Postgres::default().with_startup_timeout(Duration::from_mins(2));
let container = image.start().await.expect("docker must be available");
let host = container.get_host().await.unwrap();
let port = container.get_host_port_ipv4(5432).await.unwrap();
let url = format!("postgres://postgres:postgres@{host}:{port}/postgres");
let config = DbConfig {
url,
max_connections: 5,
pool_size: 5,
};
let pool = config.connect().await.expect("failed to connect to PG");
(pool, container)
}
fn make_graph(goal: &str, tasks: &[(&str, &str, &[u32])]) -> TaskGraph {
let mut graph = TaskGraph::new(goal);
for (i, (title, desc, deps)) in tasks.iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
let mut node = TaskNode::new(i as u32, *title, *desc);
node.depends_on = deps.iter().map(|&d| TaskId(d)).collect();
graph.tasks.push(node);
}
graph
}
#[tokio::test]
#[ignore = "requires Docker"]
async fn cache_plan_dedup_increments_success_count_on_postgres() {
let (pool, _container) = start_pg().await;
let cache = PlanCache::new(pool.clone(), PlanCacheConfig::default(), "test-model")
.await
.unwrap();
let graph = make_graph("same goal", &[("Task", "do it", &[])]);
let emb = vec![1.0_f32, 0.0];
cache.cache_plan(&graph, &emb, "test-model").await.unwrap();
cache.cache_plan(&graph, &emb, "test-model").await.unwrap();
let count: i64 = zeph_db::query_scalar(zeph_db::sql!("SELECT COUNT(*) FROM plan_cache"))
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(count, 1, "UNIQUE goal_hash must prevent a second row");
let normalized = zeph_orchestration::normalize_goal("same goal");
let success: i32 = zeph_db::query_scalar(zeph_db::sql!(
"SELECT success_count FROM plan_cache WHERE goal_hash = ?"
))
.bind(zeph_orchestration::plan_cache::goal_hash(&normalized))
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(
success, 2,
"success_count must increment across two cache_plan calls for the same goal"
);
}
#[tokio::test]
#[ignore = "requires Docker"]
async fn find_similar_updates_last_accessed_on_postgres() {
let (pool, _container) = start_pg().await;
let config = PlanCacheConfig {
similarity_threshold: 0.9,
..PlanCacheConfig::default()
};
let cache = PlanCache::new(pool, config, "test-model").await.unwrap();
let graph = make_graph("deploy service", &[("Build", "build it", &[])]);
let embedding = vec![1.0_f32, 0.0, 0.0];
cache
.cache_plan(&graph, &embedding, "test-model")
.await
.unwrap();
let result = cache
.find_similar(&[1.0, 0.0, 0.0], "test-model")
.await
.unwrap();
assert!(result.is_some());
let (template, score) = result.unwrap();
assert!((score - 1.0).abs() < 1e-5);
assert_eq!(template.tasks.len(), 1);
}