use pretty_assertions::assert_eq;
use serde_json::json;
use rho_sdk::HostInputResponse;
use rmcp::model::{ElicitRequestParams, ElicitationAction};
use super::{McpElicitationService, McpElicitationSupport, McpInFlightCalls};
fn form_request(properties: serde_json::Value, required: &[&str]) -> ElicitRequestParams {
serde_json::from_value(json!({
"message": "which colour?",
"requestedSchema": {
"type": "object",
"properties": properties,
"required": required,
},
}))
.expect("elicitation request fixture parses")
}
fn colour_request() -> ElicitRequestParams {
form_request(
json!({"colour": {"type": "string", "enum": ["red", "blue"]}}),
&["colour"],
)
}
#[tokio::test]
async fn unroutable_elicitations_are_declined() {
let calls = McpInFlightCalls::new();
let service =
McpElicitationService::new("live", calls.clone(), McpElicitationSupport::Available);
let with_no_call = service.elicit(colour_request()).await.unwrap();
assert_eq!(with_no_call.action, ElicitationAction::Decline);
assert_eq!(with_no_call.content, None);
let (_first, _first_questions) = calls.register();
let (_second, _second_questions) = calls.register();
let with_two_calls = service.elicit(colour_request()).await.unwrap();
assert_eq!(with_two_calls.action, ElicitationAction::Decline);
assert_eq!(with_two_calls.content, None);
}
#[tokio::test]
async fn a_run_that_cannot_ask_anyone_declines() {
let calls = McpInFlightCalls::new();
let (_registration, mut questions) = calls.register();
let service = McpElicitationService::new("live", calls, McpElicitationSupport::Unavailable);
let result = service.elicit(colour_request()).await.unwrap();
assert_eq!(result.action, ElicitationAction::Decline);
assert!(questions.try_recv().is_err(), "no question was raised");
}
#[tokio::test]
async fn url_elicitation_is_declined() {
let calls = McpInFlightCalls::new();
let (_registration, _questions) = calls.register();
let service = McpElicitationService::new("live", calls, McpElicitationSupport::Available);
let request: ElicitRequestParams = serde_json::from_value(json!({
"mode": "url",
"message": "sign in",
"url": "https://example.com/auth",
"elicitationId": "one",
}))
.unwrap();
let result = service.elicit(request).await.unwrap();
assert_eq!(result.action, ElicitationAction::Decline);
}
#[tokio::test]
async fn answering_the_form_produces_the_matching_action() {
let calls = McpInFlightCalls::new();
let service =
McpElicitationService::new("live", calls.clone(), McpElicitationSupport::Available);
let (registration, mut questions) = calls.register();
let (accepted, ()) = tokio::join!(service.elicit(colour_request()), async {
let question = questions.recv().await.expect("the form reached the caller");
assert_eq!(question.request.title(), "MCP server `live`: which colour?");
let _ = question
.reply
.send(Ok(HostInputResponse::new().answer("colour", ["blue"])));
});
let accepted = accepted.unwrap();
assert_eq!(accepted.action, ElicitationAction::Accept);
assert_eq!(accepted.content, Some(json!({"colour": "blue"})));
let (cancelled, ()) = tokio::join!(service.elicit(colour_request()), async {
let question = questions.recv().await.expect("the form reached the caller");
let _ = question.reply.send(Err(rho_sdk::Error::Cancelled));
});
assert_eq!(cancelled.unwrap().action, ElicitationAction::Cancel);
drop(registration);
let declined = service.elicit(colour_request()).await.unwrap();
assert_eq!(declined.action, ElicitationAction::Decline);
}