use rmcp_openapi::{HttpClient, Server};
use serde_json::json;
use std::env;
use url::Url;
mod common;
use common::mock_server::MockPetstoreServer;
use mockito::Mock;
fn should_use_live_api() -> bool {
env::var("RMCP_TEST_LIVE_API").unwrap_or_default() == "true"
}
const LIVE_API_BASE_URL: &str = "https://petstore.swagger.io/v2";
#[actix_web::test]
async fn test_get_pet_by_id_path_parameter() -> anyhow::Result<()> {
let pet_id = 123u64;
if should_use_live_api() {
let server = create_server_with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let client = HttpClient::new().with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let tool_metadata = server
.get_tool_metadata("getPetById")
.expect("getPetById tool should be registered");
let arguments = json!({
"petId": pet_id
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success || response.status_code == 404);
assert_eq!(response.request_method, "GET");
assert!(response.request_url.contains(&format!("/pet/{pet_id}")));
} else {
let mut mock_server = MockPetstoreServer::new_with_port(9101).await;
let _mock = mock_server.mock_get_pet_by_id(pet_id);
let server = create_server_with_base_url(mock_server.base_url())?;
let client = HttpClient::new().with_base_url(mock_server.base_url())?;
let tool_metadata = server
.get_tool_metadata("getPetById")
.expect("getPetById tool should be registered");
let arguments = json!({
"petId": pet_id
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.status_code, 200);
assert_eq!(response.request_method, "GET");
assert!(response.request_url.contains(&format!("/pet/{pet_id}")));
let pet_data = response.json()?;
assert_eq!(pet_data["id"], pet_id);
assert_eq!(pet_data["name"], "doggie");
}
Ok(())
}
#[actix_web::test]
async fn test_get_pet_by_id_not_found() -> anyhow::Result<()> {
let pet_id = 99999u64;
if should_use_live_api() {
let server = create_server_with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let client = HttpClient::new().with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let tool_metadata = server
.get_tool_metadata("getPetById")
.expect("getPetById tool should be registered");
let arguments = json!({
"petId": pet_id
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert_eq!(response.status_code, 404);
assert!(!response.is_success);
} else {
let mut mock_server = MockPetstoreServer::new_with_port(9102).await;
let _mock = mock_server.mock_get_pet_by_id_not_found(pet_id);
let server = create_server_with_base_url(mock_server.base_url())?;
let client = HttpClient::new().with_base_url(mock_server.base_url())?;
let tool_metadata = server
.get_tool_metadata("getPetById")
.expect("getPetById tool should be registered");
let arguments = json!({
"petId": pet_id
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert_eq!(response.status_code, 404);
assert!(!response.is_success);
assert!(response.request_url.contains(&format!("/pet/{pet_id}")));
let error_data = response.json()?;
assert_eq!(error_data["message"], "Pet not found");
}
Ok(())
}
#[actix_web::test]
async fn test_find_pets_by_status_query_parameter() -> anyhow::Result<()> {
let status = "available";
if should_use_live_api() {
let server = create_server_with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let client = HttpClient::new().with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let tool_metadata = server
.get_tool_metadata("findPetsByStatus")
.expect("findPetsByStatus tool should be registered");
let arguments = json!({
"status": [status]
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.request_method, "GET");
assert!(response.request_url.contains("/pet/findByStatus"));
assert!(response.request_url.contains(&format!("status={status}")));
} else {
let mut mock_server = MockPetstoreServer::new_with_port(9103).await;
let _mock = mock_server.mock_find_pets_by_status(status);
let server = create_server_with_base_url(mock_server.base_url())?;
let client = HttpClient::new().with_base_url(mock_server.base_url())?;
let tool_metadata = server
.get_tool_metadata("findPetsByStatus")
.expect("findPetsByStatus tool should be registered");
let arguments = json!({
"status": [status]
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.status_code, 200);
assert_eq!(response.request_method, "GET");
assert!(response.request_url.contains("/pet/findByStatus"));
assert!(response.request_url.contains(&format!("status={status}")));
let pets_data = response.json()?;
assert!(pets_data.is_array());
let pets = pets_data.as_array().unwrap();
assert!(!pets.is_empty(), "Mock should return pets data");
for pet in pets {
assert_eq!(pet["status"], status);
}
}
Ok(())
}
#[actix_web::test]
async fn test_find_pets_by_multiple_status_query_parameter() -> anyhow::Result<()> {
let statuses = vec!["available", "pending"];
if should_use_live_api() {
let server = create_server_with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let client = HttpClient::new().with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let tool_metadata = server
.get_tool_metadata("findPetsByStatus")
.expect("findPetsByStatus tool should be registered");
let arguments = json!({
"status": statuses
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.request_method, "GET");
assert!(response.request_url.contains("/pet/findByStatus"));
for status in &statuses {
assert!(response.request_url.contains(&format!("status={status}")));
}
} else {
let mut mock_server = MockPetstoreServer::new_with_port(9104).await;
let _mock = mock_server.mock_find_pets_by_status("available");
let server = create_server_with_base_url(mock_server.base_url())?;
let client = HttpClient::new().with_base_url(mock_server.base_url())?;
let tool_metadata = server
.get_tool_metadata("findPetsByStatus")
.expect("findPetsByStatus tool should be registered");
let arguments = json!({
"status": ["available"]
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.status_code, 200);
assert!(response.request_url.contains("status=available"));
}
Ok(())
}
#[actix_web::test]
async fn test_add_pet_json_request_body() -> anyhow::Result<()> {
let pet_data = json!({
"name": "new doggie",
"category": {
"id": 1,
"name": "Dogs"
},
"photoUrls": ["https://example.com/photo1.jpg"],
"tags": [
{
"id": 1,
"name": "tag1"
}
],
"status": "available"
});
if should_use_live_api() {
let server = create_server_with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let client = HttpClient::new().with_base_url(Url::parse(LIVE_API_BASE_URL)?)?;
let tool_metadata = server
.get_tool_metadata("addPet")
.expect("addPet tool should be registered");
let arguments = json!({
"request_body": pet_data
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.request_method, "POST");
assert!(response.request_url.contains("/pet"));
assert!(!response.request_body.is_empty());
let request_body: serde_json::Value = serde_json::from_str(&response.request_body)?;
assert_eq!(request_body["name"], "new doggie");
assert_eq!(request_body["status"], "available");
} else {
let mut mock_server = MockPetstoreServer::new_with_port(9105).await;
let _mock = mock_server.mock_add_pet();
let server = create_server_with_base_url(mock_server.base_url())?;
let client = HttpClient::new().with_base_url(mock_server.base_url())?;
let tool_metadata = server
.get_tool_metadata("addPet")
.expect("addPet tool should be registered");
let arguments = json!({
"request_body": pet_data
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.status_code, 201);
assert_eq!(response.request_method, "POST");
assert!(response.request_url.contains("/pet"));
assert!(!response.request_body.is_empty());
let request_body: serde_json::Value = serde_json::from_str(&response.request_body)?;
assert_eq!(request_body["name"], "new doggie");
assert_eq!(request_body["status"], "available");
let created_pet = response.json()?;
assert_eq!(created_pet["id"], 123);
assert_eq!(created_pet["name"], "new doggie");
}
Ok(())
}
#[actix_web::test]
async fn test_available_tools() -> anyhow::Result<()> {
let server = create_server_with_base_url(Url::parse("http://example.com")?)?;
let tool_names = server.get_tool_names();
println!("Available tools: {tool_names:?}");
assert!(!tool_names.is_empty());
assert!(tool_names.contains(&"getPetById".to_string()));
assert!(tool_names.contains(&"addPet".to_string()));
Ok(())
}
fn create_server_with_base_url(base_url: Url) -> anyhow::Result<Server> {
let spec_content = include_str!("assets/petstore-openapi-norefs.json");
let json_value: serde_json::Value = serde_json::from_str(spec_content)?;
let mut server = Server::builder()
.openapi_spec(json_value)
.base_url(base_url)
.build();
server.load_openapi_spec()?;
Ok(server)
}
#[actix_web::test]
async fn test_url_construction_with_path_parameters() -> anyhow::Result<()> {
let mock_server = MockPetstoreServer::new_with_port(9106).await;
let server = create_server_with_base_url(mock_server.base_url())?;
let client = HttpClient::new().with_base_url(mock_server.base_url())?;
let tool_metadata = server
.get_tool_metadata("getPetById")
.expect("getPetById tool should be registered");
let arguments = json!({
"petId": 456
});
let result = client.execute_tool_call(tool_metadata, &arguments).await;
match result {
Ok(response) => {
assert!(response.request_url.contains("/pet/456"));
}
Err(e) => {
let error_message = e.to_string();
assert!(error_message.contains("/pet/456"));
}
}
Ok(())
}
#[actix_web::test]
async fn test_content_type_header_for_json() -> anyhow::Result<()> {
let mut mock_server = MockPetstoreServer::new_with_port(9108).await;
let _mock = mock_server.mock_add_pet();
let server = create_server_with_base_url(mock_server.base_url())?;
let client = HttpClient::new().with_base_url(mock_server.base_url())?;
let tool_metadata = server
.get_tool_metadata("addPet")
.expect("addPet tool should be registered");
let arguments = json!({
"request_body": {
"name": "test pet",
"status": "available",
"photoUrls": []
}
});
let response = client.execute_tool_call(tool_metadata, &arguments).await?;
assert!(response.is_success);
assert_eq!(response.status_code, 201);
Ok(())
}
impl MockPetstoreServer {
pub fn mock_get_pet_by_id(&mut self, pet_id: u64) -> Mock {
let pet_response = json!({
"id": pet_id,
"name": "doggie",
"category": {
"id": 1,
"name": "Dogs"
},
"photoUrls": ["https://example.com/photo1.jpg"],
"tags": [
{
"id": 1,
"name": "tag1"
}
],
"status": "available"
});
self.server
.mock("GET", format!("/pet/{pet_id}").as_str())
.with_status(200)
.with_header("content-type", "application/json")
.with_body(pet_response.to_string())
.create()
}
pub fn mock_get_pet_by_id_not_found(&mut self, pet_id: u64) -> Mock {
self.server
.mock("GET", format!("/pet/{pet_id}").as_str())
.with_status(404)
.with_header("content-type", "application/json")
.with_body(json!({"message": "Pet not found"}).to_string())
.create()
}
pub fn mock_find_pets_by_status(&mut self, status: &str) -> Mock {
let pets_response = json!([
{
"id": 1,
"name": "doggie",
"category": {
"id": 1,
"name": "Dogs"
},
"photoUrls": ["https://example.com/photo1.jpg"],
"tags": [
{
"id": 1,
"name": "tag1"
}
],
"status": status
},
{
"id": 2,
"name": "kitty",
"category": {
"id": 2,
"name": "Cats"
},
"photoUrls": ["https://example.com/photo2.jpg"],
"tags": [
{
"id": 2,
"name": "tag2"
}
],
"status": status
}
]);
self.server
.mock("GET", "/pet/findByStatus")
.match_query(mockito::Matcher::AnyOf(vec![
mockito::Matcher::UrlEncoded("status".to_string(), status.to_string()),
mockito::Matcher::Regex(r"status=.+".to_string()),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(pets_response.to_string())
.create()
}
pub fn mock_add_pet(&mut self) -> Mock {
let pet_response = json!({
"id": 123,
"name": "new doggie",
"category": {
"id": 1,
"name": "Dogs"
},
"photoUrls": ["https://example.com/photo1.jpg"],
"tags": [
{
"id": 1,
"name": "tag1"
}
],
"status": "available"
});
self.server
.mock("POST", "/pet")
.match_header("content-type", "application/json")
.with_status(201)
.with_header("content-type", "application/json")
.with_body(pet_response.to_string())
.create()
}
}