use std::fs;
use std::path::Path;
fn setup_test_env() -> (tempfile::TempDir, microscope_memory::config::Config) {
let tmp = tempfile::tempdir().expect("create temp dir");
let output_dir = tmp.path().join("data");
let layers_dir = tmp.path().join("layers");
fs::create_dir_all(&output_dir).unwrap();
fs::create_dir_all(&layers_dir).unwrap();
let layer_content = "Rust is a systems programming language focusing on safety.\n\nMemory management in Rust uses ownership and borrowing.\n\nMicroscope Memory uses hierarchical indexing.";
fs::write(layers_dir.join("long_term.txt"), layer_content).unwrap();
let mut config = microscope_memory::config::Config::default();
config.paths.layers_dir = layers_dir.to_string_lossy().to_string();
config.paths.output_dir = output_dir.to_string_lossy().to_string();
config.paths.temp_dir = tmp.path().join("tmp").to_string_lossy().to_string();
config.memory_layers.layers = vec!["long_term".to_string()];
config.embedding.provider = "mock".to_string();
(tmp, config)
}
#[test]
fn test_full_build_pipeline() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).expect("build failed");
let output_dir = Path::new(&config.paths.output_dir);
assert!(output_dir.join("meta.bin").exists());
assert!(output_dir.join("microscope.bin").exists());
assert!(output_dir.join("data.bin").exists());
}
#[test]
fn test_build_and_read() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).expect("build failed");
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
assert!(reader.block_count > 0);
}
#[test]
fn test_text_search() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).expect("build failed");
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
let results = reader.find_text("Rust", 10);
assert!(!results.is_empty(), "should find 'Rust' in txt file");
}
#[test]
fn test_store_and_recall() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
microscope_memory::store_memory(
&config,
"Test memory about standing on own feet",
"long_term",
5,
)
.expect("store");
let append_path = Path::new(&config.paths.output_dir).join("append.bin");
assert!(append_path.exists());
let entries = microscope_memory::read_append_log(&append_path);
assert!(!entries.is_empty());
}
#[test]
fn test_find_includes_pending_append_entries() {
let (_tmp, mut config) = setup_test_env();
config.index.auto_rebuild = false;
microscope_memory::build::build(&config, true, true).unwrap();
let marker = "pending-search-marker-7f3a";
microscope_memory::store_memory(&config, marker, "long_term", 5).expect("store");
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
let results = reader.find_text_all(&config, marker, 10);
assert!(
results.iter().any(|(_, _, is_main)| !is_main),
"pending append entry should be searchable before rebuild"
);
}
#[test]
fn test_pending_memory_gets_a_vector_and_rebuild_clears_it() {
use microscope_memory::embedding_index::{AppendEmbeddings, APPEND_EMBEDDINGS_FILE};
let (_tmp, mut config) = setup_test_env();
config.index.auto_rebuild = false; config.embedding.provider = "mock".to_string();
config.embedding.dim = 64;
microscope_memory::build::build(&config, true, true).unwrap();
let output_dir = Path::new(&config.paths.output_dir);
let sidecar = output_dir.join(APPEND_EMBEDDINGS_FILE);
microscope_memory::store_memory(
&config,
"The user keeps a vintage bicycle in the hallway.",
"long_term",
5,
)
.expect("store");
assert!(sidecar.exists(), "store should embed the pending memory");
let side = AppendEmbeddings::open(&sidecar, 64).expect("sidecar opens");
assert_eq!(side.entries.len(), 1);
assert_eq!(side.entries[0].0, 0, "recorded under the append index");
assert_eq!(side.entries[0].1.len(), 64);
microscope_memory::store_memory(&config, "ok", "long_term", 5).expect("store fragment");
let side = AppendEmbeddings::open(&sidecar, 64).expect("sidecar still opens");
assert_eq!(
side.entries.len(),
1,
"a text under the character floor must not be embedded"
);
microscope_memory::build::rebuild_pending(&config, false, false).expect("rebuild");
assert!(
!sidecar.exists(),
"sidecar must not survive the append log it was keyed to"
);
}
#[test]
fn emotional_field_candidate_set_matches_a_full_scan() {
use microscope_memory::emotional::emotional_field;
use microscope_memory::hebbian::HebbianState;
let (_tmp, mut config) = setup_test_env();
fs::write(
Path::new(&config.paths.layers_dir).join("emotional.txt"),
"The user felt uneasy about the unannounced change and said so.\n\
The user was delighted by the finished build and said so loudly.\n",
)
.unwrap();
config.memory_layers.layers = vec!["long_term".to_string(), "emotional".to_string()];
microscope_memory::build::build(&config, true, true).unwrap();
let reader = microscope_memory::reader::MicroscopeReader::open(&config).unwrap();
let mut hebb =
HebbianState::load_or_init(Path::new(&config.paths.output_dir), reader.block_count);
let activated: Vec<(u32, f32)> = (0..reader.block_count as u32)
.filter(|i| {
let i = *i as usize;
i < 40 || reader.header(i).layer_id == 4 })
.map(|i| (i, 1.0f32))
.collect();
assert!(!activated.is_empty());
hebb.record_activation(&activated, 99);
let got = emotional_field(&reader, &hebb).expect("activated blocks exist");
let (mut sx, mut sy, mut sz, mut te, mut n) = (0f32, 0f32, 0f32, 0f32, 0usize);
let mut hottest: Option<(usize, u32)> = None;
for i in 0..reader.block_count {
let h = reader.header(i);
if h.layer_id != 4 {
continue;
}
let e = hebb.energy(i);
if e < 0.01 {
continue;
}
sx += h.x * e;
sy += h.y * e;
sz += h.z * e;
te += e;
n += 1;
if hottest.is_none() || e > f32::from_bits(hottest.unwrap().1) {
hottest = Some((i, e.to_bits()));
}
}
assert!(n > 0, "the fixture must contain an emotional-layer block");
assert_eq!(got.active_blocks, n);
assert_eq!(got.total_energy.to_bits(), te.to_bits());
assert_eq!(got.centroid.0.to_bits(), (sx / te).to_bits());
assert_eq!(got.centroid.1.to_bits(), (sy / te).to_bits());
assert_eq!(got.centroid.2.to_bits(), (sz / te).to_bits());
assert_eq!(
got.hottest_block.map(|(i, e)| (i, e.to_bits())),
hottest,
"hottest_block must match the full scan"
);
}
#[test]
fn test_auto_rebuild_after_configured_threshold() {
let (_tmp, mut config) = setup_test_env();
config.index.auto_rebuild = true;
config.index.auto_rebuild_entries = 2;
microscope_memory::build::build(&config, true, true).unwrap();
microscope_memory::store_memory(&config, "auto-rebuild-first", "long_term", 5)
.expect("first store");
let append_path = Path::new(&config.paths.output_dir).join("append.bin");
assert_eq!(microscope_memory::read_append_log(&append_path).len(), 1);
microscope_memory::store_memory(&config, "auto-rebuild-second", "long_term", 5)
.expect("second store");
assert!(
microscope_memory::read_append_log(&append_path).is_empty(),
"append log should be cleared after successful automatic rebuild"
);
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
assert!(!reader.find_text("auto-rebuild-second", 10).is_empty());
}
#[test]
fn test_rebuild_rejects_shrink_and_restores_append_log() {
let (_tmp, mut config) = setup_test_env();
config.index.auto_rebuild = false;
microscope_memory::build::build(&config, true, true).unwrap();
microscope_memory::store_memory(&config, "shrink-guard-pending", "long_term", 5)
.expect("store");
fs::write(
Path::new(&config.paths.layers_dir).join("long_term.txt"),
"tiny replacement source",
)
.unwrap();
let error = microscope_memory::build::rebuild_pending(&config, false, true)
.expect_err("shrinking rebuild must be rejected");
assert!(error.contains("refusing rebuild shrink"));
let append_path = Path::new(&config.paths.output_dir).join("append.bin");
assert!(
microscope_memory::read_append_log(&append_path)
.iter()
.any(|entry| entry.text.contains("shrink-guard-pending")),
"append log must be preserved when rebuild rejects shrink"
);
}
#[test]
fn test_incremental_build_skips() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, false, true).expect("build");
let meta1 = fs::read(Path::new(&config.paths.output_dir).join("meta.bin")).unwrap();
microscope_memory::build::build(&config, false, true).expect("build");
let meta2 = fs::read(Path::new(&config.paths.output_dir).join("meta.bin")).unwrap();
assert_eq!(
meta1, meta2,
"meta.bin should be identical when layers unchanged"
);
}
#[test]
fn test_incremental_build_force() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, false, true).expect("build");
microscope_memory::build::build(&config, true, true).unwrap();
}
#[test]
fn test_mql_query() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
let appended = vec![];
let q = microscope_memory::query::parse("\"Rust\"");
let results = microscope_memory::query::execute(&q, &reader, &appended);
assert!(!results.is_empty());
}
#[test]
fn test_mql_complex_query() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
let appended = vec![];
let q = microscope_memory::query::parse("\"Rust\" AND \"safety\"");
let results = execute_query(&q, &reader, &appended);
assert!(!results.is_empty(), "AND query failed");
let q = microscope_memory::query::parse("\"ownership\" OR \"non-existent\"");
let results = execute_query(&q, &reader, &appended);
assert!(!results.is_empty(), "OR query failed");
let q = microscope_memory::query::parse("layer:long_term depth:3 \"Rust\"");
let results = execute_query(&q, &reader, &appended);
assert!(!results.is_empty(), "Layer/Depth filter failed");
let rust_h = reader.header(0);
let rx = rust_h.x;
let ry = rust_h.y;
let rz = rust_h.z;
let q_str = format!("near:{},{},{},0.5 \"Rust\"", rx, ry, rz);
let q = microscope_memory::query::parse(&q_str);
let results = execute_query(&q, &reader, &appended);
assert!(!results.is_empty(), "Spatial 'near:' filter failed");
}
fn execute_query(
q: µscope_memory::query::Query,
reader: µscope_memory::reader::MicroscopeReader,
appended: &[microscope_memory::AppendEntry],
) -> Vec<microscope_memory::query::QueryResult> {
microscope_memory::query::execute(q, reader, appended)
}
#[test]
fn test_crc_integrity_after_build() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
for i in 0..reader.block_count {
let h = reader.header(i);
let stored = u16::from_le_bytes(h.crc16);
if stored == 0x0000 {
continue;
}
let start = h.data_offset as usize;
let end = start + h.data_len as usize;
let computed = microscope_memory::crc16_ccitt(&reader.data[start..end]);
assert_eq!(stored, computed, "CRC mismatch at block {}", i);
}
}
#[test]
fn test_merkle_integrity_after_build() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let output_dir = Path::new(&config.paths.output_dir);
let merkle_data = fs::read(output_dir.join("merkle.bin")).unwrap();
let tree = microscope_memory::merkle::MerkleTree::from_bytes(&merkle_data).unwrap();
let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
for i in 0..reader.block_count {
let h = reader.header(i);
let start = h.data_offset as usize;
let end = start + h.data_len as usize;
let data = &reader.data[start..end];
assert!(tree.verify_leaf(i, data));
}
}
#[test]
fn test_cross_platform_merkle_consistency() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let output_dir = Path::new(&config.paths.output_dir);
let merkle_data = fs::read(output_dir.join("merkle.bin")).unwrap();
let tree = microscope_memory::merkle::MerkleTree::from_bytes(&merkle_data).unwrap();
let root = tree.root;
let expected_root: [u8; 32] = [
251, 179, 237, 94, 128, 172, 123, 228, 175, 112, 2, 231, 250, 94, 111, 148, 158, 81, 141,
218, 55, 185, 26, 158, 220, 2, 45, 122, 169, 78, 161, 22,
];
assert_eq!(
root, expected_root,
"Merkle root must be consistent across platforms"
);
}
#[test]
fn test_mcp_protocol_compatibility() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let init_request = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {}
});
let init_response = handle_mcp_request(&init_request, &config);
assert_eq!(init_response["jsonrpc"], "2.0");
assert_eq!(init_response["id"], 1);
assert!(init_response.get("result").is_some());
assert_eq!(init_response["result"]["protocolVersion"], "2024-11-05");
assert_eq!(
init_response["result"]["serverInfo"]["name"],
"microscope-memory"
);
let list_request = serde_json::json!({
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
});
let list_response = handle_mcp_request(&list_request, &config);
assert_eq!(list_response["jsonrpc"], "2.0");
assert_eq!(list_response["id"], 2);
let tools = list_response["result"]["tools"].as_array().unwrap();
assert!(!tools.is_empty());
let status_tool = tools.iter().find(|t| t["name"] == "memory_status").unwrap();
assert_eq!(
status_tool["description"],
"Get microscope memory index status: block count, depths, append log size"
);
let call_request = serde_json::json!({
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "memory_status",
"arguments": {}
}
});
let call_response = handle_mcp_request(&call_request, &config);
assert_eq!(call_response["jsonrpc"], "2.0");
assert_eq!(call_response["id"], 3);
let content = call_response["result"]["content"][0]["text"]
.as_str()
.unwrap();
assert!(content.contains("Microscope Memory Status"));
assert!(content.contains("Blocks:"));
}
fn handle_mcp_request(
request: &serde_json::Value,
config: µscope_memory::config::Config,
) -> serde_json::Value {
use serde_json::json;
let id = request.get("id").cloned().unwrap_or(json!(null));
let method = request.get("method").and_then(|m| m.as_str()).unwrap_or("");
match method {
"initialize" => json!({
"jsonrpc": "2.0",
"id": id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": {
"name": "microscope-memory",
"version": env!("CARGO_PKG_VERSION")
}
}
}),
"tools/list" => json!({
"jsonrpc": "2.0",
"id": id,
"result": {
"tools": [
{
"name": "memory_status",
"description": "Get microscope memory index status: block count, depths, append log size",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
}
]
}
}),
"tools/call" => {
let params = request.get("params").cloned().unwrap_or(json!({}));
let tool_name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
match tool_name {
"memory_status" => {
let reader = microscope_memory::reader::MicroscopeReader::open(config).unwrap();
let append_path =
std::path::Path::new(&config.paths.output_dir).join("append.bin");
let appended = microscope_memory::read_append_log(&append_path);
let hdr_kb =
(reader.block_count * microscope_memory::HEADER_SIZE) as f64 / 1024.0;
let data_kb = reader.data.len() as f64 / 1024.0;
let content = format!(
"Microscope Memory Status\n\
========================\n\
Blocks: {}\n\
Headers: {:.1} KB\n\
Data: {:.1} KB\n\
Total: {:.1} KB\n\
Append log: {} entries",
reader.block_count,
hdr_kb,
data_kb,
hdr_kb + data_kb,
appended.len()
);
json!({
"jsonrpc": "2.0",
"id": id,
"result": {
"content": [{"type": "text", "text": content}]
}
})
}
_ => json!({
"jsonrpc": "2.0",
"id": id,
"error": {"code": -32601, "message": "Method not found"}
}),
}
}
_ => json!({
"jsonrpc": "2.0",
"id": id,
"error": {"code": -32601, "message": "Method not found"}
}),
}
}
#[test]
fn test_snapshot_export_import() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let output_dir = Path::new(&config.paths.output_dir);
let archive_path = output_dir.join("test.mscope");
microscope_memory::snapshot::export(output_dir, &archive_path).unwrap();
assert!(archive_path.exists(), "archive should exist");
let restore_dir = output_dir.join("restored");
fs::create_dir_all(&restore_dir).unwrap();
microscope_memory::snapshot::import(&archive_path, &restore_dir).unwrap();
assert!(restore_dir.join("meta.bin").exists());
assert!(restore_dir.join("microscope.bin").exists());
assert!(restore_dir.join("data.bin").exists());
}
#[test]
fn test_embedding_index_search() {
let (_tmp, config) = setup_test_env();
microscope_memory::build::build(&config, true, true).unwrap();
let output_dir = Path::new(&config.paths.output_dir);
let emb_path = output_dir.join("embeddings.bin");
if let Some(idx) = microscope_memory::embedding_index::EmbeddingIndex::open(&emb_path) {
assert!(idx.block_count() > 0);
use microscope_memory::embeddings::{EmbeddingProvider, MockEmbeddingProvider};
let reader =
microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
let block0_text = reader.text(0);
let provider = MockEmbeddingProvider::new(idx.dim());
let query_emb = provider.embed(block0_text).unwrap();
let results = idx.search(&query_emb, 5);
assert!(!results.is_empty());
}
}
#[test]
fn test_morphogenesis_mycelium_growth_integration() {
use microscope_memory::morphogenesis::*;
let seed = Seed::new("int_test", 0.0, 0.0, 0.0, "service");
let field = MorphogenField::new();
let config = GrowthConfig::mycelium_default();
let organism = mycelium_growth(&seed, &field, &config);
assert!(!organism.nodes.is_empty(), "Mycelium should grow nodes");
}
#[test]
fn test_morphogenesis_engine_evolve() {
use microscope_memory::morphogenesis::*;
let engine = MorphogenesisEngine::new();
let seeds = vec![Seed::new("evo", 0.0, 0.0, 0.0, "test")];
let results = engine.evolve_population(&seeds, 3, &FitnessObjective::Balanced, 8);
assert!(!results.is_empty(), "Should produce organisms");
}
#[test]
fn test_pattern_recognition_sequences() {
use microscope_memory::pattern_recognition::*;
let recognizer = PatternRecognizer::new();
recognizer.register_pattern(
"test_seq",
PatternType::Sequence,
PatternTemplate::Sequence(vec!["a".to_string(), "b".to_string()]),
"test",
vec![],
);
let found = recognizer.find_pattern("test_seq");
assert!(found.is_some(), "Should find registered pattern");
assert_eq!(found.unwrap().pattern_type, PatternType::Sequence);
}
#[test]
fn test_pattern_recognition_motifs() {
use microscope_memory::pattern_recognition::*;
let recognizer = PatternRecognizer::new();
recognizer.register_pattern(
"motif_test",
PatternType::Structural,
PatternTemplate::Structural(vec![("a".to_string(), "b".to_string(), 1.0)]),
"test_graph",
vec![],
);
let patterns = recognizer.list_patterns(Some(PatternType::Structural), Some("test_graph"));
assert!(!patterns.is_empty(), "Should have structural patterns");
}
#[test]
fn test_executive_schedule_and_cycle() {
use microscope_memory::executive::Executive;
let exec = Executive::new();
exec.register_module("a", "A", 200, 0.3, vec![]);
exec.register_module("b", "B", 100, 0.5, vec![]);
let plan = exec.schedule();
assert_eq!(plan.module_order[0], "a", "Higher priority first");
let executed = exec.cycle();
assert!(!executed.is_empty(), "Should execute modules");
}
#[test]
fn test_executive_homeostasis() {
use microscope_memory::executive::Executive;
let exec = Executive::new();
exec.register_module("m", "M", 50, 0.5, vec![]);
exec.update_resources(|r| r.energy_level = 0.05);
let actions = exec.homeostasis();
assert!(actions.contains(&"energy_conservation".to_string()));
}
#[test]
fn test_planning_goal_decomposition() {
use microscope_memory::planning::Planner;
let planner = Planner::new();
let gid = planner.add_goal("learn", "Learn Rust", 100, None);
let subs = planner.decompose_goal(gid);
assert!(!subs.is_empty(), "Should decompose into subgoals");
}
#[test]
fn test_planning_create_and_execute() {
use microscope_memory::planning::Planner;
let planner = Planner::new();
let gid = planner.add_goal("learn", "Learning", 100, None);
planner.decompose_goal(gid);
let plan = planner.create_plan(gid);
assert_eq!(plan.status, microscope_memory::planning::PlanStatus::Draft);
let step = planner.execute_step(plan.id).expect("plan step");
assert!(step.is_some(), "Should execute first step");
}
#[test]
fn test_autopoiesis_template_and_mutation() {
use microscope_memory::autopoiesis::*;
let engine = AutopoiesisEngine::new();
engine.register_template(
"tmpl",
"mod",
"fn {{name}}() {}",
vec!["name".to_string()],
"Test",
);
let mut vars = std::collections::HashMap::new();
vars.insert("name".to_string(), "hello".to_string());
let code = engine.generate_from_template("tmpl", &vars);
assert_eq!(code, Some("fn hello() {}".to_string()));
}
#[test]
fn test_autopoiesis_mutation_lifecycle() {
use microscope_memory::autopoiesis::*;
let engine = AutopoiesisEngine::new();
let id = engine.propose_mutation(
"fix",
MutationType::Modification,
"test_mod",
"code",
"reason",
"system",
);
assert!(engine.apply_mutation(id));
assert!(!engine.list_mutations(None).is_empty());
}
#[test]
fn test_code_memory_store_and_recall() {
use microscope_memory::code_memory::*;
let cm = CodeMemory::new();
cm.store(
CodeEntryType::Function,
"parse",
"fn parse() {}",
"lib.rs",
"rust",
"proj",
vec![],
vec![],
);
let q = CodeQuery {
query: "parse".to_string(),
language: None,
entry_type: None,
project: None,
file: None,
k: 10,
};
assert!(!cm.recall(&q).is_empty());
}
#[test]
fn test_code_memory_error_solution() {
use microscope_memory::code_memory::*;
let cm = CodeMemory::new();
let id = cm.store_error_solution("cannot borrow", "add mut", "test.rs", "rust", "proj");
let entry = cm.get(id);
assert_eq!(entry.unwrap().solution, Some("add mut".to_string()));
}
#[test]
fn test_code_memory_stats() {
use microscope_memory::code_memory::*;
let cm = CodeMemory::new();
cm.store(
CodeEntryType::Function,
"a",
"fn a() {}",
"a.rs",
"rust",
"p1",
vec![],
vec![],
);
cm.store(
CodeEntryType::Type,
"B",
"struct B {}",
"b.rs",
"rust",
"p2",
vec![],
vec![],
);
assert_eq!(cm.stats().0, 2);
}
#[test]
fn test_retention_shift_remaps_state_files() {
use microscope_memory::spaced_repetition::{SpacedBlock, SpacedRepetition};
let (_tmp, config) = setup_test_env();
let output_dir = Path::new(&config.paths.output_dir);
let layers_dir = Path::new(&config.paths.layers_dir);
microscope_memory::store_memory(&config, "Alpha first entry about rust.", "long_term", 7)
.expect("store alpha");
microscope_memory::store_memory(&config, "Beta second entry about mmap.", "long_term", 6)
.expect("store beta");
microscope_memory::store_memory(&config, "Gamma third entry about zoom.", "long_term", 5)
.expect("store gamma");
microscope_memory::build::build(&config, true, false).expect("build");
let reader = microscope_memory::MicroscopeReader::open(&config).expect("open reader");
let hits = reader.find_text("Beta second entry about mmap.", 10);
let old_beta = hits
.iter()
.find(|(d, _)| *d == 3)
.map(|(_, i)| *i as u32)
.expect("Beta D3 block in old layout");
assert!(
old_beta >= 3,
"Beta should not be the first D3 block (got {old_beta})"
);
let spaced = SpacedRepetition {
blocks: vec![SpacedBlock {
block_idx: old_beta,
recall_count: 3,
last_recall_ms: 1_700_000_000_000,
interval_days: 2.5,
ease_factor: 2.5,
importance: 6,
}],
};
spaced.save(output_dir).expect("save spaced");
drop(reader);
let layer_file = layers_dir.join("long_term.txt");
let kept = "(imp=6) Beta second entry about mmap.\n\n(imp=5) Gamma third entry about zoom.";
fs::write(&layer_file, kept).unwrap();
microscope_memory::build::build(&config, true, false).expect("rebuild after retention");
let reader = microscope_memory::MicroscopeReader::open(&config).expect("open reader");
let hits = reader.find_text("Beta second entry about mmap.", 10);
let new_beta = hits
.iter()
.find(|(d, _)| *d == 3)
.map(|(_, i)| *i as u32)
.expect("Beta D3 block after rebuild");
assert!(
new_beta < old_beta,
"retention should shift Beta earlier ({old_beta} -> {new_beta})"
);
let after = SpacedRepetition::load_or_init(output_dir);
assert_eq!(
after.blocks.len(),
1,
"surviving recall must not be dropped"
);
assert_eq!(
after.blocks[0].block_idx, new_beta,
"spaced record must follow the block content through retention"
);
}
#[test]
fn test_text_index_find_matches_full_scan() {
let (_tmp, config) = setup_test_env();
microscope_memory::store_memory(&config, "Rust borrow checker is strict", "long_term", 7)
.expect("store a");
microscope_memory::store_memory(&config, "Memory is managed by ownership", "long_term", 6)
.expect("store b");
microscope_memory::store_memory(&config, "Rusting in the rain tonight", "long_term", 5)
.expect("store c");
microscope_memory::build::build(&config, true, true).expect("heavy build");
let output_dir = Path::new(&config.paths.output_dir);
assert!(
output_dir.join("text_index.bin").exists(),
"heavy build must emit text_index.bin"
);
let indexed = {
let reader = microscope_memory::MicroscopeReader::open(&config).expect("open reader");
assert!(
reader.text_index.is_some(),
"reader must load the text index"
);
let mut out = Vec::new();
for q in ["rust", "rus", "Rust borrow", "memory", "zzz"] {
out.push((q.to_string(), reader.find_text(q, 20)));
}
out
};
fs::remove_file(output_dir.join("text_index.bin")).unwrap();
let scanned = {
let reader = microscope_memory::MicroscopeReader::open(&config).expect("open reader");
assert!(
reader.text_index.is_none(),
"removed index must not be loaded"
);
let mut out = Vec::new();
for q in ["rust", "rus", "Rust borrow", "memory", "zzz"] {
out.push((q.to_string(), reader.find_text(q, 20)));
}
out
};
assert_eq!(indexed.len(), scanned.len());
for ((q1, a), (q2, b)) in indexed.iter().zip(scanned.iter()) {
assert_eq!(q1, q2);
assert_eq!(
a, b,
"indexed and scanned find_text differ for query '{q1}'"
);
}
}
#[test]
fn test_light_rebuild_regenerates_text_index() {
let (_tmp, config) = setup_test_env();
microscope_memory::store_memory(&config, "Alpha entry for light rebuild", "long_term", 7)
.expect("store");
microscope_memory::build::build(&config, true, true).expect("heavy build");
let output_dir = Path::new(&config.paths.output_dir);
assert!(output_dir.join("text_index.bin").exists());
let layers_dir = Path::new(&config.paths.layers_dir);
fs::write(
layers_dir.join("long_term.txt"),
"(imp=7) Beta entry after the content change",
)
.unwrap();
microscope_memory::build::build(&config, true, false).expect("light rebuild");
assert!(
output_dir.join("text_index.bin").exists(),
"light rebuild must regenerate the text index it dropped"
);
let reader = microscope_memory::MicroscopeReader::open(&config).expect("open reader");
let idx = reader
.text_index
.as_ref()
.expect("reader must see the regenerated index");
assert_eq!(
idx.block_count(),
reader.block_count,
"regenerated index must match the new layout, never the old one"
);
let hits = reader.find_text("Beta entry", 10);
assert!(
!hits.is_empty(),
"indexed path must find the new content after light rebuild"
);
let mut reader_noidx = microscope_memory::MicroscopeReader::open(&config).expect("open");
reader_noidx.text_index = None;
let scan = reader_noidx.find_text("Beta entry", 10);
assert_eq!(hits, scan, "index must not change results, only speed");
}