microscope-memory 0.6.0

Pure binary cognitive memory engine. Zero-JSON, mmap-based, hierarchical memory architecture.
Documentation
//! Integration tests for the Microscope Memory pipeline.
//! Tests the full build -> query -> store -> recall -> verify cycle.

use std::fs;
use std::path::Path;

/// Create a temporary test environment with config pointing to real fixture data.
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();

    // Create dummy RAW TEXT layer file (Zero-JSON)
    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).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).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).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).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.len() >= 1);
}

#[test]
fn test_incremental_build_skips() {
    let (_tmp, config) = setup_test_env();

    // First build
    microscope_memory::build::build(&config, false).expect("build");
    let meta1 = fs::read(Path::new(&config.paths.output_dir).join("meta.bin")).unwrap();

    // Second build (should skip -- layers unchanged)
    microscope_memory::build::build(&config, false).expect("build");
    let meta2 = fs::read(Path::new(&config.paths.output_dir).join("meta.bin")).unwrap();

    // Meta should be identical (no rebuild happened)
    assert_eq!(
        meta1, meta2,
        "meta.bin should be identical when layers unchanged"
    );
}

#[test]
fn test_incremental_build_force() {
    let (_tmp, config) = setup_test_env();

    // First build
    microscope_memory::build::build(&config, false).expect("build");

    // Force rebuild should complete without error
    microscope_memory::build::build(&config, true).unwrap();
}

#[test]
fn test_mql_query() {
    let (_tmp, config) = setup_test_env();
    microscope_memory::build::build(&config, true).unwrap();

    let reader = microscope_memory::reader::MicroscopeReader::open(&config).expect("open reader");
    let appended = vec![];

    // Query with keyword
    let q = microscope_memory::query::parse("\"Rust\"");
    let results = microscope_memory::query::execute(&q, &reader, &appended);
    assert!(!results.is_empty());
}

#[test]
fn test_crc_integrity_after_build() {
    let (_tmp, config) = setup_test_env();
    microscope_memory::build::build(&config, 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).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_snapshot_export_import() {
    let (_tmp, config) = setup_test_env();
    microscope_memory::build::build(&config, true).unwrap();

    let output_dir = Path::new(&config.paths.output_dir);
    let archive_path = output_dir.join("test.mscope");

    // Export
    microscope_memory::snapshot::export(output_dir, &archive_path).unwrap();
    assert!(archive_path.exists(), "archive should exist");

    // Import to new directory
    let restore_dir = output_dir.join("restored");
    fs::create_dir_all(&restore_dir).unwrap();
    microscope_memory::snapshot::import(&archive_path, &restore_dir).unwrap();

    // Verify key files restored
    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).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());
    }
}