use std::time::Duration;
use basemyai::{AgentId, Memory, MemoryEventKind, MemoryLayer};
use basemyai_core::{Embedder, Result, Store};
const DIM: usize = 384;
struct FakeEmbedder;
impl FakeEmbedder {
fn vec_for(text: &str) -> Vec<f32> {
let mut v = vec![0.0_f32; DIM];
for (i, b) in text.bytes().enumerate() {
v[i % DIM] += f32::from(b) + 1.0;
}
v[0] += 1.0;
v
}
}
impl Embedder for FakeEmbedder {
fn embed(&self, text: &str) -> Result<Vec<f32>> {
Ok(Self::vec_for(text))
}
fn embed_batch(&self, texts: &[String]) -> Result<Vec<Vec<f32>>> {
Ok(texts.iter().map(|t| Self::vec_for(t)).collect())
}
fn model_id(&self) -> &str {
"fake-deterministic"
}
fn dim(&self) -> usize {
DIM
}
}
fn agent(id: &str) -> AgentId {
AgentId::new(id).expect("non-empty agent id")
}
async fn open_memory(agent_id: &str) -> Memory {
let store = Store::open_in_memory().await.expect("open in-memory store");
Memory::open(store, Box::new(FakeEmbedder), agent(agent_id))
.await
.expect("open memory")
}
async fn recv_soon(sub: &mut basemyai::MemorySubscription) -> Option<basemyai::MemoryEvent> {
tokio::time::timeout(Duration::from_millis(300), sub.recv())
.await
.unwrap_or_default()
}
#[tokio::test]
async fn subscription_never_leaks_other_agents_events() {
let path = temp_db_path("events-isolation");
let store_a = Store::open(&path, None).await.expect("open A");
store_a.migrate(&basemyai::schema()).await.expect("migrate A");
let mem_a = Memory::new(store_a, Box::new(FakeEmbedder), agent("agent-a"));
let store_b = Store::open(&path, None).await.expect("open B same db");
store_b.migrate(&basemyai::schema()).await.expect("migrate B");
let mem_b = Memory::new(store_b, Box::new(FakeEmbedder), agent("agent-b' OR '1'='1"));
let mut sub_a = mem_a.watch("agent-a", None);
mem_b
.remember("B private note", MemoryLayer::Semantic)
.await
.expect("B remembers");
assert!(
recv_soon(&mut sub_a).await.is_none(),
"agent A subscription must not receive agent B's event"
);
let id = mem_a
.remember("A private note", MemoryLayer::Semantic)
.await
.expect("A remembers");
let ev = recv_soon(&mut sub_a).await.expect("A receives its own event");
assert_eq!(ev.agent_id, "agent-a");
assert_eq!(ev.kind, MemoryEventKind::Remembered);
assert_eq!(ev.layer, MemoryLayer::Semantic);
assert_eq!(ev.id, id);
}
#[tokio::test]
async fn watch_with_foreign_id_only_sees_that_foreign_agent() {
let mem = open_memory("agent-a").await;
let mut sub_foreign = mem.watch("someone-else", None);
mem.remember("A note", MemoryLayer::Semantic)
.await
.expect("A remembers");
assert!(
recv_soon(&mut sub_foreign).await.is_none(),
"a foreign-id subscription must not receive agent A's events"
);
}
#[tokio::test]
async fn delivers_each_event_kind_with_correct_id() {
let mem = open_memory("agent-a").await;
let mut sub = mem.watch("agent-a", None);
let id = mem
.remember("a memory to mutate", MemoryLayer::Episodic)
.await
.expect("remember");
let ev = recv_soon(&mut sub).await.expect("remembered event");
assert_eq!(ev.kind, MemoryEventKind::Remembered);
assert_eq!(ev.id, id);
assert_eq!(ev.layer, MemoryLayer::Episodic);
mem.invalidate(&id).await.expect("invalidate");
let ev = recv_soon(&mut sub).await.expect("invalidated event");
assert_eq!(ev.kind, MemoryEventKind::Invalidated);
assert_eq!(ev.id, id);
mem.forget(&id).await.expect("forget");
let ev = recv_soon(&mut sub).await.expect("forgotten event");
assert_eq!(ev.kind, MemoryEventKind::Forgotten);
assert_eq!(ev.id, id);
}
#[tokio::test]
async fn batch_remember_emits_one_event_per_record() {
let mem = open_memory("agent-a").await;
let mut sub = mem.watch("agent-a", None);
let texts = vec!["one".to_string(), "two".to_string(), "three".to_string()];
let ids = mem
.remember_batch(&texts, MemoryLayer::Semantic)
.await
.expect("batch remember");
for expected in &ids {
let ev = recv_soon(&mut sub).await.expect("batch event");
assert_eq!(ev.kind, MemoryEventKind::Remembered);
assert_eq!(&ev.id, expected);
}
}
#[tokio::test]
async fn layer_filter_drops_other_layers() {
let mem = open_memory("agent-a").await;
let mut sub = mem.watch("agent-a", Some(MemoryLayer::Semantic));
mem.remember("transient", MemoryLayer::ShortTerm)
.await
.expect("short-term remember");
assert!(
recv_soon(&mut sub).await.is_none(),
"a semantic-only subscription must ignore a short_term event"
);
let id = mem
.remember("durable fact", MemoryLayer::Semantic)
.await
.expect("semantic remember");
let ev = recv_soon(&mut sub).await.expect("semantic event passes the filter");
assert_eq!(ev.layer, MemoryLayer::Semantic);
assert_eq!(ev.id, id);
}
#[tokio::test]
async fn recv_tolerates_lag_and_keeps_delivering() {
let mem = open_memory("agent-a").await;
let mut sub = mem.watch("agent-a", None);
let flood = 1024usize + 50;
let mut last_id = String::new();
for i in 0..flood {
last_id = mem
.remember(&format!("flood-{i}"), MemoryLayer::Semantic)
.await
.expect("flood remember");
}
let ev = recv_soon(&mut sub).await.expect("recv survives lag");
assert_eq!(ev.kind, MemoryEventKind::Remembered);
assert!(
!ev.id.is_empty(),
"a real event id is delivered after lag, not an error"
);
let after = mem
.remember("after the flood", MemoryLayer::Semantic)
.await
.expect("post-flood remember");
let mut saw_after = ev.id == after || last_id == after;
while !saw_after {
match recv_soon(&mut sub).await {
Some(e) if e.id == after => saw_after = true,
Some(_) => continue,
None => break,
}
}
assert!(saw_after, "channel stays live and eventually delivers the post-flood event");
}
#[tokio::test]
async fn remember_with_no_subscriber_succeeds() {
let mem = open_memory("agent-a").await;
let id = mem
.remember("nobody is listening", MemoryLayer::Semantic)
.await
.expect("remember succeeds with no subscriber");
assert!(!id.is_empty());
}
fn current_unix() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
i64::try_from(SystemTime::now().duration_since(UNIX_EPOCH).expect("clock").as_secs()).expect("fits i64")
}
fn temp_db_path(name: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!("basemyai-{name}-{}-{}.db", std::process::id(), current_unix()))
}