// ABOUTME: Comprehensive tests for MCP protocol handling coverage improvement
// ABOUTME: Tests error scenarios, edge cases, and protocol handling in multitenant
//
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 Pierre Fitness Intelligence
//! Comprehensive tests for MCP protocol handling to improve coverage
//!
//! This test suite focuses on the low-coverage areas in mcp/multitenant.rs
//! including error scenarios, edge cases, and protocol handling
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#![allow(missing_docs)]
mod common;
use anyhow::Result;
use pierre_mcp_server::{
config::environment::ServerConfig,
database_plugins::DatabaseProvider,
mcp::{multitenant::MultiTenantMcpServer, resources::ServerResources, schema::get_tools},
models::User,
};
use serde_json::{json, Value};
use std::sync::Arc;
use uuid::Uuid;
const TEST_JWT_SECRET: &str = "test_jwt_secret_for_mcp_protocol_tests";
/// Test helper to create MCP request
fn create_mcp_request(method: &str, params: Option<&Value>, id: Option<Value>) -> Value {
json!({
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": id.unwrap_or_else(|| json!(1))
})
}
/// Test helper to create authenticated MCP request
fn create_auth_mcp_request(
method: &str,
params: Option<&Value>,
token: &str,
id: Option<Value>,
) -> Value {
let mut request = create_mcp_request(method, params, id);
request["auth_token"] = json!(token);
request
}
#[tokio::test]
async fn test_mcp_initialize_request() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let server = MultiTenantMcpServer::new(resources);
// Test initialize request
let _request = create_mcp_request("initialize", None, Some(json!("init-1")));
// We can't directly call handle_request as it's private, but we can test through integration
// This validates that the server is properly initialized
// Database should be available
let _ = server.database();
Ok(())
}
#[tokio::test]
async fn test_mcp_ping_request() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Test ping request structure
let request = create_mcp_request("ping", None, Some(json!(123)));
assert_eq!(request["jsonrpc"], "2.0");
assert_eq!(request["method"], "ping");
Ok(())
}
#[tokio::test]
async fn test_mcp_tools_list_request() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Test tools/list request
let request = create_mcp_request("tools/list", None, Some(json!("list-1")));
assert_eq!(request["method"], "tools/list");
// Verify tools are available through schema
let tools = get_tools();
assert!(!tools.is_empty());
assert!(tools.iter().any(|t| t.name == "get_activities"));
Ok(())
}
#[tokio::test]
async fn test_mcp_authenticate_request() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Create test user
let user = User::new(
"mcp_auth@example.com".to_owned(),
"password123".to_owned(),
Some("MCP Auth Test".to_owned()),
);
database.create_user(&user).await?;
// Test authenticate request format
let auth_params = json!({
"email": "mcp_auth@example.com",
"password": "password123"
});
let request = create_mcp_request("authenticate", Some(&auth_params), Some(json!("auth-1")));
assert_eq!(request["method"], "authenticate");
assert_eq!(request["params"]["email"], "mcp_auth@example.com");
Ok(())
}
#[tokio::test]
async fn test_mcp_tools_call_without_auth() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Test tools/call without authentication
let params = json!({
"name": "get_activities",
"arguments": {
"provider": "strava",
"limit": 10
}
});
let request = create_mcp_request("tools/call", Some(¶ms), Some(json!("call-1")));
// This should fail without auth_token
assert!(request.get("auth_token").is_none());
Ok(())
}
#[tokio::test]
async fn test_mcp_tools_call_with_expired_token() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Create an expired token scenario
let expired_token = "expired.jwt.token";
let params = json!({
"name": "get_activities",
"arguments": {
"provider": "strava",
"limit": 10
}
});
let request = create_auth_mcp_request(
"tools/call",
Some(¶ms),
expired_token,
Some(json!("call-2")),
);
assert_eq!(request["auth_token"], expired_token);
Ok(())
}
#[tokio::test]
async fn test_mcp_tools_call_malformed_token() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Test with malformed token
let malformed_token = "not.a.valid.jwt";
let params = json!({
"name": "get_athlete",
"arguments": {
"provider": "fitbit"
}
});
let request = create_auth_mcp_request(
"tools/call",
Some(¶ms),
malformed_token,
Some(json!("call-3")),
);
assert_eq!(request["method"], "tools/call");
assert_eq!(request["auth_token"], malformed_token);
Ok(())
}
#[tokio::test]
async fn test_mcp_unknown_method() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Test unknown method
let request = create_mcp_request("unknown/method", None, Some(json!("unknown-1")));
assert_eq!(request["method"], "unknown/method");
Ok(())
}
#[tokio::test]
async fn test_mcp_oauth_tool_calls() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Create test user and generate valid token
let user = User::new(
"oauth_test@example.com".to_owned(),
"password".to_owned(),
Some("OAuth Test User".to_owned()),
);
database.create_user(&user).await?;
// Create JWKS manager for RS256 token generation
let jwks_manager = common::get_shared_test_jwks();
let jwks_manager = Arc::new(jwks_manager);
let token = auth_manager.generate_token(&user, &jwks_manager)?;
// Test connect_strava tool
let connect_params = json!({
"name": "connect_strava",
"arguments": {}
});
let connect_request = create_auth_mcp_request(
"tools/call",
Some(&connect_params),
&token,
Some(json!("oauth-1")),
);
assert_eq!(connect_request["params"]["name"], "connect_strava");
// Test connect_fitbit tool
let fitbit_params = json!({
"name": "connect_fitbit",
"arguments": {}
});
let fitbit_request = create_auth_mcp_request(
"tools/call",
Some(&fitbit_params),
&token,
Some(json!("oauth-2")),
);
assert_eq!(fitbit_request["params"]["name"], "connect_fitbit");
// Test get_connection_status tool
let status_params = json!({
"name": "get_connection_status",
"arguments": {}
});
let status_request = create_auth_mcp_request(
"tools/call",
Some(&status_params),
&token,
Some(json!("oauth-3")),
);
assert_eq!(status_request["params"]["name"], "get_connection_status");
// Test disconnect_provider tool
let disconnect_params = json!({
"name": "disconnect_provider",
"arguments": {
"provider": "strava"
}
});
let disconnect_request = create_auth_mcp_request(
"tools/call",
Some(&disconnect_params),
&token,
Some(json!("oauth-4")),
);
assert_eq!(disconnect_request["params"]["name"], "disconnect_provider");
Ok(())
}
#[tokio::test]
async fn test_mcp_intelligence_tool_calls() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Create test user
let user = User::new(
"intel_test@example.com".to_owned(),
"password".to_owned(),
Some("Intelligence Test User".to_owned()),
);
database.create_user(&user).await?;
// Create JWKS manager for RS256 token generation
let jwks_manager = common::get_shared_test_jwks();
let jwks_manager = Arc::new(jwks_manager);
let token = auth_manager.generate_token(&user, &jwks_manager)?;
// Test intelligence tools that don't require providers
let tool_tests = vec![
(
"set_goal",
json!({"goal_type": "distance", "target_value": 100, "target_date": "2024-12-31"}),
),
("track_progress", json!({"goal_id": "test-goal-123"})),
(
"analyze_goal_feasibility",
json!({"goal_type": "speed", "target_value": 20}),
),
("suggest_goals", json!({})),
("calculate_fitness_score", json!({})),
("generate_recommendations", json!({})),
("analyze_training_load", json!({})),
("detect_patterns", json!({})),
("analyze_performance_trends", json!({})),
];
for (tool_name, args) in tool_tests {
let params = json!({
"name": tool_name,
"arguments": args
});
let request =
create_auth_mcp_request("tools/call", Some(¶ms), &token, Some(json!(tool_name)));
assert_eq!(request["params"]["name"], tool_name);
}
Ok(())
}
#[tokio::test]
async fn test_mcp_provider_required_tools() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Create test user
let user = User::new(
"provider_test@example.com".to_owned(),
"password".to_owned(),
Some("Provider Test User".to_owned()),
);
database.create_user(&user).await?;
// Create JWKS manager for RS256 token generation
let jwks_manager = common::get_shared_test_jwks();
let jwks_manager = Arc::new(jwks_manager);
let token = auth_manager.generate_token(&user, &jwks_manager)?;
// Test tools that require a provider
let provider_tools = vec![
("get_activities", json!({"provider": "strava", "limit": 10})),
("get_athlete", json!({"provider": "fitbit"})),
("get_stats", json!({"provider": "strava"})),
(
"get_activity_intelligence",
json!({"provider": "strava", "activity_id": "123"}),
),
(
"analyze_activity",
json!({"provider": "strava", "activity_id": "456"}),
),
(
"calculate_metrics",
json!({"provider": "fitbit", "activity_ids": ["789"]}),
),
(
"compare_activities",
json!({"provider": "strava", "activity_ids": ["111", "222"]}),
),
(
"predict_performance",
json!({"provider": "strava", "activity_type": "run"}),
),
("calculate_recovery_score", json!({"provider": "strava"})),
("suggest_rest_day", json!({"provider": "strava"})),
];
for (tool_name, args) in provider_tools {
let params = json!({
"name": tool_name,
"arguments": args
});
let request =
create_auth_mcp_request("tools/call", Some(¶ms), &token, Some(json!(tool_name)));
assert_eq!(request["params"]["name"], tool_name);
assert!(request["params"]["arguments"]["provider"].is_string());
}
Ok(())
}
#[tokio::test]
async fn test_mcp_unknown_tool() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Create test user
let user = User::new(
"unknown_tool@example.com".to_owned(),
"password".to_owned(),
Some("Unknown Tool Test".to_owned()),
);
database.create_user(&user).await?;
// Create JWKS manager for RS256 token generation
let jwks_manager = common::get_shared_test_jwks();
let jwks_manager = Arc::new(jwks_manager);
let token = auth_manager.generate_token(&user, &jwks_manager)?;
// Test unknown tool
let params = json!({
"name": "completely_unknown_tool",
"arguments": {}
});
let request = create_auth_mcp_request(
"tools/call",
Some(¶ms),
&token,
Some(json!("unknown-tool")),
);
assert_eq!(request["params"]["name"], "completely_unknown_tool");
Ok(())
}
#[tokio::test]
async fn test_mcp_api_key_authentication() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Create test user and API key
let user = User::new(
"api_key_test@example.com".to_owned(),
"password".to_owned(),
Some("API Key Test".to_owned()),
);
database.create_user(&user).await?;
// Simulate API key authentication format
let api_key_token = format!("Bearer pk_test_{}", Uuid::new_v4().simple());
let params = json!({
"name": "get_connection_status",
"arguments": {}
});
let request = create_auth_mcp_request(
"tools/call",
Some(¶ms),
&api_key_token,
Some(json!("api-key-1")),
);
assert!(request["auth_token"]
.as_str()
.unwrap()
.starts_with("Bearer pk_"));
Ok(())
}
#[tokio::test]
async fn test_mcp_request_id_variations() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Test different ID types
let id_variations = vec![
json!(1),
json!("string-id"),
json!(null),
json!({"complex": "id"}),
json!([1, 2, 3]),
];
for id in id_variations {
let request = create_mcp_request("ping", None, Some(id.clone()));
assert_eq!(request["id"], id);
}
Ok(())
}
#[tokio::test]
async fn test_mcp_error_scenarios() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = MultiTenantMcpServer::new(resources);
// Test various error scenarios
// 1. Missing required parameters
let missing_params = json!({
"name": "get_activities",
"arguments": {} // Missing provider
});
let request1 = create_mcp_request("tools/call", Some(&missing_params), Some(json!("error-1")));
assert!(request1["params"]["arguments"]["provider"].is_null());
// 2. Invalid parameter types
let invalid_params = json!({
"name": "get_activities",
"arguments": {
"provider": 123, // Should be string
"limit": "ten" // Should be number
}
});
let request2 = create_mcp_request("tools/call", Some(&invalid_params), Some(json!("error-2")));
assert!(request2["params"]["arguments"]["provider"].is_number());
// 3. Empty method
let request3 = create_mcp_request("", None, Some(json!("error-3")));
assert_eq!(request3["method"], "");
Ok(())
}
#[tokio::test]
async fn test_mcp_concurrent_requests() -> Result<()> {
common::init_server_config();
let database = common::create_test_database().await?;
let auth_manager = common::create_test_auth_manager();
let config = Arc::new(ServerConfig::from_env()?);
let cache = common::create_test_cache().await.unwrap();
let resources = Arc::new(
ServerResources::new(
(*database).clone(),
(*auth_manager).clone(),
TEST_JWT_SECRET,
config,
cache,
2048, // Use 2048-bit RSA keys for faster test execution
Some(common::get_shared_test_jwks()),
)
.await,
);
let _server = Arc::new(MultiTenantMcpServer::new(resources));
// Create test user
let user = User::new(
"concurrent_test@example.com".to_owned(),
"password".to_owned(),
Some("Concurrent Test".to_owned()),
);
database.create_user(&user).await?;
// Create JWKS manager for RS256 token generation
let jwks_manager = common::get_shared_test_jwks();
let jwks_manager = Arc::new(jwks_manager);
let token = auth_manager.generate_token(&user, &jwks_manager)?;
// Simulate concurrent requests
let mut handles = Vec::new();
for i in 0..3 {
let token_clone = token.clone();
let handle = tokio::spawn(async move {
let params = json!({
"name": "get_connection_status",
"arguments": {}
});
let request = create_auth_mcp_request(
"tools/call",
Some(¶ms),
&token_clone,
Some(json!(format!("concurrent-{i}"))),
);
request
});
handles.push(handle);
}
// Wait for all requests
for handle in handles {
let request = handle.await?;
assert_eq!(request["method"], "tools/call");
}
Ok(())
}