use std::collections::HashSet;
use nexus_memory_hooks::{PersistentBuffer, SessionContext};
use tempfile::tempdir;
#[tokio::test]
async fn multi_agent_buffer_isolation() {
let dir = tempdir().unwrap();
let buffer = PersistentBuffer::new(Some(dir.path().to_path_buf()))
.unwrap()
.with_max_entries(100);
let agents = ["codex", "gemini", "hermes"];
for agent in &agents {
buffer.start_buffering(agent).await.unwrap();
let mut ctx = SessionContext::new(*agent);
ctx.add_insight(format!("{agent}-unique-insight"));
buffer
.buffer_context(agent, ctx, "checkpoint")
.await
.unwrap();
}
for agent in &agents {
let status = buffer.get_buffer_status(agent).await.unwrap();
assert_eq!(
status.entries_count, 1,
"{agent} should have exactly 1 buffered entry"
);
}
for agent in &agents {
let status = buffer.get_buffer_status(agent).await.unwrap();
assert_eq!(status.entries_count, 1);
}
}
#[tokio::test]
async fn buffer_entries_stay_separated_after_flush_and_recover() {
let dir = tempdir().unwrap();
let buffer = PersistentBuffer::new(Some(dir.path().to_path_buf()))
.unwrap()
.with_max_entries(1);
buffer.start_buffering("agent-a").await.unwrap();
let mut ctx_a1 = SessionContext::new("agent-a");
ctx_a1.add_insight("agent-a-first");
buffer
.buffer_context("agent-a", ctx_a1, "checkpoint-1")
.await
.unwrap();
let mut ctx_a2 = SessionContext::new("agent-a");
ctx_a2.add_insight("agent-a-second");
buffer
.buffer_context("agent-a", ctx_a2, "checkpoint-2")
.await
.unwrap();
buffer.start_buffering("agent-b").await.unwrap();
let mut ctx_b = SessionContext::new("agent-b");
ctx_b.add_insight("agent-b-only");
buffer
.buffer_context("agent-b", ctx_b, "checkpoint")
.await
.unwrap();
buffer.flush_all().await.unwrap();
let buffer2 = PersistentBuffer::new(Some(dir.path().to_path_buf())).unwrap();
let recovered_a = buffer2
.recover_buffer("agent-a")
.await
.unwrap()
.expect("agent-a buffer should be recoverable");
assert_eq!(
recovered_a.entries.len(),
2,
"agent-a should have 2 entries after recovery"
);
let a_context_types: HashSet<_> = recovered_a
.entries
.iter()
.map(|e| e.context_type.clone())
.collect();
assert!(
a_context_types.contains("checkpoint-1"),
"agent-a should have checkpoint-1 entry"
);
assert!(
a_context_types.contains("checkpoint-2"),
"agent-a should have checkpoint-2 entry"
);
let recovered_b = buffer2
.recover_buffer("agent-b")
.await
.unwrap()
.expect("agent-b buffer should be recoverable");
assert_eq!(
recovered_b.entries.len(),
1,
"agent-b should have 1 entry after recovery"
);
assert_eq!(
recovered_b.entries[0].context.agent_type, "agent-b",
"Recovered agent-b entry should have agent_type 'agent-b'"
);
}
#[tokio::test]
async fn context_types_preserved_through_flush_recover() {
let dir = tempdir().unwrap();
let buffer = PersistentBuffer::new(Some(dir.path().to_path_buf()))
.unwrap()
.with_max_entries(100);
buffer.start_buffering("test-agent").await.unwrap();
let context_types = ["session-start", "checkpoint", "tool-use", "session-end"];
for ct in &context_types {
let mut ctx = SessionContext::new("test-agent");
ctx.add_command(format!("cmd-for-{ct}"));
buffer.buffer_context("test-agent", ctx, ct).await.unwrap();
}
buffer.flush_to_disk("test-agent").await.unwrap();
let buffer2 = PersistentBuffer::new(Some(dir.path().to_path_buf())).unwrap();
let recovered = buffer2
.recover_buffer("test-agent")
.await
.unwrap()
.expect("Buffer should be recoverable");
let recovered_types: HashSet<_> = recovered
.entries
.iter()
.map(|e| e.context_type.as_str())
.collect();
for ct in &context_types {
assert!(
recovered_types.contains(*ct),
"Context type '{ct}' should be preserved through flush/recover"
);
}
}
#[tokio::test]
async fn clear_buffer_removes_memory_and_disk_state() {
let dir = tempdir().unwrap();
let buffer = PersistentBuffer::new(Some(dir.path().to_path_buf()))
.unwrap()
.with_max_entries(1);
buffer.start_buffering("clear-test").await.unwrap();
let mut ctx = SessionContext::new("clear-test");
ctx.add_insight("will-be-cleared");
buffer
.buffer_context("clear-test", ctx, "pre-clear")
.await
.unwrap();
buffer.flush_to_disk("clear-test").await.unwrap();
let buffer_file = dir.path().join("clear-test.json");
assert!(buffer_file.exists(), "Buffer file should exist after flush");
assert!(
buffer.get_buffer_status("clear-test").await.is_some(),
"In-memory status should exist before clear"
);
buffer.clear_buffer("clear-test").await.unwrap();
assert!(
buffer.get_buffer_status("clear-test").await.is_none(),
"In-memory status should be gone after clear"
);
assert!(
!buffer_file.exists(),
"Buffer file should be removed after clear"
);
let buffer2 = PersistentBuffer::new(Some(dir.path().to_path_buf())).unwrap();
assert!(
buffer2
.recover_buffer("clear-test")
.await
.unwrap()
.is_none(),
"Recovery should return None after clear"
);
}
#[tokio::test]
async fn flush_all_handles_multiple_agents() {
let dir = tempdir().unwrap();
let buffer = PersistentBuffer::new(Some(dir.path().to_path_buf()))
.unwrap()
.with_max_entries(100);
let agents = ["alpha", "beta", "gamma", "delta"];
for agent in &agents {
buffer.start_buffering(agent).await.unwrap();
let mut ctx = SessionContext::new(*agent);
ctx.add_insight(format!("{agent}-data"));
buffer
.buffer_context(agent, ctx, "batch-checkpoint")
.await
.unwrap();
}
buffer.flush_all().await.unwrap();
for agent in &agents {
let path = dir.path().join(format!("{agent}.json"));
assert!(
path.exists(),
"Flushed buffer file for {agent} should exist on disk"
);
}
let buffer2 = PersistentBuffer::new(Some(dir.path().to_path_buf())).unwrap();
for agent in &agents {
let recovered = buffer2
.recover_buffer(agent)
.await
.unwrap()
.unwrap_or_else(|| panic!("{agent} should be recoverable"));
assert_eq!(
recovered.entries.len(),
1,
"{agent} should have exactly 1 entry"
);
assert_eq!(
recovered.entries[0].context.agent_type, *agent,
"{agent} recovered entry should reference the correct agent type"
);
}
}