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::Storage;
use serial_test::serial;
use sqlx::Row;
use std::sync::Arc;

/// Simulate complete user journey from installation to retrieval
#[tokio::test]
#[serial]
#[ignore] // Run with: cargo test --ignored full_user_journey
async fn test_full_user_journey() -> Result<()> {
    println!("🚀 Starting E2E test: Complete user journey");

    // Step 1: Setup database (simulating first-time setup)
    println!("Step 1: Setting up database...");
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;

    // Verify migrations ran
    let table_exists: bool = sqlx::query(
        "SELECT EXISTS (
            SELECT FROM information_schema.tables 
            WHERE table_name = 'memories'
        ) as exists",
    )
    .fetch_one(&pool)
    .await?
    .get("exists");

    assert!(table_exists, "Memories table should exist after setup");
    println!("✅ Database setup complete");

    // Step 2: Store various types of content
    println!("\nStep 2: Storing various content types...");
    let storage = Arc::new(Storage::new(pool.clone()));

    // Store content with minimal context/summary
    let id1 = storage
        .store(
            "User's important note without any AI processing",
            "Direct user note".to_string(),
            "User's important note".to_string(),
            Some(vec!["important".to_string(), "note".to_string()]),
        )
        .await?;
    println!("  ✅ Stored plain content: {}", id1);

    // Store content with context and summary
    let id2 = storage
        .store(
            "Error: Connection timeout at line 42",
            "Debug session for production issue".to_string(),
            "Connection timeout error during debug".to_string(),
            Some(vec!["error".to_string(), "debug".to_string()]),
        )
        .await?;
    println!("  ✅ Stored content with context: {}", id2);

    // Store content with both context and summary
    let id3 = storage.store(
        "The complete implementation of the authentication system includes JWT token generation, 
         refresh token handling, session management, and role-based access control. 
         The system uses RS256 for signing tokens and implements automatic token rotation.",
        "Code review discussion about auth system".to_string(),
        "Authentication system with JWT, refresh tokens, and RBAC".to_string(),
        Some(vec!["auth".to_string(), "security".to_string(), "jwt".to_string()])
    ).await?;
    println!("  ✅ Stored content with context and summary: {}", id3);

    // Step 3: Retrieve and verify content
    println!("\nStep 3: Retrieving stored content...");

    let memory1 = storage.get(id1).await?.expect("Memory 1 should exist");
    assert_eq!(
        memory1.content,
        "User's important note without any AI processing"
    );
    assert_eq!(memory1.context, "Direct user note");
    assert_eq!(memory1.summary, "User's important note");
    println!("  ✅ Retrieved plain content correctly");

    let memory2 = storage.get(id2).await?.expect("Memory 2 should exist");
    assert_eq!(memory2.context, "Debug session for production issue");
    assert_eq!(memory2.summary, "Connection timeout error during debug");
    println!("  ✅ Retrieved content with context correctly");

    let memory3 = storage.get(id3).await?.expect("Memory 3 should exist");
    assert_eq!(memory3.context, "Code review discussion about auth system");
    assert_eq!(
        memory3.summary,
        "Authentication system with JWT, refresh tokens, and RBAC"
    );
    assert_eq!(memory3.tags.len(), 3);
    println!("  ✅ Retrieved content with all fields correctly");

    // Step 4: Test deduplication
    println!("\nStep 4: Testing deduplication...");
    let duplicate_id = storage
        .store(
            "User's important note without any AI processing", // Same content as id1
            "Different context".to_string(),
            "Now with a summary".to_string(),
            None,
        )
        .await?;

    assert_eq!(
        duplicate_id, id1,
        "Should return same ID for duplicate content"
    );

    // Verify context/summary were updated
    let updated = storage.get(id1).await?.expect("Memory should exist");
    assert_eq!(updated.context, "Different context");
    assert_eq!(updated.summary, "Now with a summary");
    println!("  ✅ Deduplication working, context/summary updated");

    // Step 5: Check statistics
    println!("\nStep 5: Checking statistics...");
    let stats = storage.stats().await?;
    assert_eq!(stats.total_memories, 3); // Only 3 unique memories
    assert!(stats.last_memory_created.is_some());
    println!("  ✅ Statistics: {} memories stored", stats.total_memories);

    // Step 6: List recent memories
    println!("\nStep 6: Listing recent memories...");
    let recent = storage.list_recent(10).await?;
    assert_eq!(recent.len(), 3);
    println!("  ✅ Listed {} recent memories", recent.len());

    // Step 7: Delete a memory
    println!("\nStep 7: Testing deletion...");
    let deleted = storage.delete(id2).await?;
    assert!(deleted);
    assert!(storage.get(id2).await?.is_none());
    println!("  ✅ Successfully deleted memory");

    // Final stats
    let final_stats = storage.stats().await?;
    assert_eq!(final_stats.total_memories, 2);

    println!("\n🎉 E2E test completed successfully!");

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

/// Test batch operations for performance
#[tokio::test]
#[serial]
async fn test_batch_operations() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool));

    // Generate and store batch of test data
    let batch_size = 50;
    let start = std::time::Instant::now();

    let mut ids = Vec::new();
    for i in 0..batch_size {
        let content = format!("Batch test content item #{}", i);
        let context = format!("Context for item {}", i);
        let summary = format!("Summary for item {}", i);
        let tags = Some(vec![format!("batch-{}", i), "test".to_string()]);

        let id = storage.store(&content, context, summary, tags).await?;
        ids.push((id, content));
    }

    let duration = start.elapsed();
    println!("Stored {} items in {:?}", batch_size, duration);

    // Verify all were stored
    let stats = storage.stats().await?;
    assert_eq!(stats.total_memories as usize, batch_size);

    // Retrieve random samples
    for i in [0, 25, 49] {
        let retrieved = storage.get(ids[i].0).await?.expect("Should exist");
        assert_eq!(retrieved.content, ids[i].1);
    }

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

// Tier migration test removed - tier system no longer exists

/// Test tag-based search scenarios
#[tokio::test]
#[serial]
async fn test_tag_search_scenarios() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool.clone()));

    // Store memories with various tag combinations
    storage
        .store(
            "Python code",
            "Test context".to_string(),
            "Test summary".to_string(),
            Some(vec!["python".to_string(), "code".to_string()]),
        )
        .await?;

    storage
        .store(
            "Rust code",
            "Test context".to_string(),
            "Test summary".to_string(),
            Some(vec!["rust".to_string(), "code".to_string()]),
        )
        .await?;

    storage
        .store(
            "Python tutorial",
            "Test context".to_string(),
            "Test summary".to_string(),
            Some(vec!["python".to_string(), "tutorial".to_string()]),
        )
        .await?;

    storage
        .store(
            "No tags content",
            "Test context".to_string(),
            "Test summary".to_string(),
            None,
        )
        .await?;

    // Query by single tag
    let results: Vec<String> =
        sqlx::query("SELECT content FROM memories WHERE 'python' = ANY(tags)")
            .fetch_all(&pool)
            .await?
            .into_iter()
            .map(|row| row.get("content"))
            .collect();

    assert_eq!(results.len(), 2);
    assert!(results.contains(&"Python code".to_string()));
    assert!(results.contains(&"Python tutorial".to_string()));

    // Query by multiple tags (AND condition)
    let results: Vec<String> =
        sqlx::query("SELECT content FROM memories WHERE tags @> ARRAY['python', 'code']::text[]")
            .fetch_all(&pool)
            .await?
            .into_iter()
            .map(|row| row.get("content"))
            .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0], "Python code");

    // Query memories without tags
    let results: Vec<String> =
        sqlx::query("SELECT content FROM memories WHERE array_length(tags, 1) IS NULL")
            .fetch_all(&pool)
            .await?
            .into_iter()
            .map(|row| row.get("content"))
            .collect();

    assert_eq!(results.len(), 1);
    assert_eq!(results[0], "No tags content");

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

/// Test real-world usage patterns
#[tokio::test]
#[serial]
async fn test_real_world_patterns() -> Result<()> {
    let mut manager = TestDatabaseManager::new()?;
    let pool = manager.setup_test_database().await?;
    let storage = Arc::new(Storage::new(pool));

    // Pattern 1: Storing code snippets with context
    let code_snippet = r#"
    fn fibonacci(n: u32) -> u32 {
        match n {
            0 => 0,
            1 => 1,
            _ => fibonacci(n - 1) + fibonacci(n - 2),
        }
    }
    "#;

    let id = storage
        .store(
            code_snippet,
            "Learning Rust: recursive functions".to_string(),
            "Recursive Fibonacci implementation in Rust".to_string(),
            Some(vec![
                "rust".to_string(),
                "recursion".to_string(),
                "fibonacci".to_string(),
            ]),
        )
        .await?;

    let retrieved = storage.get(id).await?.expect("Should exist");
    assert_eq!(retrieved.content, code_snippet);

    // Pattern 2: Storing error logs
    let error_log = "2024-01-15 10:23:45 ERROR: Database connection timeout after 30s";

    storage
        .store(
            error_log,
            "Production incident on API server".to_string(),
            "Database connection timeout error log".to_string(),
            Some(vec![
                "error".to_string(),
                "database".to_string(),
                "production".to_string(),
            ]),
        )
        .await?;

    // Pattern 3: Storing meeting notes
    let meeting_notes = "Discussion points:\n- Q1 roadmap\n- Budget allocation\n- Team expansion";

    storage
        .store(
            meeting_notes,
            "Q1 planning meeting with stakeholders".to_string(),
            "Q1 planning: roadmap, budget, team expansion".to_string(),
            Some(vec![
                "meeting".to_string(),
                "planning".to_string(),
                "q1".to_string(),
            ]),
        )
        .await?;

    // Verify all stored correctly
    let stats = storage.stats().await?;
    assert_eq!(stats.total_memories, 3);

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