#![cfg(feature = "integration")]
use std::time::Duration;
use memoir_core::memory::ForgetTarget;
mod common;
const EXTRACTION_TIMEOUT: Duration = Duration::from_secs(120);
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn should_extract_semantic_memories_from_episodic_content() -> anyhow::Result<()> {
let client = common::fresh_client_with_extraction().await?;
let scope = common::fresh_scope();
let content = "Alice works at Acme Corp as a senior engineer. She lives in Berlin.";
let _ = client.remember(content, scope.clone()).await?;
let episodic_pid =
common::wait_for_first_pid(&client, &scope, content, Duration::from_secs(15)).await?;
let semantics = common::wait_until_extracted(
&client,
&scope,
&episodic_pid,
EXTRACTION_TIMEOUT,
)
.await?;
assert!(
!semantics.is_empty(),
"expected at least one semantic memory derived from {episodic_pid}",
);
for m in &semantics {
assert_eq!(
m.source_pid.as_deref(),
Some(episodic_pid.as_str()),
"semantic memory {} should reference source {}",
m.pid,
episodic_pid,
);
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn should_cascade_delete_semantic_memories_when_episodic_is_forgotten() -> anyhow::Result<()> {
let client = common::fresh_client_with_extraction().await?;
let scope = common::fresh_scope();
let content = "Bob is a researcher at MIT studying quantum cryptography.";
let _ = client.remember(content, scope.clone()).await?;
let episodic_pid =
common::wait_for_first_pid(&client, &scope, content, Duration::from_secs(15)).await?;
let semantics_before = common::wait_until_extracted(
&client,
&scope,
&episodic_pid,
EXTRACTION_TIMEOUT,
)
.await?;
assert!(
!semantics_before.is_empty(),
"expected at least one semantic memory before forget",
);
let deleted = client.forget(ForgetTarget::Pid(episodic_pid.clone())).await?;
assert!(
deleted.contains(&episodic_pid),
"forget must return the deleted episodic pid; got {deleted:?}",
);
for semantic in &semantics_before {
assert!(
deleted.contains(&semantic.pid),
"forget must return cascade-deleted semantic pid {} for vector eviction; got {deleted:?}",
semantic.pid,
);
}
use memoir_core::store::MemoryStore;
let surviving_pids = client.store().indexed_pids_in_scope(&scope).await?;
let surviving_refs: Vec<&str> = surviving_pids.iter().map(String::as_str).collect();
let surviving = client.store().find_by_pids(&surviving_refs).await?;
let still_linked: Vec<_> = surviving
.iter()
.filter(|m| m.source_pid.as_deref() == Some(episodic_pid.as_str()))
.collect();
assert!(
still_linked.is_empty(),
"semantic derivatives should cascade-delete with episodic source; survived: {:?}",
still_linked.iter().map(|m| &m.pid).collect::<Vec<_>>(),
);
Ok(())
}