use std::sync::Arc;
use super::*;
use serde_json::json;
#[tokio::test]
async fn in_memory_store_put_get_delete() {
let store = InMemoryStore::new();
assert_eq!(store.get("ns", "k").await.unwrap(), None);
store.put("ns", "k", json!({"x": 1})).await.unwrap();
assert_eq!(store.get("ns", "k").await.unwrap(), Some(json!({"x": 1})));
store.delete("ns", "k").await.unwrap();
assert_eq!(store.get("ns", "k").await.unwrap(), None);
}
#[tokio::test]
async fn in_memory_store_list() {
let store = InMemoryStore::new();
store.put("ns", "a", json!(1)).await.unwrap();
store.put("ns", "b", json!(2)).await.unwrap();
let mut keys = store.list("ns").await.unwrap();
keys.sort();
assert_eq!(keys, vec!["a", "b"]);
}
#[tokio::test]
async fn store_registry_default_store_is_usable() {
let reg = StoreRegistry::new();
let store = reg.default_store();
store.put("ns", "key", json!("value")).await.unwrap();
assert_eq!(store.get("ns", "key").await.unwrap(), Some(json!("value")));
}
#[tokio::test]
async fn store_registry_named_store() {
let mut reg = StoreRegistry::new();
reg.register("cache", Arc::new(InMemoryStore::new()));
let cache = reg.get("cache").expect("cache store registered");
cache.put("ns", "k", json!(42)).await.unwrap();
assert_eq!(cache.get("ns", "k").await.unwrap(), Some(json!(42)));
assert!(reg.get("missing").is_none());
}
#[tokio::test]
async fn in_memory_delete_missing_is_noop() {
let store = InMemoryStore::new();
store.delete("ns", "never").await.unwrap();
assert!(store.list("empty-ns").await.unwrap().is_empty());
}
#[tokio::test]
async fn in_memory_namespaces_are_isolated() {
let store = InMemoryStore::new();
store.put("a", "k", json!(1)).await.unwrap();
store.put("b", "k", json!(2)).await.unwrap();
assert_eq!(store.get("a", "k").await.unwrap(), Some(json!(1)));
assert_eq!(store.get("b", "k").await.unwrap(), Some(json!(2)));
assert_eq!(store.list("a").await.unwrap(), vec!["k"]);
}
struct TempDir(std::path::PathBuf);
impl TempDir {
fn new(tag: &str) -> Self {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let path = std::env::temp_dir().join(format!("tinyagents-store-test-{tag}-{nanos}"));
std::fs::create_dir_all(&path).unwrap();
Self(path)
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[tokio::test]
async fn file_store_put_get_delete_list() {
let dir = TempDir::new("crud");
let store = FileStore::new(&dir.0);
assert_eq!(store.get("threads", "t1").await.unwrap(), None);
assert!(store.list("threads").await.unwrap().is_empty());
store
.put("threads", "t1", json!({"role": "user"}))
.await
.unwrap();
assert_eq!(
store.get("threads", "t1").await.unwrap(),
Some(json!({"role": "user"}))
);
store.put("threads", "t2", json!(2)).await.unwrap();
store.put("events", "e1", json!("ev")).await.unwrap();
let mut threads = store.list("threads").await.unwrap();
threads.sort();
assert_eq!(threads, vec!["t1", "t2"]);
assert_eq!(store.list("events").await.unwrap(), vec!["e1"]);
store.delete("threads", "t1").await.unwrap();
assert_eq!(store.get("threads", "t1").await.unwrap(), None);
assert_eq!(store.list("threads").await.unwrap(), vec!["t2"]);
store.delete("threads", "absent").await.unwrap();
}
#[tokio::test]
async fn file_store_rejects_unsafe_names() {
let dir = TempDir::new("sanitize");
let store = FileStore::new(&dir.0);
assert!(store.get("../etc", "passwd").await.is_err());
assert!(store.put("ns", "a/b", json!(1)).await.is_err());
assert!(store.delete("ns", "a\\b").await.is_err());
assert!(store.list("with space").await.is_err());
assert!(store.put("", "k", json!(1)).await.is_err());
assert!(store.get("ns", "").await.is_err());
assert!(store.put("..", "k", json!(1)).await.is_err());
assert!(store.get(".", "k").await.is_err());
assert!(store.list("...").await.is_err());
assert!(store.put("ns-1", "key_2.v3", json!(1)).await.is_ok());
assert_eq!(store.get("ns-1", "key_2.v3").await.unwrap(), Some(json!(1)));
}
#[tokio::test]
async fn file_store_overwrites_existing_key() {
let dir = TempDir::new("overwrite");
let store = FileStore::new(&dir.0);
store.put("ns", "k", json!("first")).await.unwrap();
store.put("ns", "k", json!("second")).await.unwrap();
assert_eq!(store.get("ns", "k").await.unwrap(), Some(json!("second")));
}
#[tokio::test]
async fn store_registry_default_store_is_stable() {
let reg = StoreRegistry::new();
reg.default_store().put("ns", "k", json!(1)).await.unwrap();
assert_eq!(
reg.default_store().get("ns", "k").await.unwrap(),
Some(json!(1))
);
}
#[tokio::test]
async fn store_registry_register_replaces() {
let mut reg = StoreRegistry::new();
reg.register("s", Arc::new(InMemoryStore::new()));
reg.get("s")
.unwrap()
.put("ns", "k", json!(1))
.await
.unwrap();
reg.register("s", Arc::new(InMemoryStore::new()));
assert_eq!(reg.get("s").unwrap().get("ns", "k").await.unwrap(), None);
}