use shodh_memory::embeddings::ner::{NerConfig, NeuralNer};
use shodh_memory::memory::{EntityRef, Experience, ExperienceType, Memory, MemoryId, MemoryTier};
use shodh_memory::uuid::Uuid;
fn setup_fallback_ner() -> NeuralNer {
let config = NerConfig::default();
NeuralNer::new_fallback(config)
}
fn create_experience_with_ner(
content: &str,
exp_type: ExperienceType,
ner: &NeuralNer,
) -> Experience {
let entities = ner.extract(content).unwrap_or_default();
let entity_names: Vec<String> = entities.iter().map(|e| e.text.clone()).collect();
Experience {
experience_type: exp_type,
content: content.to_string(),
entities: entity_names,
..Default::default()
}
}
#[test]
fn test_memory_new_defaults() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
assert_eq!(memory.tier, MemoryTier::Working);
assert!(memory.entity_refs.is_empty());
assert!((memory.activation() - 1.0).abs() < f32::EPSILON);
assert!(memory.last_retrieval_id.is_none());
}
#[test]
fn test_memory_new_with_all_params() {
let id = Uuid::new_v4();
let memory = Memory::new(
MemoryId(id),
Experience {
content: "Test content".to_string(),
experience_type: ExperienceType::Decision,
..Default::default()
},
0.8,
Some("agent-1".to_string()),
Some("run-1".to_string()),
Some("actor-1".to_string()),
None, );
assert_eq!(memory.id.0, id);
assert_eq!(memory.experience.content, "Test content");
assert!((memory.importance() - 0.8).abs() < f32::EPSILON);
assert_eq!(memory.agent_id, Some("agent-1".to_string()));
assert_eq!(memory.run_id, Some("run-1".to_string()));
assert_eq!(memory.actor_id, Some("actor-1".to_string()));
}
#[test]
fn test_add_entity_ref() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let entity_id = Uuid::new_v4();
memory.add_entity_ref(entity_id, "robot".to_string(), "mentioned".to_string());
assert_eq!(memory.entity_refs.len(), 1);
assert_eq!(memory.entity_refs[0].entity_id, entity_id);
assert_eq!(memory.entity_refs[0].name, "robot");
assert_eq!(memory.entity_refs[0].relation, "mentioned");
}
#[test]
fn test_add_entity_ref_no_duplicates() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let entity_id = Uuid::new_v4();
memory.add_entity_ref(entity_id, "robot".to_string(), "mentioned".to_string());
memory.add_entity_ref(entity_id, "robot".to_string(), "mentioned".to_string());
memory.add_entity_ref(entity_id, "robot".to_string(), "subject".to_string());
assert_eq!(
memory.entity_refs.len(),
1,
"Should not add duplicate entity"
);
}
#[test]
fn test_add_multiple_entity_refs() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let entity1 = Uuid::new_v4();
let entity2 = Uuid::new_v4();
let entity3 = Uuid::new_v4();
memory.add_entity_ref(entity1, "robot".to_string(), "subject".to_string());
memory.add_entity_ref(entity2, "warehouse".to_string(), "location".to_string());
memory.add_entity_ref(entity3, "obstacle".to_string(), "mentioned".to_string());
assert_eq!(memory.entity_refs.len(), 3);
}
#[test]
fn test_entity_ids() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let entity1 = Uuid::new_v4();
let entity2 = Uuid::new_v4();
memory.add_entity_ref(entity1, "a".to_string(), "x".to_string());
memory.add_entity_ref(entity2, "b".to_string(), "y".to_string());
let ids = memory.entity_ids();
assert_eq!(ids.len(), 2);
assert!(ids.contains(&entity1));
assert!(ids.contains(&entity2));
}
#[test]
fn test_memory_tier_default() {
let tier = MemoryTier::default();
assert_eq!(tier, MemoryTier::Working);
}
#[test]
fn test_memory_tier_promotion() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
assert_eq!(memory.tier, MemoryTier::Working);
memory.promote();
assert_eq!(memory.tier, MemoryTier::Session);
memory.promote();
assert_eq!(memory.tier, MemoryTier::LongTerm);
memory.promote();
assert_eq!(memory.tier, MemoryTier::Archive);
memory.promote();
assert_eq!(memory.tier, MemoryTier::Archive, "Should stay at Archive");
}
#[test]
fn test_memory_tier_demotion() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.tier = MemoryTier::Archive;
memory.demote();
assert_eq!(memory.tier, MemoryTier::LongTerm);
memory.demote();
assert_eq!(memory.tier, MemoryTier::Session);
memory.demote();
assert_eq!(memory.tier, MemoryTier::Working);
memory.demote();
assert_eq!(memory.tier, MemoryTier::Working, "Should stay at Working");
}
#[test]
fn test_tier_cycle() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
for _ in 0..10 {
memory.promote();
}
assert_eq!(memory.tier, MemoryTier::Archive);
for _ in 0..10 {
memory.demote();
}
assert_eq!(memory.tier, MemoryTier::Working);
}
#[test]
fn test_activation_default() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
assert!(
(memory.activation() - 1.0).abs() < f32::EPSILON,
"New memory should be fully activated"
);
}
#[test]
fn test_activate_increases() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(0.5);
memory.activate(0.3);
assert!((memory.activation() - 0.8).abs() < f32::EPSILON);
}
#[test]
fn test_activate_capped_at_one() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(0.9);
memory.activate(0.5);
assert!(
(memory.activation() - 1.0).abs() < f32::EPSILON,
"Activation should cap at 1.0"
);
}
#[test]
fn test_decay_activation() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(1.0);
memory.decay_activation(0.9);
assert!((memory.activation() - 0.9).abs() < f32::EPSILON);
}
#[test]
fn test_decay_activation_multiple() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(1.0);
for _ in 0..10 {
memory.decay_activation(0.9);
}
assert!(memory.activation() > 0.3 && memory.activation() < 0.4);
}
#[test]
fn test_decay_activation_approaches_zero() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(1.0);
for _ in 0..100 {
memory.decay_activation(0.9);
}
assert!(
memory.activation() < 0.001,
"Should approach zero with enough decay"
);
}
#[test]
fn test_activate_then_decay() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(0.5);
memory.activate(0.3); memory.decay_activation(0.5);
assert!((memory.activation() - 0.4).abs() < f32::EPSILON);
}
#[test]
fn test_mark_retrieved() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
assert!(memory.last_retrieval_id.is_none());
let retrieval_id = Uuid::new_v4();
memory.mark_retrieved(retrieval_id);
assert_eq!(memory.last_retrieval_id, Some(retrieval_id));
}
#[test]
fn test_mark_retrieved_updates_access() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let initial_access = memory.access_count();
memory.mark_retrieved(Uuid::new_v4());
assert_eq!(memory.access_count(), initial_access + 1);
}
#[test]
fn test_mark_retrieved_multiple() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let retrieval1 = Uuid::new_v4();
let retrieval2 = Uuid::new_v4();
memory.mark_retrieved(retrieval1);
assert_eq!(memory.last_retrieval_id, Some(retrieval1));
memory.mark_retrieved(retrieval2);
assert_eq!(
memory.last_retrieval_id,
Some(retrieval2),
"Should update to latest"
);
}
#[test]
fn test_importance_getter() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.75,
None,
None,
None,
None,
);
assert!((memory.importance() - 0.75).abs() < f32::EPSILON);
}
#[test]
fn test_importance_update() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_importance(0.9);
assert!((memory.importance() - 0.9).abs() < f32::EPSILON);
}
#[test]
fn test_importance_clamped() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_importance(1.5);
assert!(
(memory.importance() - 1.0).abs() < f32::EPSILON,
"Should clamp at 1.0"
);
memory.set_importance(-0.5);
assert!(
(memory.importance() - 0.0).abs() < f32::EPSILON,
"Should clamp at 0.0"
);
}
#[test]
fn test_boost_importance() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.boost_importance(0.2);
assert!((memory.importance() - 0.7).abs() < f32::EPSILON);
}
#[test]
fn test_decay_importance() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.decay_importance(0.1);
assert!((memory.importance() - 0.45).abs() < f32::EPSILON);
}
#[test]
fn test_access_count_initial() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
assert_eq!(memory.access_count(), 0);
}
#[test]
fn test_record_access() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.record_access();
assert_eq!(memory.access_count(), 1);
memory.record_access();
assert_eq!(memory.access_count(), 2);
}
#[test]
fn test_record_access_many() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
for _ in 0..1000 {
memory.record_access();
}
assert_eq!(memory.access_count(), 1000);
}
#[test]
fn test_bincode_roundtrip_basic() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience {
content: "Test content".to_string(),
..Default::default()
},
0.5,
None,
None,
None,
None,
);
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(memory.id.0, deserialized.id.0);
assert_eq!(memory.experience.content, deserialized.experience.content);
assert!((memory.importance() - deserialized.importance()).abs() < f32::EPSILON);
}
#[test]
fn test_bincode_roundtrip_with_entity_refs() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let entity1 = Uuid::new_v4();
let entity2 = Uuid::new_v4();
memory.add_entity_ref(entity1, "robot".to_string(), "subject".to_string());
memory.add_entity_ref(entity2, "warehouse".to_string(), "location".to_string());
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(deserialized.entity_refs.len(), 2);
assert_eq!(deserialized.entity_refs[0].entity_id, entity1);
assert_eq!(deserialized.entity_refs[1].entity_id, entity2);
}
#[test]
fn test_bincode_roundtrip_with_tier() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.tier = MemoryTier::LongTerm;
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(deserialized.tier, MemoryTier::LongTerm);
}
#[test]
fn test_bincode_roundtrip_with_activation() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(0.42);
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert!((deserialized.activation() - 0.42).abs() < f32::EPSILON);
}
#[test]
fn test_bincode_roundtrip_with_retrieval_id() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
let retrieval_id = Uuid::new_v4();
memory.last_retrieval_id = Some(retrieval_id);
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(deserialized.last_retrieval_id, Some(retrieval_id));
}
#[test]
fn test_bincode_roundtrip_full() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience {
content: "Full test with all fields".to_string(),
experience_type: ExperienceType::Decision,
entities: vec!["entity1".to_string(), "entity2".to_string()],
..Default::default()
},
0.85,
Some("agent-1".to_string()),
Some("run-1".to_string()),
Some("actor-1".to_string()),
None, );
let entity1 = Uuid::new_v4();
memory.add_entity_ref(entity1, "robot".to_string(), "subject".to_string());
memory.tier = MemoryTier::Session;
memory.set_activation(0.75);
memory.last_retrieval_id = Some(Uuid::new_v4());
for _ in 0..5 {
memory.record_access();
}
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(memory.id.0, deserialized.id.0);
assert_eq!(memory.experience.content, deserialized.experience.content);
assert!((memory.importance() - deserialized.importance()).abs() < f32::EPSILON);
assert_eq!(memory.agent_id, deserialized.agent_id);
assert_eq!(memory.run_id, deserialized.run_id);
assert_eq!(memory.actor_id, deserialized.actor_id);
assert_eq!(memory.entity_refs.len(), deserialized.entity_refs.len());
assert_eq!(memory.tier, deserialized.tier);
assert!((memory.activation() - deserialized.activation()).abs() < f32::EPSILON);
assert_eq!(memory.last_retrieval_id, deserialized.last_retrieval_id);
assert_eq!(memory.access_count(), deserialized.access_count());
}
#[test]
fn test_json_roundtrip() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience {
content: "JSON test".to_string(),
..Default::default()
},
0.5,
None,
None,
None,
None,
);
let json = serde_json::to_string(&memory).expect("Failed to serialize JSON");
let deserialized: Memory = serde_json::from_str(&json).expect("Failed to deserialize JSON");
assert_eq!(memory.id.0, deserialized.id.0);
assert_eq!(memory.experience.content, deserialized.experience.content);
}
#[test]
fn test_empty_content() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience {
content: "".to_string(),
..Default::default()
},
0.5,
None,
None,
None,
None,
);
assert!(memory.experience.content.is_empty());
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert!(deserialized.experience.content.is_empty());
}
#[test]
fn test_very_long_content() {
let long_content = "x".repeat(100_000);
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience {
content: long_content.clone(),
..Default::default()
},
0.5,
None,
None,
None,
None,
);
assert_eq!(memory.experience.content.len(), 100_000);
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(deserialized.experience.content.len(), 100_000);
}
#[test]
fn test_unicode_content() {
let unicode_content = "こんにちは世界 🤖 émoji 中文 العربية";
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience {
content: unicode_content.to_string(),
..Default::default()
},
0.5,
None,
None,
None,
None,
);
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(deserialized.experience.content, unicode_content);
}
#[test]
fn test_many_entity_refs() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
for i in 0..1000 {
memory.add_entity_ref(
Uuid::new_v4(),
format!("entity_{}", i),
"mentioned".to_string(),
);
}
assert_eq!(memory.entity_refs.len(), 1000);
let serialized = bincode::serde::encode_to_vec(&memory, bincode::config::standard())
.expect("Failed to serialize");
let (deserialized, _): (Memory, _) =
bincode::serde::decode_from_slice(&serialized, bincode::config::standard())
.expect("Failed to deserialize");
assert_eq!(deserialized.entity_refs.len(), 1000);
}
#[test]
fn test_zero_importance() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.0,
None,
None,
None,
None,
);
assert!((memory.importance() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_max_importance() {
let memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
1.0,
None,
None,
None,
None,
);
assert!((memory.importance() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_zero_activation() {
let mut memory = Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None,
);
memory.set_activation(0.0);
memory.activate(0.0);
assert!((memory.activation() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_concurrent_access_count() {
use std::sync::Arc;
use std::thread;
let memory = Arc::new(Memory::new(
MemoryId(Uuid::new_v4()),
Experience::default(),
0.5,
None,
None,
None,
None, ));
let mut handles = vec![];
for _ in 0..10 {
let mem = Arc::clone(&memory);
let handle = thread::spawn(move || {
for _ in 0..100 {
let _ = mem.access_count();
let _ = mem.importance();
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread panicked");
}
}
#[test]
fn test_clone_independence() {
let memory1 = Memory::new(
MemoryId(Uuid::new_v4()),
Experience {
content: "Original".to_string(),
..Default::default()
},
0.5,
None,
None,
None,
None,
);
let mut memory2 = memory1.clone();
memory2.tier = MemoryTier::LongTerm;
memory2.set_activation(0.1);
assert_eq!(memory1.tier, MemoryTier::Working);
assert!((memory1.activation() - 1.0).abs() < f32::EPSILON);
}