use klieo_core::error::MemoryError;
use klieo_core::memory::{Fact, Scope};
use klieo_memory_graph::FilterableLongTermMemory;
use klieo_memory_pgvector::{DummyEmbedder, FakeEmbedder, MemoryPgvector, PgvectorConfig};
use std::sync::Arc;
use testcontainers::core::{IntoContainerPort, WaitFor};
use testcontainers::runners::AsyncRunner;
use testcontainers::{ContainerAsync, GenericImage, ImageExt};
async fn start_pgvector() -> (ContainerAsync<GenericImage>, String) {
let container = GenericImage::new("pgvector/pgvector", "pg16")
.with_wait_for(WaitFor::message_on_stderr(
"database system is ready to accept connections",
))
.with_exposed_port(5432.tcp())
.with_env_var("POSTGRES_PASSWORD", "test")
.with_env_var("POSTGRES_DB", "klieo")
.start()
.await
.expect("start pgvector container");
let port = container
.get_host_port_ipv4(5432)
.await
.expect("mapped port");
let url = format!("postgres://postgres:test@127.0.0.1:{port}/klieo");
(container, url)
}
#[tokio::test]
#[ignore = "requires Docker (pgvector/pgvector image)"]
async fn remember_recall_filter_forget_roundtrip() {
let (_container, url) = start_pgvector().await;
let mem = MemoryPgvector::new(PgvectorConfig::new(url), Arc::new(DummyEmbedder))
.await
.expect("connect + pool");
let scope = Scope::Workspace("ws-1".to_string());
let id_alpha = mem
.long_term
.remember(scope.clone(), Fact::new("alpha fact"))
.await
.expect("remember alpha");
let id_beta = mem
.long_term
.remember(scope.clone(), Fact::new("beta fact"))
.await
.expect("remember beta");
let recalled = mem
.long_term
.recall(scope.clone(), "alpha", 10)
.await
.expect("recall");
assert!(recalled.iter().any(|f| f.text == "alpha fact"));
let filtered = mem
.pgvector_long_term
.recall_filtered(scope.clone(), "beta", 10, std::slice::from_ref(&id_beta))
.await
.expect("recall_filtered");
assert!(filtered.iter().all(|f| f.text == "beta fact"));
mem.long_term.forget(id_alpha).await.expect("forget alpha");
let after = mem
.long_term
.recall(scope, "alpha", 10)
.await
.expect("recall after forget");
assert!(!after.iter().any(|f| f.text == "alpha fact"));
}
#[tokio::test]
#[ignore = "requires Docker (pgvector/pgvector image)"]
async fn existing_table_dim_mismatch_fails_closed() {
let (_container, url) = start_pgvector().await;
let scope = Scope::Workspace("dim".to_string());
let mem64 = MemoryPgvector::new(PgvectorConfig::new(url.clone()), Arc::new(FakeEmbedder::new(64)))
.await
.expect("connect 64");
mem64
.long_term
.remember(scope.clone(), Fact::new("hi"))
.await
.expect("remember 64");
let mem128 = MemoryPgvector::new(PgvectorConfig::new(url), Arc::new(FakeEmbedder::new(128)))
.await
.expect("connect 128");
let err = mem128
.long_term
.remember(scope, Fact::new("hi"))
.await
.expect_err("dimension mismatch must fail closed");
assert!(matches!(err, MemoryError::Embedding(_)));
}