do-memory-mcp 0.1.46

Model Context Protocol (MCP) server for AI agents
use super::*;
use do_memory_core::SelfLearningMemory;
use do_memory_mcp::SandboxConfig;
use serde_json::json;
use std::sync::Arc;

#[tokio::test]
async fn test_handle_query_memory_field_truncation() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();

    // Verify constant
    assert_eq!(do_memory_mcp::constants::MAX_QUERY_FIELDS, 20);

    let many_fields: Vec<String> = (0..100).map(|i| format!("field_{i}")).collect();
    let args = json!({
        "query": "test",
        "fields": many_fields,
        "limit": 5000
    });
    // Confirm handler runs with large input without panic
    let _ = handle_query_memory(&mut server, Some(args)).await;
}

#[tokio::test]
async fn test_handle_query_memory_with_provenance() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();

    let secret = "user password secret query about tokens";
    let args = json!({
        "query": secret,
        "domain": "web-api",
        "limit": 5,
        "with_provenance": true
    });
    let content = handle_query_memory(&mut server, Some(args))
        .await
        .expect("handler should succeed with with_provenance");
    let Content::Text { text } = &content[0];
    let value: serde_json::Value = serde_json::from_str(text).expect("valid json");
    assert!(
        value.get("provenance").is_some(),
        "handler with_provenance must include provenance: {text}"
    );
    assert!(
        !text.contains("password") && !text.contains(secret),
        "handler must not echo raw query into provenance: {text}"
    );
}

#[tokio::test]
async fn test_handle_analyze_patterns_clamping() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();

    // Verify clamping logic explicitly (Upper Bound)
    let rate_high = 5.0f64;
    let rate_clamped = rate_high.clamp(0.0, 1.0) as f32;
    assert!((rate_clamped - 1.0).abs() < f32::EPSILON);

    let limit_high = 5000usize;
    let limit_clamped = limit_high.clamp(
        do_memory_mcp::constants::MIN_QUERY_LIMIT,
        do_memory_mcp::constants::MAX_SEARCH_LIMIT,
    );
    assert_eq!(limit_clamped, 100);

    // Verify clamping logic explicitly (Lower Bound)
    let rate_low = -0.5f64;
    let rate_clamped_low = rate_low.clamp(0.0, 1.0) as f32;
    assert!(rate_clamped_low.abs() < f32::EPSILON);

    let limit_low = 0usize;
    let limit_clamped_low = limit_low.clamp(
        do_memory_mcp::constants::MIN_QUERY_LIMIT,
        do_memory_mcp::constants::MAX_SEARCH_LIMIT,
    );
    assert_eq!(limit_clamped_low, 1);

    let args = json!({
        "task_type": "test",
        "min_success_rate": 5.0,
        "limit": 5000
    });
    let _ = handle_analyze_patterns(&mut server, Some(args)).await;
}

#[tokio::test]
async fn test_advanced_analysis_truncation() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();
    let mut data = std::collections::HashMap::new();
    for i in 0..20 {
        data.insert(format!("v{i}"), vec![0.0; 2000]);
    }
    let _ = handle_advanced_pattern_analysis(
        &mut server,
        Some(json!({"analysis_type": "statistical", "time_series_data": data})),
    )
    .await;
}

#[tokio::test]
async fn test_advanced_analysis_within_series_limit() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();
    let mut data = std::collections::HashMap::new();
    for i in 0..5 {
        data.insert(format!("v{i}"), vec![0.0; 2000]);
    }
    let _ = handle_advanced_pattern_analysis(
        &mut server,
        Some(json!({"analysis_type": "statistical", "time_series_data": data})),
    )
    .await;
}

#[tokio::test]
async fn test_get_metrics_truncation() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();
    let _ = handle_get_metrics(&mut server, Some(json!({"metric_type": "a".repeat(200)}))).await;
}

#[tokio::test]
async fn test_get_metrics_short_input() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();
    let _ = handle_get_metrics(&mut server, Some(json!({"metric_type": "short"}))).await;
}

#[tokio::test]
async fn test_get_metrics_multibyte_truncation() {
    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory)
        .await
        .unwrap();
    let long_unicode = "あ".repeat(200);
    let _ = handle_get_metrics(&mut server, Some(json!({"metric_type": long_unicode}))).await;
}

#[tokio::test]
async fn test_relationship_handlers_rate_limit_by_process_identity() -> anyhow::Result<()> {
    use do_memory_mcp::server::rate_limiter::OperationType;

    let memory = Arc::new(SelfLearningMemory::new());
    let mut server = MemoryMCPServer::new(SandboxConfig::default(), memory).await?;
    assert!(
        server.rate_limiter().is_enabled(),
        "this test requires rate limiting to be enabled"
    );

    // Saturate the process-scoped write bucket
    let identity = server.rate_limit_identity();
    let burst = server.rate_limiter().config.write_burst_size.max(1);
    for _ in 0..burst {
        server.check_rate_limit(&identity, OperationType::Write);
    }

    // #1084: the caller-supplied `client_id` must not select the bucket, so the
    // saturated process bucket still rejects the relationship write.
    let error = handle_add_episode_relationship(&mut server, Some(json!({"client_id": "rotated"})))
        .await
        .expect_err("a saturated write bucket must reject the relationship write");
    assert!(
        error.to_string().contains("Rate limit exceeded"),
        "unexpected error: {error}"
    );
    Ok(())
}