codex-memory 3.0.15

A simple memory storage service with MCP interface for Claude Desktop
Documentation
use crate::common::TestDatabaseManager;
use anyhow::Result;
use codex_memory::models::SearchParams;
use codex_memory::Storage;
use serial_test::serial;
use std::sync::Arc;

#[tokio::test]
#[serial]
async fn test_progressive_search_stage_1_success() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool));

    // Store a memory that should match well
    let _id = storage
        .store(
            "Rust programming is great for systems development",
            "Programming tutorial".to_string(),
            "Learning Rust programming".to_string(),
            Some(vec!["rust".to_string(), "programming".to_string()]),
        )
        .await?;

    // Search with high threshold - should find results in Stage 1
    let params = SearchParams {
        query: "Rust programming".to_string(),
        similarity_threshold: 0.7,
        max_results: 10,
        ..Default::default()
    };

    let result_with_metadata = storage
        .search_memories_progressive_with_metadata(params)
        .await?;

    assert_eq!(result_with_metadata.metadata.stage_used, 1);
    assert_eq!(
        result_with_metadata.metadata.stage_description,
        "Original parameters"
    );
    assert_eq!(result_with_metadata.metadata.threshold_used, 0.7);
    assert!(!result_with_metadata.results.is_empty());

    manager.cleanup().await?;
    Ok(())
}

#[tokio::test]
#[serial]
async fn test_progressive_search_stage_2_fallback() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool));

    // Store a memory with less exact match
    let _id = storage
        .store(
            "System architecture discussion about software patterns",
            "Technical discussion".to_string(),
            "Architecture patterns in software".to_string(),
            Some(vec!["architecture".to_string(), "patterns".to_string()]),
        )
        .await?;

    // Search with very high threshold that won't match in Stage 1
    let params = SearchParams {
        query: "system patterns".to_string(),
        similarity_threshold: 0.95,
        max_results: 10,
        ..Default::default()
    };

    let result_with_metadata = storage
        .search_memories_progressive_with_metadata(params)
        .await?;

    // Should fallback to Stage 2 or 3
    assert!(result_with_metadata.metadata.stage_used >= 2);
    assert!(result_with_metadata.metadata.threshold_used < 0.95);

    manager.cleanup().await?;
    Ok(())
}

#[tokio::test]
#[serial]
async fn test_progressive_search_stage_3_content_only() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool));

    // Store memories that won't match well with high thresholds
    let _id1 = storage
        .store(
            "Database optimization techniques for better performance",
            "Performance tuning".to_string(),
            "Optimizing database queries".to_string(),
            Some(vec!["database".to_string(), "performance".to_string()]),
        )
        .await?;

    let _id2 = storage
        .store(
            "Web development with modern JavaScript frameworks",
            "Frontend development".to_string(),
            "Building web applications".to_string(),
            Some(vec!["web".to_string(), "javascript".to_string()]),
        )
        .await?;

    // Search for something that only loosely matches
    let params = SearchParams {
        query: "completely unrelated search terms".to_string(),
        similarity_threshold: 0.9,
        max_results: 10,
        ..Default::default()
    };

    let result_with_metadata = storage
        .search_memories_progressive_with_metadata(params)
        .await?;

    // Should use Stage 3 with very low threshold
    assert_eq!(result_with_metadata.metadata.stage_used, 3);
    assert_eq!(
        result_with_metadata.metadata.stage_description,
        "Content-only similarity"
    );
    assert_eq!(result_with_metadata.metadata.threshold_used, 0.1);

    manager.cleanup().await?;
    Ok(())
}

#[tokio::test]
#[serial]
async fn test_progressive_search_threshold_relaxation() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool));

    // Store a memory
    let _id = storage
        .store(
            "Machine learning algorithms for data analysis",
            "Data science".to_string(),
            "Understanding ML algorithms".to_string(),
            Some(vec!["ml".to_string(), "algorithms".to_string()]),
        )
        .await?;

    // Test with threshold 0.8 - should relax to 0.55 in Stage 2
    let params = SearchParams {
        query: "machine learning".to_string(),
        similarity_threshold: 0.8,
        max_results: 10,
        ..Default::default()
    };

    let result_with_metadata = storage
        .search_memories_progressive_with_metadata(params)
        .await?;

    // Verify threshold relaxation math: 0.8 - 0.25 = 0.55
    if result_with_metadata.metadata.stage_used == 2 {
        assert!((result_with_metadata.metadata.threshold_used - 0.55).abs() < 0.01);
    }

    manager.cleanup().await?;
    Ok(())
}

#[tokio::test]
#[serial]
async fn test_progressive_search_minimum_threshold() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool));

    // Store a memory
    let _id = storage
        .store(
            "Test content for minimum threshold verification",
            "Test context".to_string(),
            "Test summary".to_string(),
            Some(vec!["test".to_string()]),
        )
        .await?;

    // Test with very low threshold - Stage 2 should not go below 0.1
    let params = SearchParams {
        query: "test content".to_string(),
        similarity_threshold: 0.2, // 0.2 - 0.25 would be -0.05, but should clamp to 0.1
        max_results: 10,
        ..Default::default()
    };

    let result_with_metadata = storage
        .search_memories_progressive_with_metadata(params)
        .await?;

    // If it uses Stage 2, threshold should be clamped to minimum 0.1
    if result_with_metadata.metadata.stage_used == 2 {
        assert_eq!(result_with_metadata.metadata.threshold_used, 0.1);
    }

    manager.cleanup().await?;
    Ok(())
}