use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use skadoosh::memory::MemoryStore;
fn temp_path(label: &str) -> (PathBuf, TempFile) {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut p = std::env::temp_dir();
p.push(format!(
"skadoosh-memory-{}-{label}-{n}.json",
std::process::id()
));
(p.clone(), TempFile(p))
}
struct TempFile(PathBuf);
impl Drop for TempFile {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
#[test]
fn open_missing_file_starts_empty() {
let (path, _guard) = temp_path("missing");
let store = MemoryStore::open(&path);
assert!(store.recall("anything").is_none());
assert!(store.preferences_summary().is_none());
assert!(store.summaries().is_empty());
}
#[test]
fn remember_and_recall_round_trip() {
let (path, _guard) = temp_path("roundtrip");
{
let mut store = MemoryStore::open(&path);
assert!(store.recall("name").is_none(), "nothing remembered yet");
store.remember("name", "Alice");
store.remember("city", "Paris");
assert_eq!(store.recall("name").as_deref(), Some("Alice"));
assert_eq!(store.recall("city").as_deref(), Some("Paris"));
assert!(store.recall("unknown").is_none());
}
let store = MemoryStore::open(&path);
assert_eq!(
store.recall("name").as_deref(),
Some("Alice"),
"persisted across open"
);
assert_eq!(store.recall("city").as_deref(), Some("Paris"));
}
#[test]
fn remember_overwrites_existing_key() {
let (path, _guard) = temp_path("overwrite");
let mut store = MemoryStore::open(&path);
store.remember("color", "red");
store.remember("color", "blue");
assert_eq!(store.recall("color").as_deref(), Some("blue"));
}
#[test]
fn preferences_summary_formats_known_pairs() {
let (path, _guard) = temp_path("prefs");
let mut store = MemoryStore::open(&path);
assert!(store.preferences_summary().is_none(), "empty → no summary");
store.remember("name", "Alice");
store.remember("city", "Paris");
let summary = store.preferences_summary().expect("non-empty");
assert!(summary.contains("name: Alice"), "{summary}");
assert!(summary.contains("city: Paris"), "{summary}");
}
#[test]
fn summarize_turn_appends_and_persists() {
let (path, _guard) = temp_path("summaries");
{
let mut store = MemoryStore::open(&path);
store.summarize_turn("hi there", "hello!");
store.summarize_turn("how are you?", "great");
let summaries = store.summaries();
assert_eq!(summaries.len(), 2);
assert!(summaries[0].contains("hi there"), "{summaries:?}");
assert!(summaries[0].contains("hello!"), "{summaries:?}");
assert!(summaries[1].contains("how are you?"), "{summaries:?}");
assert!(summaries[1].contains("great"), "{summaries:?}");
}
let store = MemoryStore::open(&path);
assert_eq!(
store.summaries().len(),
2,
"summaries persisted across open"
);
}
#[test]
fn summarize_turn_caps_rolling_list() {
let (path, _guard) = temp_path("cap");
let mut store = MemoryStore::open(&path);
for i in 0..15 {
store.summarize_turn(&format!("u{i}"), &format!("a{i}"));
}
let summaries = store.summaries();
assert_eq!(summaries.len(), 10, "rolling list capped at 10");
assert!(
summaries[0].contains("u5"),
"oldest kept: {:?}",
summaries[0]
);
assert!(
summaries[9].contains("u14"),
"newest kept: {:?}",
summaries[9]
);
}
#[test]
fn malformed_file_starts_empty_without_panicking() {
let (path, _guard) = temp_path("malformed");
std::fs::write(&path, b"{ this is not valid json").expect("write junk");
let mut store = MemoryStore::open(&path);
assert!(store.recall("x").is_none());
assert!(store.summaries().is_empty());
store.remember("k", "v");
let store = MemoryStore::open(&path);
assert_eq!(
store.recall("k").as_deref(),
Some("v"),
"recovered after overwrite"
);
}