use hippmem_core::model::enums::ContentType;
use hippmem_core::model::links::RetrievalMode;
use hippmem_core::model::unit::{MemoryUnit, WriteContext};
use hippmem_engine::{
ConsolidationScope, Engine, EngineConfig, InspectQuery, InspectReport, RetrieveContext,
RetrieveInput, WriteMemoryInput,
};
use tempfile::tempdir;
fn ctx() -> WriteContext {
WriteContext {
conversation_id: Some(1),
session_id: Some(1),
project_id: None,
task_id: None,
user_id: None,
local_time: hippmem_core::time::Timestamp(1_700_000_000_000),
preceding_memory_ids: vec![],
source_refs: vec![],
}
}
fn default_retrieve_ctx() -> RetrieveContext {
RetrieveContext {
conversation_id: None,
session_id: None,
project_id: None,
task_id: None,
user_id: None,
recent_memory_ids: vec![],
}
}
#[test]
fn public_api_no_backend_leak() {
let dir = tempdir().unwrap();
let engine = Engine::open(EngineConfig {
store_dir: dir.path().join("hippmem.redb"),
..Default::default()
})
.unwrap();
let output = engine
.write(WriteMemoryInput {
content: "HIPPMEM compliance test".into(),
content_type: Some(ContentType::ProjectKnowledge),
context: ctx(),
importance_hint: Some(0.8),
source_refs: vec![],
})
.unwrap();
let retrieve = engine
.retrieve(RetrieveInput {
query: "HIPPMEM".into(),
context: default_retrieve_ctx(),
top_k: 5,
max_hops: None,
retrieval_mode: RetrievalMode::Balanced,
})
.unwrap();
assert!(
!retrieve.results.is_empty(),
"should retrieve the written memory"
);
let stats = engine.inspect(InspectQuery::StoreStats).unwrap();
if let InspectReport::StoreStats(s) = stats {
assert!(s.memory_count >= 1, "store should have at least 1 memory");
}
engine
.feedback(hippmem_engine::FeedbackInput {
retrieval_id: 1,
used_memory_ids: vec![output.memory_id],
signal: hippmem_engine::UsageSignal::Referenced,
})
.unwrap();
let report = engine.consolidate(ConsolidationScope::Full).unwrap();
assert!(report.memories_processed >= 1);
engine.close().unwrap();
}
#[test]
fn reindex_retrieval_consistent() {
let dir = tempdir().unwrap();
let store_path = dir.path().join("hippmem.redb");
let _written_ids = {
let engine = Engine::open(EngineConfig {
store_dir: store_path.clone(),
..Default::default()
})
.unwrap();
let mut ids = Vec::new();
for i in 0..5 {
let output = engine
.write(WriteMemoryInput {
content: format!("Rust programming {}", i),
content_type: Some(ContentType::UserStatement),
context: ctx(),
importance_hint: None,
source_refs: vec![],
})
.unwrap();
ids.push(output.memory_id);
}
let pre = engine
.retrieve(RetrieveInput {
query: "Rust".into(),
context: default_retrieve_ctx(),
top_k: 5,
max_hops: None,
retrieval_mode: RetrievalMode::Balanced,
})
.unwrap();
let pre_count = pre.results.len();
let report = engine.consolidate(ConsolidationScope::Reindex).unwrap();
assert!(report.reindexed, "reindex report should set reindexed=true");
let post = engine
.retrieve(RetrieveInput {
query: "Rust".into(),
context: default_retrieve_ctx(),
top_k: 5,
max_hops: None,
retrieval_mode: RetrievalMode::Balanced,
})
.unwrap();
assert_eq!(
post.results.len(),
pre_count,
"result count should be consistent after reindex"
);
engine.close().unwrap();
ids
};
{
let engine = Engine::open(EngineConfig {
store_dir: store_path,
..Default::default()
})
.unwrap();
let retrieve = engine
.retrieve(RetrieveInput {
query: "Rust".into(),
context: default_retrieve_ctx(),
top_k: 5,
max_hops: None,
retrieval_mode: RetrievalMode::Balanced,
})
.unwrap();
assert!(
!retrieve.results.is_empty(),
"retrieve should still return results after reopen"
);
engine.close().unwrap();
}
}
#[test]
fn memory_unit_bincode_roundtrip_no_field_loss() {
let dir = tempdir().unwrap();
let engine = Engine::open(EngineConfig {
store_dir: dir.path().join("hippmem.redb"),
..Default::default()
})
.unwrap();
let output = engine
.write(WriteMemoryInput {
content: "serialization roundtrip test memory".into(),
content_type: Some(ContentType::Decision),
context: ctx(),
importance_hint: Some(0.7),
source_refs: vec![],
})
.unwrap();
if let Ok(InspectReport::Memory(inspect)) =
engine.inspect(InspectQuery::Memory(output.memory_id))
{
let unit = &inspect.unit;
let encoded = bincode::serde::encode_to_vec(unit, bincode::config::standard()).unwrap();
let (decoded, _len): (MemoryUnit, _) =
bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
assert_eq!(decoded.id, unit.id, "id should match");
assert_eq!(
decoded.schema_version, unit.schema_version,
"schema_version should match"
);
assert_eq!(
decoded.content.raw, unit.content.raw,
"content.raw should match"
);
assert_eq!(decoded.content.content_type, unit.content.content_type);
assert_eq!(decoded.lifecycle, unit.lifecycle, "lifecycle should match");
assert_eq!(decoded.stage, unit.stage, "stage should match");
assert_eq!(
decoded.understanding.importance.value(),
unit.understanding.importance.value(),
"importance should match"
);
assert_eq!(
decoded.links.len(),
unit.links.len(),
"links count should match"
);
assert_eq!(
decoded.context.conversation_id,
unit.context.conversation_id
);
} else {
panic!("should be able to inspect the written memory");
}
engine.close().unwrap();
}
#[test]
fn inverted_index_immediately_readable() {
let dir = tempdir().unwrap();
let engine = Engine::open(EngineConfig {
store_dir: dir.path().join("hippmem.redb"),
..Default::default()
})
.unwrap();
let _output = engine
.write(WriteMemoryInput {
content: "HippMem project test".into(),
content_type: Some(ContentType::ProjectKnowledge),
context: ctx(),
importance_hint: None,
source_refs: vec![],
})
.unwrap();
let retrieve = engine
.retrieve(RetrieveInput {
query: "HippMem".into(),
context: default_retrieve_ctx(),
top_k: 5,
max_hops: None,
retrieval_mode: RetrievalMode::Balanced,
})
.unwrap();
assert!(
!retrieve.results.is_empty(),
"index should be immediately readable after write"
);
let has_entity = retrieve.trace.seeds.iter().any(|s| {
matches!(
s.channel,
hippmem_core::model::links::RecallChannel::EntityInverted
)
});
let _ = has_entity;
engine.close().unwrap();
}
#[test]
fn retrieve_includes_explanation_path() {
let dir = tempdir().unwrap();
let engine = Engine::open(EngineConfig {
store_dir: dir.path().join("hippmem.redb"),
..Default::default()
})
.unwrap();
for i in 0..3 {
engine
.write(WriteMemoryInput {
content: format!("Memory engine HippMem item #{}", i),
content_type: Some(ContentType::ProjectKnowledge),
context: ctx(),
importance_hint: None,
source_refs: vec![],
})
.unwrap();
}
let retrieve = engine
.retrieve(RetrieveInput {
query: "HippMem".into(),
context: default_retrieve_ctx(),
top_k: 3,
max_hops: None,
retrieval_mode: RetrievalMode::Balanced,
})
.unwrap();
for result in &retrieve.results {
assert!(
!result.activation_trace.is_empty(),
"each result should have activation_trace (constitution C4)"
);
let _dims = &result.matched_dimensions;
let _warns = &result.warnings;
}
assert!(
!retrieve.trace.seeds.is_empty(),
"retrieval trace should have seed records (constitution C4)"
);
assert!(
!retrieve.diagnostics.channel_contributions.is_empty(),
"diagnostics should have channel_contributions (constitution C9)"
);
engine.close().unwrap();
}