use crate::Storage;
use codemem_core::MemoryNode;
use std::collections::HashMap;
fn test_memory_with_metadata(
content: &str,
tool: &str,
extra: HashMap<String, serde_json::Value>,
) -> MemoryNode {
let mut metadata = extra;
metadata.insert(
"tool".to_string(),
serde_json::Value::String(tool.to_string()),
);
let mut m = MemoryNode::test_default(content);
m.metadata = metadata;
m
}
#[test]
fn stats() {
let storage = Storage::open_in_memory().unwrap();
let stats = storage.stats().unwrap();
assert_eq!(stats.memory_count, 0);
}
#[test]
fn get_repeated_searches_groups_by_pattern() {
let storage = Storage::open_in_memory().unwrap();
for i in 0..3 {
let mut extra = HashMap::new();
extra.insert(
"pattern".to_string(),
serde_json::Value::String("error".to_string()),
);
let mem = test_memory_with_metadata(&format!("grep search {i} for error"), "Grep", extra);
storage.insert_memory(&mem).unwrap();
}
let mut extra = HashMap::new();
extra.insert(
"pattern".to_string(),
serde_json::Value::String("*.rs".to_string()),
);
let mem = test_memory_with_metadata("glob search for rs files", "Glob", extra);
storage.insert_memory(&mem).unwrap();
let results = storage.get_repeated_searches(2, None).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].0, "error");
assert_eq!(results[0].1, 3);
assert_eq!(results[0].2.len(), 3);
let results = storage.get_repeated_searches(1, None).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn get_file_hotspots_groups_by_file_path() {
let storage = Storage::open_in_memory().unwrap();
for i in 0..4 {
let mut extra = HashMap::new();
extra.insert(
"file_path".to_string(),
serde_json::Value::String("src/main.rs".to_string()),
);
let mem = test_memory_with_metadata(&format!("read main.rs attempt {i}"), "Read", extra);
storage.insert_memory(&mem).unwrap();
}
let mut extra = HashMap::new();
extra.insert(
"file_path".to_string(),
serde_json::Value::String("src/lib.rs".to_string()),
);
let mem = test_memory_with_metadata("read lib.rs", "Read", extra);
storage.insert_memory(&mem).unwrap();
let results = storage.get_file_hotspots(3, None).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].0, "src/main.rs");
assert_eq!(results[0].1, 4);
}
#[test]
fn get_tool_usage_stats_counts_by_tool() {
let storage = Storage::open_in_memory().unwrap();
for i in 0..5 {
let mem = test_memory_with_metadata(&format!("read file {i}"), "Read", HashMap::new());
storage.insert_memory(&mem).unwrap();
}
for i in 0..3 {
let mem = test_memory_with_metadata(&format!("grep search {i}"), "Grep", HashMap::new());
storage.insert_memory(&mem).unwrap();
}
let mem = test_memory_with_metadata("edit file", "Edit", HashMap::new());
storage.insert_memory(&mem).unwrap();
let stats = storage.get_tool_usage_stats(None).unwrap();
let stats_map: HashMap<String, usize> = stats.into_iter().collect();
assert_eq!(stats_map.get("Read"), Some(&5));
assert_eq!(stats_map.get("Grep"), Some(&3));
assert_eq!(stats_map.get("Edit"), Some(&1));
}
#[test]
fn get_decision_chains_groups_edits_by_file() {
let storage = Storage::open_in_memory().unwrap();
for i in 0..3 {
let mut extra = HashMap::new();
extra.insert(
"file_path".to_string(),
serde_json::Value::String("src/main.rs".to_string()),
);
let mem = test_memory_with_metadata(&format!("edit main.rs {i}"), "Edit", extra);
storage.insert_memory(&mem).unwrap();
}
let mut extra = HashMap::new();
extra.insert(
"file_path".to_string(),
serde_json::Value::String("src/new.rs".to_string()),
);
let mem = test_memory_with_metadata("write new.rs", "Write", extra);
storage.insert_memory(&mem).unwrap();
let results = storage.get_decision_chains(2, None).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].0, "src/main.rs");
assert_eq!(results[0].1, 3);
}
#[test]
fn pattern_queries_empty_db() {
let storage = Storage::open_in_memory().unwrap();
let searches = storage.get_repeated_searches(1, None).unwrap();
assert!(searches.is_empty());
let hotspots = storage.get_file_hotspots(1, None).unwrap();
assert!(hotspots.is_empty());
let stats = storage.get_tool_usage_stats(None).unwrap();
assert!(stats.is_empty());
let chains = storage.get_decision_chains(1, None).unwrap();
assert!(chains.is_empty());
}
#[test]
fn pattern_queries_with_namespace_filter() {
let storage = Storage::open_in_memory().unwrap();
for i in 0..3 {
let mut extra = HashMap::new();
extra.insert(
"pattern".to_string(),
serde_json::Value::String("error".to_string()),
);
let mut mem = test_memory_with_metadata(&format!("ns-a grep {i}"), "Grep", extra);
mem.namespace = Some("project-a".to_string());
storage.insert_memory(&mem).unwrap();
}
for i in 0..2 {
let mut extra = HashMap::new();
extra.insert(
"pattern".to_string(),
serde_json::Value::String("error".to_string()),
);
let mut mem = test_memory_with_metadata(&format!("ns-b grep {i}"), "Grep", extra);
mem.namespace = Some("project-b".to_string());
storage.insert_memory(&mem).unwrap();
}
let results = storage.get_repeated_searches(1, None).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].1, 5);
let results = storage.get_repeated_searches(1, Some("project-a")).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].1, 3);
}
#[test]
fn session_lifecycle() {
let storage = Storage::open_in_memory().unwrap();
storage.start_session("sess-1", Some("my-project")).unwrap();
let sessions = storage.list_sessions(Some("my-project")).unwrap();
assert_eq!(sessions.len(), 1);
assert_eq!(sessions[0].id, "sess-1");
assert_eq!(sessions[0].namespace, Some("my-project".to_string()));
assert!(sessions[0].ended_at.is_none());
storage
.end_session("sess-1", Some("Explored the codebase"))
.unwrap();
let sessions = storage.list_sessions(None).unwrap();
assert_eq!(sessions.len(), 1);
assert!(sessions[0].ended_at.is_some());
assert_eq!(
sessions[0].summary,
Some("Explored the codebase".to_string())
);
}
#[test]
fn list_sessions_filters_by_namespace() {
let storage = Storage::open_in_memory().unwrap();
storage.start_session("sess-a", Some("project-a")).unwrap();
storage.start_session("sess-b", Some("project-b")).unwrap();
storage.start_session("sess-c", None).unwrap();
let all = storage.list_sessions(None).unwrap();
assert_eq!(all.len(), 3);
let proj_a = storage.list_sessions(Some("project-a")).unwrap();
assert_eq!(proj_a.len(), 1);
assert_eq!(proj_a[0].id, "sess-a");
}
#[test]
fn start_session_ignores_duplicate() {
let storage = Storage::open_in_memory().unwrap();
storage.start_session("sess-1", Some("ns")).unwrap();
storage.start_session("sess-1", Some("ns")).unwrap();
let sessions = storage.list_sessions(None).unwrap();
assert_eq!(sessions.len(), 1);
}
fn tagged_memory(content: &str, tags: Vec<String>, namespace: Option<String>) -> MemoryNode {
let mut m = MemoryNode::test_default(content);
m.tags = tags;
m.namespace = namespace;
m
}
#[test]
fn find_memory_ids_by_tag_basic_match() {
let storage = Storage::open_in_memory().unwrap();
let m1 = tagged_memory("tagged memory one", vec!["alpha".to_string()], None);
let m2 = tagged_memory(
"tagged memory two",
vec!["alpha".to_string(), "beta".to_string()],
None,
);
let m3 = tagged_memory("tagged memory three", vec!["beta".to_string()], None);
storage.insert_memory(&m1).unwrap();
storage.insert_memory(&m2).unwrap();
storage.insert_memory(&m3).unwrap();
let ids = storage
.find_memory_ids_by_tag("alpha", None, "nonexistent")
.unwrap();
assert_eq!(ids.len(), 2);
assert!(ids.contains(&m1.id));
assert!(ids.contains(&m2.id));
}
#[test]
fn find_memory_ids_by_tag_empty_tags_not_matched() {
let storage = Storage::open_in_memory().unwrap();
let m1 = tagged_memory("no tags memory", vec![], None);
storage.insert_memory(&m1).unwrap();
let ids = storage
.find_memory_ids_by_tag("anything", None, "nonexistent")
.unwrap();
assert!(ids.is_empty());
}
#[test]
fn find_memory_ids_by_tag_special_characters() {
let storage = Storage::open_in_memory().unwrap();
let m_percent = tagged_memory("percent tag", vec!["100%".to_string()], None);
let m_underscore = tagged_memory("underscore tag", vec!["my_tag".to_string()], None);
let m_quote = tagged_memory("quote tag", vec!["say\"hello".to_string()], None);
storage.insert_memory(&m_percent).unwrap();
storage.insert_memory(&m_underscore).unwrap();
storage.insert_memory(&m_quote).unwrap();
let ids = storage
.find_memory_ids_by_tag("100%", None, "nonexistent")
.unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids[0], m_percent.id);
let ids = storage
.find_memory_ids_by_tag("my_tag", None, "nonexistent")
.unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids[0], m_underscore.id);
let ids = storage
.find_memory_ids_by_tag("say\"hello", None, "nonexistent")
.unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids[0], m_quote.id);
}
#[test]
fn find_memory_ids_by_tag_limit_50_boundary() {
let storage = Storage::open_in_memory().unwrap();
let mut all_ids = Vec::new();
for i in 0..55 {
let m = tagged_memory(
&format!("memory number {i}"),
vec!["shared-tag".to_string()],
None,
);
all_ids.push(m.id.clone());
storage.insert_memory(&m).unwrap();
}
let ids = storage
.find_memory_ids_by_tag("shared-tag", None, "nonexistent")
.unwrap();
assert_eq!(ids.len(), 50, "LIMIT 50 should cap results at 50");
}
#[test]
fn find_memory_ids_by_tag_namespace_isolation() {
let storage = Storage::open_in_memory().unwrap();
let m_ns_a = tagged_memory(
"ns-a memory",
vec!["shared".to_string()],
Some("ns-a".to_string()),
);
let m_ns_b = tagged_memory(
"ns-b memory",
vec!["shared".to_string()],
Some("ns-b".to_string()),
);
storage.insert_memory(&m_ns_a).unwrap();
storage.insert_memory(&m_ns_b).unwrap();
let ids = storage
.find_memory_ids_by_tag("shared", Some("ns-a"), "nonexistent")
.unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids[0], m_ns_a.id);
let ids = storage
.find_memory_ids_by_tag("shared", Some("ns-b"), "nonexistent")
.unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids[0], m_ns_b.id);
}
#[test]
fn find_memory_ids_by_tag_exclude_id() {
let storage = Storage::open_in_memory().unwrap();
let m1 = tagged_memory("first tagged", vec!["mytag".to_string()], None);
let m2 = tagged_memory("second tagged", vec!["mytag".to_string()], None);
storage.insert_memory(&m1).unwrap();
storage.insert_memory(&m2).unwrap();
let ids = storage
.find_memory_ids_by_tag("mytag", None, &m1.id)
.unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids[0], m2.id);
}
#[test]
fn find_memory_ids_by_tag_null_namespace_handling() {
let storage = Storage::open_in_memory().unwrap();
let m_null_ns = tagged_memory("null ns memory", vec!["tag-x".to_string()], None);
let m_with_ns = tagged_memory(
"with ns memory",
vec!["tag-x".to_string()],
Some("proj".to_string()),
);
storage.insert_memory(&m_null_ns).unwrap();
storage.insert_memory(&m_with_ns).unwrap();
let ids = storage
.find_memory_ids_by_tag("tag-x", None, "nonexistent")
.unwrap();
assert_eq!(ids.len(), 1);
assert_eq!(ids[0], m_null_ns.id);
}