#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::Path;
use std::sync::Arc;
use embedmind_core::api::{MemoryDraft, Query, Scope, Store, StoreOptions};
use embedmind_core::storage::sim::SimVfs;
use embedmind_core::storage::vfs::Vfs;
use embedmind_core::storage::{Pager, PagerOptions};
use embedmind_core::{Result, embed::OnnxEmbedder};
const STORE: &str = "memory.mind";
fn store() -> (Arc<dyn Vfs>, Store) {
let embedder = Arc::new(OnnxEmbedder::load().expect("embedded model must load"));
let vfs: Arc<dyn Vfs> = Arc::new(SimVfs::new());
let opts = StoreOptions {
embedder: Some(embedder),
..StoreOptions::default()
};
let store = Store::create_with(Arc::clone(&vfs), Path::new(STORE), opts).unwrap();
(vfs, store)
}
fn reopen(vfs: Arc<dyn Vfs>) -> Store {
let embedder = Arc::new(OnnxEmbedder::load().expect("embedded model must load"));
let opts = StoreOptions {
embedder: Some(embedder),
..StoreOptions::default()
};
Store::open_with(vfs, Path::new(STORE), opts).unwrap()
}
#[test]
fn recall_ranks_semantically_closest_first() {
let (_vfs, mut store) = store();
store
.remember(MemoryDraft::new("the cat sat quietly on the warm mat"))
.unwrap();
store
.remember(MemoryDraft::new("quarterly corporate tax filing deadline"))
.unwrap();
let feline = store
.remember(MemoryDraft::new("a kitten was sleeping on the rug"))
.unwrap();
let hits = store
.recall(Query::new("a small feline resting").limit(3))
.unwrap();
assert!(!hits.is_empty(), "recall must return the indexed memories");
assert!(
hits[0].content.contains("cat") || hits[0].id == feline.id,
"top hit should be cat-related, got {:?}",
hits[0].content
);
assert!(
hits.iter().any(|h| h.id == feline.id),
"the kitten memory should be among the top hits"
);
}
#[test]
fn recall_excludes_forgotten_memories() {
let (_vfs, mut store) = store();
let doomed = store
.remember(MemoryDraft::new("temporary note about database indexes"))
.unwrap();
store
.remember(MemoryDraft::new("permanent note about database indexes"))
.unwrap();
assert!(store.forget(doomed.id).unwrap());
let hits = store
.recall(Query::new("database indexes").limit(10))
.unwrap();
assert!(
hits.iter().all(|h| h.id != doomed.id),
"forgotten memory must never appear in recall"
);
assert!(
hits.iter().any(|h| h.content.starts_with("permanent")),
"the live memory must still be recallable"
);
}
#[test]
fn recall_scope_filters_by_project() {
let (_vfs, mut store) = store();
store
.remember(MemoryDraft::new("uses tokio for async").project("alpha"))
.unwrap();
store
.remember(MemoryDraft::new("uses tokio for async").project("beta"))
.unwrap();
let alpha = store
.recall(
Query::new("async runtime")
.scope(Scope::Project("alpha".into()))
.limit(10),
)
.unwrap();
assert!(!alpha.is_empty());
assert!(
alpha.iter().all(|h| h.project.as_deref() == Some("alpha")),
"project scope must exclude other projects"
);
let all = store.recall(Query::new("async runtime").limit(10)).unwrap();
assert!(
all.len() >= 2,
"Scope::All must see memories across projects"
);
}
#[test]
fn recall_survives_reopen() {
let (vfs, mut store) = store();
for i in 0..20 {
store
.remember(MemoryDraft::new(format!(
"memory number {i} about rust and systems"
)))
.unwrap();
}
let probe = store
.remember(MemoryDraft::new(
"the founder prefers explicit typed errors over panics",
))
.unwrap();
store.close().unwrap();
let store = reopen(vfs);
let hits = store
.recall(Query::new("explicit error handling without panic").limit(5))
.unwrap();
assert!(
hits.iter().any(|h| h.id == probe.id),
"the distinctive memory must be recallable after reopen"
);
}
#[test]
fn recall_finds_content_past_the_first_window_via_chunking() {
let (_vfs, mut store) = store();
let filler = "the meeting notes continued with routine status updates ".repeat(100);
let needle = "the production database password rotation happens every thursday at noon";
let long = format!("{filler} {needle}");
let chunked = store.remember(MemoryDraft::new(long.clone())).unwrap();
store
.remember(MemoryDraft::new("grocery list: apples, bread, coffee"))
.unwrap();
let hits = store
.recall(Query::new("when does the database password rotate").limit(5))
.unwrap();
assert!(
hits.iter().any(|h| h.id == chunked.id),
"content past the first token window must be recallable"
);
let occurrences = hits.iter().filter(|h| h.id == chunked.id).count();
assert_eq!(
occurrences, 1,
"a chunked memory must be returned once, not once per chunk"
);
let hit = hits.iter().find(|h| h.id == chunked.id).unwrap();
assert_eq!(
hit.content, long,
"recall returns the whole memory, never a chunk"
);
}
#[test]
fn legacy_file_without_fts_index_recalls_vector_only_with_warning_flag() {
let (vfs, mut store) = store();
let feline = store
.remember(MemoryDraft::new("a kitten was sleeping on the rug"))
.unwrap();
store
.remember(MemoryDraft::new("quarterly corporate tax filing deadline"))
.unwrap();
store.close().unwrap();
let mut pager =
Pager::open(Arc::clone(&vfs), Path::new(STORE), PagerOptions::default()).unwrap();
let mut txn = pager.begin().unwrap();
txn.set_fts_root_page(0);
txn.commit().unwrap();
pager.close().unwrap();
let store = reopen(vfs);
let outcome = store
.recall_detailed(Query::new("a small feline resting").limit(5))
.expect("recall on a legacy file must degrade, never error");
assert!(
outcome.degraded_to_vector_only,
"the missing fts index must be reported so shells can warn"
);
assert!(
outcome.hits.iter().any(|h| h.id == feline.id),
"vector similarity must still find the semantically close memory"
);
let hits = store
.recall(Query::new("a small feline resting").limit(5))
.unwrap();
assert_eq!(
hits.iter().map(|h| h.id).collect::<Vec<_>>(),
outcome.hits.iter().map(|h| h.id).collect::<Vec<_>>(),
"recall and recall_detailed must agree on a legacy file"
);
}
#[test]
fn recall_without_embedder_is_a_typed_error() {
let vfs: Arc<dyn Vfs> = Arc::new(SimVfs::new());
let mut store = Store::create_with(vfs, Path::new(STORE), StoreOptions::default()).unwrap();
store
.remember(MemoryDraft::new("stored without a vector"))
.unwrap();
let err = store.recall(Query::new("anything")).unwrap_err();
assert!(
matches!(err, embedmind_core::Error::InvalidArgument(_)),
"recall on an embedder-less store must be InvalidArgument, got {err:?}"
);
}
#[test]
fn recency_breaks_a_genuine_content_tie_toward_the_newer_memory() {
let (_vfs, mut store) = store();
let original = store
.remember(MemoryDraft::new(
"the deploy runbook says restart the worker process before the scheduler process",
))
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(2));
let correction = store
.remember(MemoryDraft::new(
"deploy runbook correction: restart the scheduler process before the worker process",
))
.unwrap();
let hits = store
.recall(
Query::new("deploy runbook restart order worker scheduler process")
.recency(true)
.limit(5),
)
.unwrap();
assert!(hits.len() >= 2, "both memories must be recalled");
let without_recency = store
.recall(Query::new("deploy runbook restart order worker scheduler process").limit(5))
.unwrap();
let score_of = |hits: &[_], id| {
hits.iter()
.find(|h: &&embedmind_core::api::Recalled| h.id == id)
.map(|h| h.score)
};
assert_eq!(
score_of(&without_recency, original.id),
score_of(&without_recency, correction.id),
"precondition: this golden case needs a genuine content-score tie"
);
assert_eq!(
hits[0].id, correction.id,
"a genuine content tie must break toward the newer memory when recency is on"
);
assert_eq!(hits[1].id, original.id);
}
#[test]
fn identical_restatement_does_not_displace_the_first_recorded_one() {
let (_vfs, mut store) = store();
let text = "the deploy runbook says restart the worker before the scheduler";
let original = store.remember(MemoryDraft::new(text)).unwrap();
std::thread::sleep(std::time::Duration::from_millis(2));
store.remember(MemoryDraft::new(text)).unwrap();
let hits = store
.recall(
Query::new("deploy runbook restart order worker scheduler")
.recency(true)
.limit(5),
)
.unwrap();
assert!(hits.len() >= 2, "both identical memories must be recalled");
assert_eq!(
hits[0].id, original.id,
"the memory ranked first in both content lists must not be displaced by recency alone"
);
}
#[test]
fn recency_never_displaces_a_strong_old_match_with_a_weak_new_one() {
let (_vfs, mut store) = store();
let strong_old = store
.remember(MemoryDraft::new(
"postgres connection pool exhaustion causes request timeouts under load",
))
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(2));
store
.remember(MemoryDraft::new(
"the office coffee machine was replaced last week",
))
.unwrap();
let hits = store
.recall(
Query::new("postgres connection pool exhaustion timeouts")
.recency(true)
.limit(5),
)
.unwrap();
assert!(!hits.is_empty());
assert_eq!(
hits[0].id, strong_old.id,
"a strong content match must win regardless of a newer, unrelated memory"
);
}
#[test]
fn recency_defaults_off_and_is_a_pure_opt_in() {
let (_vfs, mut store) = store();
store
.remember(MemoryDraft::new("the cat sat quietly on the warm mat"))
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(2));
store
.remember(MemoryDraft::new("quarterly corporate tax filing deadline"))
.unwrap();
let without = store
.recall(Query::new("a small feline resting").limit(5))
.unwrap();
let with = store
.recall(Query::new("a small feline resting").recency(true).limit(5))
.unwrap();
assert_eq!(
without.iter().map(|h| h.id).collect::<Vec<_>>(),
with.iter().map(|h| h.id).collect::<Vec<_>>(),
"recency only breaks ties; a clear content winner is unaffected"
);
}
#[test]
fn recency_never_resurfaces_a_superseded_memory() {
let (_vfs, mut store) = store();
let old_fact = store
.remember(MemoryDraft::new(
"the api gateway timeout is thirty seconds",
))
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(2));
let correction = store
.remember(
MemoryDraft::new("the api gateway timeout is sixty seconds")
.supersedes(vec![old_fact.id]),
)
.unwrap();
let hits = store
.recall(Query::new("api gateway timeout").recency(true).limit(10))
.unwrap();
assert!(
hits.iter().all(|h| h.id != old_fact.id),
"a superseded memory must never come back through the recency list"
);
assert!(hits.iter().any(|h| h.id == correction.id));
}
#[test]
fn recency_respects_scope_and_never_reintroduces_an_excluded_memory() {
let (_vfs, mut store) = store();
let alpha = store
.remember(MemoryDraft::new("uses tokio for async runtime").project("alpha"))
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(2));
store
.remember(MemoryDraft::new("uses tokio for async runtime").project("beta"))
.unwrap();
let hits = store
.recall(
Query::new("async runtime")
.scope(Scope::Project("alpha".into()))
.recency(true)
.limit(10),
)
.unwrap();
assert!(
hits.iter().all(|h| h.project.as_deref() == Some("alpha")),
"recency must not pull in the newer beta-scoped memory across scope"
);
assert!(hits.iter().any(|h| h.id == alpha.id));
}
#[test]
fn reopening_with_mismatched_model_is_refused() -> Result<()> {
struct FakeEmbedder;
impl embedmind_core::embed::Embedder for FakeEmbedder {
fn embed(&self, _text: &str) -> Result<Vec<f32>> {
Ok(vec![0.1; 384])
}
fn id(&self) -> embedmind_core::embed::ModelId {
"totally-different-model"
}
fn dims(&self) -> u16 {
384
}
}
let vfs: Arc<dyn Vfs> = Arc::new(SimVfs::new());
let real = Arc::new(OnnxEmbedder::load().expect("model must load"));
let opts = StoreOptions {
embedder: Some(real),
..StoreOptions::default()
};
Store::create_with(Arc::clone(&vfs), Path::new(STORE), opts)?.close()?;
let opts = StoreOptions {
embedder: Some(Arc::new(FakeEmbedder)),
..StoreOptions::default()
};
match Store::open_with(vfs, Path::new(STORE), opts) {
Ok(_) => panic!("mismatched model must be refused"),
Err(embedmind_core::Error::InvalidArgument(_)) => {}
Err(other) => panic!("expected InvalidArgument, got {other:?}"),
}
Ok(())
}