#[cfg(feature = "testing")]
use semantic_memory::{
AuthorityPermit, ExactnessProfile, ReceiptMode, SearchContext, SearchSource,
};
use semantic_memory::{MemoryConfig, MemoryStore, MockEmbedder, Role};
use tempfile::TempDir;
fn test_store() -> (MemoryStore, TempDir) {
let tmp = TempDir::new().unwrap();
let config = MemoryConfig {
base_dir: tmp.path().to_path_buf(),
..Default::default()
};
let embedder = Box::new(MockEmbedder::new(768));
let store = MemoryStore::open_with_embedder(config, embedder).unwrap();
(store, tmp)
}
#[tokio::test]
async fn end_to_end_add_facts_and_search() {
let (store, _tmp) = test_store();
store
.add_fact("general", "Rust was first released in 2015", None, None)
.await
.unwrap();
store
.add_fact(
"general",
"Python was created by Guido van Rossum",
None,
None,
)
.await
.unwrap();
store
.add_fact("general", "JavaScript was created in 10 days", None, None)
.await
.unwrap();
store
.add_fact("user", "Josh's favorite language is Rust", None, None)
.await
.unwrap();
store
.add_fact("user", "Josh works on Ironforge", None, None)
.await
.unwrap();
let results = store
.search_fts_only("Rust", None, None, None)
.await
.unwrap();
assert!(!results.is_empty(), "Should find facts about Rust");
let _results = store
.search("programming languages", None, None, None)
.await
.unwrap();
let stats = store.stats().await.unwrap();
assert_eq!(stats.total_facts, 5);
assert_eq!(stats.total_sessions, 0);
}
#[tokio::test]
async fn end_to_end_document_ingestion() {
let (store, _tmp) = test_store();
let content = "This is a test document about machine learning. ".repeat(50);
let doc_id = store
.ingest_document("Test Doc", &content, "docs", Some("/test/doc.txt"), None)
.await
.unwrap();
let docs = store.list_documents("docs", 10, 0).await.unwrap();
assert_eq!(docs.len(), 1);
assert_eq!(docs[0].title, "Test Doc");
assert!(docs[0].chunk_count > 0);
let results = store
.search_fts_only("machine learning", None, None, None)
.await
.unwrap();
assert!(
!results.is_empty(),
"Document chunks should be FTS searchable"
);
store.delete_document(&doc_id).await.unwrap();
let docs = store.list_documents("docs", 10, 0).await.unwrap();
assert!(docs.is_empty());
let results = store
.search_fts_only("machine learning", None, None, None)
.await
.unwrap();
assert!(
results.is_empty(),
"FTS should be clean after document deletion"
);
}
#[tokio::test]
async fn clone_shares_state() {
let (store_a, _tmp) = test_store();
let store_b = store_a.clone();
store_a
.add_fact("test", "Shared state test", None, None)
.await
.unwrap();
let results = store_b
.search_fts_only("Shared state", None, None, None)
.await
.unwrap();
assert!(!results.is_empty(), "Clone should share state");
}
#[tokio::test]
async fn conversations_and_facts_coexist() {
let (store, _tmp) = test_store();
let sid = store.create_session("test").await.unwrap();
store
.add_message(&sid, Role::User, "What is Rust?", Some(10), None)
.await
.unwrap();
store
.add_message(
&sid,
Role::Assistant,
"Rust is a programming language",
Some(15),
None,
)
.await
.unwrap();
store
.add_fact("general", "Rust is a systems language", None, None)
.await
.unwrap();
let messages = store.get_recent_messages(&sid, 10).await.unwrap();
assert_eq!(messages.len(), 2);
let facts = store.list_facts("general", 10, 0).await.unwrap();
assert_eq!(facts.len(), 1);
let stats = store.stats().await.unwrap();
assert_eq!(stats.total_sessions, 1);
assert_eq!(stats.total_messages, 2);
assert_eq!(stats.total_facts, 1);
}
#[tokio::test]
async fn reopen_database_preserves_data() {
let tmp = TempDir::new().unwrap();
let path = tmp.path().to_path_buf();
{
let config = MemoryConfig {
base_dir: path.clone(),
..Default::default()
};
let store =
MemoryStore::open_with_embedder(config, Box::new(MockEmbedder::new(768))).unwrap();
store
.add_fact("general", "Persistent fact", None, None)
.await
.unwrap();
let sid = store.create_session("test").await.unwrap();
store
.add_message(&sid, Role::User, "Persistent message", Some(10), None)
.await
.unwrap();
}
{
let config = MemoryConfig {
base_dir: path,
..Default::default()
};
let store =
MemoryStore::open_with_embedder(config, Box::new(MockEmbedder::new(768))).unwrap();
let facts = store.list_facts("general", 10, 0).await.unwrap();
assert_eq!(facts.len(), 1);
assert_eq!(facts[0].content, "Persistent fact");
let sessions = store.list_sessions(10, 0).await.unwrap();
assert_eq!(sessions.len(), 1);
}
}
#[tokio::test]
async fn vacuum_works() {
let (store, _tmp) = test_store();
store
.add_fact("general", "Vacuumable fact", None, None)
.await
.unwrap();
store.vacuum().await.unwrap();
}
#[test]
fn chunk_text_exposed_on_store() {
let (store, _tmp) = test_store();
let chunks = store.chunk_text("Hello world, this is a test.");
assert_eq!(chunks.len(), 1);
}
#[tokio::test]
async fn embed_exposed_on_store() {
let (store, _tmp) = test_store();
let embedding = store.embed("Hello world").await.unwrap();
assert_eq!(embedding.len(), 768);
}
#[tokio::test]
async fn embed_batch_exposed_on_store() {
let (store, _tmp) = test_store();
let embeddings = store.embed_batch(&["Hello", "World"]).await.unwrap();
assert_eq!(embeddings.len(), 2);
assert_eq!(embeddings[0].len(), 768);
assert_eq!(embeddings[1].len(), 768);
}
#[tokio::test]
async fn v2_migration_preserves_existing_data() {
let tmp = TempDir::new().unwrap();
let path = tmp.path().to_path_buf();
{
let config = MemoryConfig {
base_dir: path.clone(),
..Default::default()
};
let store =
MemoryStore::open_with_embedder(config, Box::new(MockEmbedder::new(768))).unwrap();
let sid = store.create_session("test").await.unwrap();
store
.add_message(&sid, Role::User, "V1 message", Some(10), None)
.await
.unwrap();
store
.add_fact("general", "V1 fact", None, None)
.await
.unwrap();
}
{
let config = MemoryConfig {
base_dir: path,
..Default::default()
};
let store =
MemoryStore::open_with_embedder(config, Box::new(MockEmbedder::new(768))).unwrap();
let sessions = store.list_sessions(10, 0).await.unwrap();
assert_eq!(sessions.len(), 1);
let messages = store
.get_recent_messages(&sessions[0].id, 10)
.await
.unwrap();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, "V1 message");
let facts = store.list_facts("general", 10, 0).await.unwrap();
assert_eq!(facts.len(), 1);
assert_eq!(facts[0].content, "V1 fact");
let sid = &sessions[0].id;
store
.add_message_embedded(sid, Role::User, "V2 embedded message", Some(10), None)
.await
.unwrap();
let results = store
.search_conversations("embedded", None, None)
.await
.unwrap();
assert!(!results.is_empty());
}
}
#[tokio::test]
async fn test_v3_migration() {
let (store, _tmp) = test_store();
let dirty = store.embeddings_are_dirty().await.unwrap();
assert!(!dirty, "Fresh DB should not have dirty embeddings");
}
#[cfg(feature = "admin-ops")]
#[tokio::test]
async fn test_embedding_dirty_flag() {
let tmp = TempDir::new().unwrap();
let path = tmp.path().to_path_buf();
{
let config = MemoryConfig {
base_dir: path.clone(),
..Default::default()
};
let store =
MemoryStore::open_with_embedder(config, Box::new(MockEmbedder::new(768))).unwrap();
store.add_fact("ns", "test fact", None, None).await.unwrap();
assert!(!store.embeddings_are_dirty().await.unwrap());
}
{
let mut config = MemoryConfig {
base_dir: path.clone(),
..Default::default()
};
config.embedding.dimensions = 256;
config.embedding.model = "model-b".to_string();
let store =
MemoryStore::open_with_embedder(config, Box::new(MockEmbedder::new(256))).unwrap();
assert!(
store.embeddings_are_dirty().await.unwrap(),
"Embeddings should be dirty after model change"
);
let count = store.reembed_all().await.unwrap();
assert!(count >= 1, "Should have re-embedded at least 1 item");
assert!(
!store.embeddings_are_dirty().await.unwrap(),
"Embeddings should be clean after reembed_all"
);
}
}
#[cfg(feature = "admin-ops")]
#[tokio::test]
async fn test_reembed_all_includes_messages() {
let (store, _tmp) = test_store();
let session = store.create_session("test").await.unwrap();
store
.add_message_embedded(&session, Role::User, "fluid dynamics", Some(5), None)
.await
.unwrap();
store
.add_message(&session, Role::User, "not embedded", Some(5), None)
.await
.unwrap();
let count = store.reembed_all().await.unwrap();
assert!(
count >= 1,
"At least the one embedded message should be re-embedded"
);
}
#[tokio::test]
async fn test_auto_token_count() {
let (store, _tmp) = test_store();
let session = store.create_session("test").await.unwrap();
store
.add_message(&session, Role::User, "hello world testing", None, None)
.await
.unwrap();
let messages = store.get_recent_messages(&session, 10).await.unwrap();
assert!(messages[0].token_count.is_some());
assert!(messages[0].token_count.unwrap() > 0);
}
#[cfg(feature = "admin-ops")]
#[tokio::test]
async fn search_cache_cleared_after_update_fact() {
let (store, _tmp) = test_store();
let fact_id = store
.add_fact(
"general",
"xyzzy unique marker phrase for cache test",
None,
None,
)
.await
.unwrap();
let results1 = store
.search("xyzzy unique marker", Some(5), None, None)
.await
.unwrap();
assert!(
!results1.is_empty(),
"Should find the fact with xyzzy marker before update"
);
assert!(results1
.iter()
.any(|r| r.content.contains("xyzzy unique marker")));
store
.update_fact(&fact_id, "completely replaced content nothing in common")
.await
.unwrap();
let results2 = store
.search("xyzzy unique marker", Some(5), None, None)
.await
.unwrap();
assert!(
results2
.iter()
.all(|r| !r.content.contains("xyzzy unique marker")),
"Search cache must be cleared after update_fact — old stale content should not appear"
);
}
#[cfg(feature = "admin-ops")]
#[tokio::test]
async fn search_cache_cleared_after_delete_namespace() {
let (store, _tmp) = test_store();
store
.add_fact(
"testns",
"qqqq distinctive namespace cache fact",
None,
None,
)
.await
.unwrap();
let results1 = store
.search("qqqq distinctive namespace", Some(5), None, None)
.await
.unwrap();
assert!(
!results1.is_empty(),
"Should find qqqq fact before namespace delete"
);
store.delete_namespace("testns").await.unwrap();
let results2 = store
.search("qqqq distinctive namespace", Some(5), None, None)
.await
.unwrap();
assert!(
results2
.iter()
.all(|r| !r.content.contains("qqqq distinctive")),
"Cache must be cleared after delete_namespace"
);
}
#[cfg(feature = "testing")]
#[tokio::test]
async fn two_store_governed_canary_is_immediately_witnessed_and_cache_coherent_by_epoch() {
let dir = tempfile::TempDir::new().unwrap();
let config_a = MemoryConfig {
base_dir: dir.path().to_path_buf(),
..Default::default()
};
let config_b = config_a.clone();
let reader =
MemoryStore::open_with_embedder(config_a, Box::new(MockEmbedder::new(768))).unwrap();
let writer =
MemoryStore::open_with_embedder(config_b, Box::new(MockEmbedder::new(768))).unwrap();
let cache_query = "coherence canary epoch cache token 2026";
let canary = "coherence canary epoch cache token 2026";
reader
.add_fact(
"coherence",
"coherence baseline fact for cache warmup coherence canary epoch cache token",
None,
None,
)
.await
.unwrap();
let baseline = reader
.search(cache_query, Some(10), None, None)
.await
.unwrap();
assert!(
baseline
.iter()
.any(|r| r.content.contains("baseline fact for cache warmup")),
"Reader cache should be primed by a baseline fact"
);
assert!(
!baseline.iter().any(|r| r.content.contains(canary)),
"Baseline should not yet contain the canary"
);
let permit = AuthorityPermit::operator_system(
"coherence-principal".to_string(),
"coherence-caller".to_string(),
AuthorityPermit::APPEND_CAPABILITY,
);
let receipt = writer
.authority()
.append(
permit,
"coherence-canary-write".to_string(),
"coherence".to_string(),
canary.to_string(),
Some("tests/coherence-canary.md".to_string()),
)
.await
.unwrap();
let mut context = SearchContext::default_now();
context.receipt_mode = ReceiptMode::ReturnReceipt;
context.exactness_profile = ExactnessProfile::PreferExact;
context.request_id = Some("coherence-canary-witnessed".to_string());
let witnessed = reader
.search_with_context(cache_query, Some(10), None, None, context)
.await
.unwrap();
let witness = if let Some(witness) = witnessed.receipt.as_ref() {
witness
} else {
panic!("witnessed search should emit a receipt");
};
let canary_result = if let Some(canary_result) =
witnessed
.results
.iter()
.find(|result| match &result.source {
SearchSource::Fact { fact_id, .. } => fact_id == &receipt.affected_ids[0],
_ => false,
}) {
canary_result
} else {
panic!("canary result should be present in witnessed search");
};
assert!(
canary_result.cosine_similarity.is_some(),
"governed append must carry vector evidence on exact witnessed search"
);
assert!(
witness
.result_ids
.iter()
.any(|id| id == &format!("fact:{}", receipt.affected_ids[0])),
"witnessed receipt should include the governed fact id"
);
let replayed = reader
.search(cache_query, Some(10), None, None)
.await
.unwrap();
assert!(
replayed
.iter()
.any(|r| matches!(&r.source, SearchSource::Fact { fact_id, .. } if *fact_id == receipt.affected_ids[0]))
|| replayed.iter().any(|r| r.content.contains(canary)),
"Ordinary search should eventually observe the canary under cache coherence checks"
);
}