use rmcp::{model::*, object, service::*};
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
#[cfg(feature = "schemars")]
use serde::{Deserialize, Serialize};
use serde_json::json;
#[tokio::test]
async fn test_elicitation_serialization() {
let accept = ElicitationAction::Accept;
let decline = ElicitationAction::Decline;
let cancel = ElicitationAction::Cancel;
assert_eq!(serde_json::to_string(&accept).unwrap(), "\"accept\"");
assert_eq!(serde_json::to_string(&decline).unwrap(), "\"decline\"");
assert_eq!(serde_json::to_string(&cancel).unwrap(), "\"cancel\"");
assert_eq!(
serde_json::from_str::<ElicitationAction>("\"accept\"").unwrap(),
ElicitationAction::Accept
);
assert_eq!(
serde_json::from_str::<ElicitationAction>("\"decline\"").unwrap(),
ElicitationAction::Decline
);
assert_eq!(
serde_json::from_str::<ElicitationAction>("\"cancel\"").unwrap(),
ElicitationAction::Cancel
);
}
#[tokio::test]
async fn test_elicitation_request_param_serialization() {
let schema = ElicitationSchema::builder()
.required_property("email", PrimitiveSchema::String(StringSchema::email()))
.build()
.unwrap();
let request_param = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Please provide your email address".to_string(),
requested_schema: schema,
};
let json = serde_json::to_value(&request_param).unwrap();
let expected = json!({
"mode": "form",
"message": "Please provide your email address",
"requestedSchema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
}
},
"required": ["email"]
}
});
assert_eq!(json, expected);
let deserialized: CreateElicitationRequestParams = serde_json::from_value(expected).unwrap();
match (&deserialized, &request_param) {
(
CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: msg1,
requested_schema: schema1,
},
CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: msg2,
requested_schema: schema2,
},
) => {
assert_eq!(msg1, msg2);
assert_eq!(schema1, schema2);
}
_ => panic!("Expected FormElicitationParam variant"),
}
}
#[tokio::test]
async fn test_elicitation_result_serialization() {
let accept_result = CreateElicitationResult {
action: ElicitationAction::Accept,
content: Some(json!({"email": "user@example.com"})),
meta: None,
};
let json = serde_json::to_value(&accept_result).unwrap();
let expected = json!({
"action": "accept",
"content": {"email": "user@example.com"}
});
assert_eq!(json, expected);
let decline_result = CreateElicitationResult {
action: ElicitationAction::Decline,
content: None,
meta: None,
};
let json = serde_json::to_value(&decline_result).unwrap();
let expected = json!({
"action": "decline"
});
assert_eq!(json, expected);
let deserialized: CreateElicitationResult = serde_json::from_value(expected).unwrap();
assert_eq!(deserialized.action, ElicitationAction::Decline);
assert_eq!(deserialized.content, None);
assert_eq!(deserialized.meta, None);
let meta_result =
CreateElicitationResult::new(ElicitationAction::Accept).with_meta(Meta(object!({
"traceId": "elicitation-123"
})));
let json = serde_json::to_value(&meta_result).unwrap();
let expected = json!({
"action": "accept",
"_meta": {"traceId": "elicitation-123"}
});
assert_eq!(json, expected);
let deserialized: CreateElicitationResult = serde_json::from_value(expected).unwrap();
assert_eq!(
deserialized.meta,
Some(Meta(object!({ "traceId": "elicitation-123" })))
);
}
#[tokio::test]
async fn test_elicitation_json_rpc_protocol() {
let schema = ElicitationSchema::builder()
.required_property(
"confirmation",
PrimitiveSchema::Boolean(BooleanSchema::new()),
)
.build()
.unwrap();
let request = JsonRpcRequest {
jsonrpc: JsonRpcVersion2_0,
id: RequestId::Number(1),
request: CreateElicitationRequest::new(
CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Do you want to continue?".to_string(),
requested_schema: schema,
},
),
};
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json["jsonrpc"], "2.0");
assert_eq!(json["id"], 1);
assert_eq!(json["method"], "elicitation/create");
assert_eq!(json["params"]["message"], "Do you want to continue?");
let deserialized: JsonRpcRequest<CreateElicitationRequest> =
serde_json::from_value(json).unwrap();
assert_eq!(deserialized.id, RequestId::Number(1));
match &deserialized.request.params {
CreateElicitationRequestParams::FormElicitationParams { message, .. } => {
assert_eq!(message, "Do you want to continue?");
}
_ => panic!("Expected FormElicitationParam variant"),
}
}
#[tokio::test]
async fn test_elicitation_action_types() {
let actions = [
ElicitationAction::Accept,
ElicitationAction::Decline,
ElicitationAction::Cancel,
];
let serialized: Vec<String> = actions
.iter()
.map(|action| serde_json::to_string(action).unwrap())
.collect();
assert_eq!(serialized.len(), 3);
assert!(serialized.contains(&"\"accept\"".to_string()));
assert!(serialized.contains(&"\"decline\"".to_string()));
assert!(serialized.contains(&"\"cancel\"".to_string()));
for action in actions {
let json = serde_json::to_string(&action).unwrap();
let deserialized: ElicitationAction = serde_json::from_str(&json).unwrap();
assert_eq!(action, deserialized);
}
}
#[tokio::test]
async fn test_elicitation_spec_compliance() {
assert_eq!(ElicitationCreateRequestMethod::VALUE, "elicitation/create");
assert_eq!(
ElicitationResponseNotificationMethod::VALUE,
"notifications/elicitation/response"
);
let actions = [
ElicitationAction::Accept,
ElicitationAction::Decline,
ElicitationAction::Cancel,
];
let serialized: Vec<String> = actions
.iter()
.map(|a| serde_json::to_string(a).unwrap())
.collect();
assert_eq!(serialized, vec!["\"accept\"", "\"decline\"", "\"cancel\""]);
}
#[tokio::test]
async fn test_elicitation_error_handling() {
let minimal_schema_request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Test message".to_string(),
requested_schema: ElicitationSchema::builder().build().unwrap(),
};
let _json = serde_json::to_value(&minimal_schema_request).unwrap();
let empty_message_request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "".to_string(),
requested_schema: ElicitationSchema::builder()
.property("value", PrimitiveSchema::String(StringSchema::new()))
.build()
.unwrap(),
};
let _json = serde_json::to_value(&empty_message_request).unwrap();
let invalid_action_json = json!("invalid_action");
let result = serde_json::from_value::<ElicitationAction>(invalid_action_json);
assert!(result.is_err());
}
#[tokio::test]
async fn test_elicitation_performance() {
let schema = ElicitationSchema::builder()
.property("data", PrimitiveSchema::String(StringSchema::new()))
.build()
.unwrap();
let request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Performance test message".to_string(),
requested_schema: schema,
};
let start = std::time::Instant::now();
for _ in 0..1000 {
let json = serde_json::to_value(&request).unwrap();
let _deserialized: CreateElicitationRequestParams = serde_json::from_value(json).unwrap();
}
let duration = start.elapsed();
println!(
"1000 elicitation serialization/deserialization cycles took: {:?}",
duration
);
assert!(
duration.as_millis() < 1000,
"Performance test took too long: {:?}",
duration
);
}
#[tokio::test]
async fn test_elicitation_capabilities() {
use rmcp::model::{ClientCapabilities, ElicitationCapability};
let mut elicitation_cap = ElicitationCapability::default();
assert_eq!(elicitation_cap.form, None);
assert_eq!(elicitation_cap.url, None);
elicitation_cap.form = Some(FormElicitationCapability {
schema_validation: Some(true),
});
let json = serde_json::to_value(&elicitation_cap).unwrap();
let expected = json!({"form":{"schemaValidation": true}});
assert_eq!(json, expected);
let deserialized: ElicitationCapability = serde_json::from_value(expected).unwrap();
assert_eq!(
deserialized.form.as_ref().unwrap().schema_validation,
Some(true)
);
let client_caps = ClientCapabilities::builder()
.enable_elicitation()
.enable_elicitation_schema_validation()
.build();
assert!(client_caps.elicitation.is_some());
assert_eq!(
client_caps
.elicitation
.as_ref()
.unwrap()
.form
.as_ref()
.unwrap()
.schema_validation,
Some(true)
);
let json = serde_json::to_value(&client_caps).unwrap();
assert!(
json["elicitation"]["form"]["schemaValidation"]
.as_bool()
.unwrap_or(false)
);
}
#[tokio::test]
async fn test_elicitation_convenience_methods() {
let confirmation_schema = serde_json::json!({
"type": "boolean",
"description": "User confirmation (true for yes, false for no)"
});
assert_eq!(confirmation_schema["type"], "boolean");
assert!(confirmation_schema["description"].is_string());
let text_schema = serde_json::json!({
"type": "string",
"description": "User text input"
});
assert_eq!(text_schema["type"], "string");
assert!(text_schema.get("minLength").is_none());
let required_text_schema = serde_json::json!({
"type": "string",
"description": "User text input",
"minLength": 1
});
assert_eq!(required_text_schema["minLength"], 1);
let options = ["Option A", "Option B", "Option C"];
let choice_schema = serde_json::json!({
"type": "integer",
"minimum": 0,
"maximum": options.len() - 1,
"description": format!("Choose an option: {}", options.join(", "))
});
assert_eq!(choice_schema["type"], "integer");
assert_eq!(choice_schema["minimum"], 0);
assert_eq!(choice_schema["maximum"], 2);
assert!(
choice_schema["description"]
.as_str()
.unwrap()
.contains("Option A")
);
let confirmation_request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Test confirmation".to_string(),
requested_schema: ElicitationSchema::builder()
.property(
"confirmed",
PrimitiveSchema::Boolean(
BooleanSchema::new()
.description("User confirmation (true for yes, false for no)"),
),
)
.build()
.unwrap(),
};
let json = serde_json::to_value(&confirmation_request).unwrap();
assert_eq!(json["message"], "Test confirmation");
assert_eq!(json["requestedSchema"]["type"], "object");
assert_eq!(
json["requestedSchema"]["properties"]["confirmed"]["type"],
"boolean"
);
}
#[tokio::test]
async fn test_elicitation_structured_schemas() {
let schema = ElicitationSchema::builder()
.required_string_with("name", |s| s.length(1, 100))
.required_email("email")
.required_integer("age", 0, 150)
.optional_bool("newsletter", false)
.required_enum_schema(
"country",
EnumSchema::builder(vec!["US".to_string(), "UK".to_string(), "CA".to_string()]).build(),
)
.description("User registration information")
.build()
.unwrap();
let request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Please provide your user information".to_string(),
requested_schema: schema,
};
let json = serde_json::to_value(&request).unwrap();
let deserialized: CreateElicitationRequestParams = serde_json::from_value(json).unwrap();
match deserialized {
CreateElicitationRequestParams::FormElicitationParams {
message,
requested_schema,
..
} => {
assert_eq!(message, "Please provide your user information");
assert_eq!(requested_schema.properties.len(), 5);
assert!(requested_schema.properties.contains_key("name"));
assert!(requested_schema.properties.contains_key("email"));
assert!(requested_schema.properties.contains_key("age"));
assert!(requested_schema.properties.contains_key("newsletter"));
assert!(requested_schema.properties.contains_key("country"));
assert_eq!(
requested_schema.required,
Some(vec![
"name".to_string(),
"email".to_string(),
"age".to_string(),
"country".to_string()
])
);
}
_ => panic!("Expected FormElicitationParam variant"),
}
}
#[cfg(feature = "schemars")]
mod typed_elicitation_tests {
use super::*;
#[derive(Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[schemars(description = "User confirmation with optional reasoning")]
struct UserConfirmation {
#[schemars(description = "User's decision (true for yes, false for no)")]
confirmed: bool,
#[schemars(description = "Optional reason for the decision")]
reason: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[schemars(description = "Complete user profile information")]
struct UserProfile {
#[schemars(description = "Full name")]
name: String,
#[schemars(description = "Email address")]
email: String,
#[schemars(description = "Age in years")]
age: u8,
#[schemars(description = "User preferences")]
preferences: UserPreferences,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
struct UserPreferences {
#[schemars(description = "UI theme preference")]
theme: Theme,
#[schemars(description = "Enable notifications")]
notifications: bool,
#[schemars(description = "Language preference")]
language: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[schemars(description = "Available UI themes")]
enum Theme {
#[schemars(description = "Light theme")]
Light,
#[schemars(description = "Dark theme")]
Dark,
#[schemars(description = "Auto-detect based on system")]
Auto,
}
rmcp::elicit_safe!(UserConfirmation, UserProfile, UserPreferences);
#[tokio::test]
async fn test_typed_elicitation_simple_schema() {
let schema = rmcp::handler::server::tool::schema_for_type::<UserConfirmation>();
assert!(schema.contains_key("type"));
assert_eq!(schema.get("type"), Some(&json!("object")));
assert!(schema.contains_key("properties"));
if let Some(properties) = schema.get("properties") {
assert!(properties.is_object());
let props = properties.as_object().unwrap();
assert!(props.contains_key("confirmed"));
assert!(props.contains_key("reason"));
if let Some(confirmed_schema) = props.get("confirmed") {
let confirmed_obj = confirmed_schema.as_object().unwrap();
assert_eq!(confirmed_obj.get("type"), Some(&json!("boolean")));
}
if let Some(reason_schema) = props.get("reason") {
assert!(reason_schema.is_object());
}
}
}
#[tokio::test]
async fn test_typed_elicitation_complex_schema() {
let schema = rmcp::handler::server::tool::schema_for_type::<UserProfile>();
assert!(schema.contains_key("type"));
assert_eq!(schema.get("type"), Some(&json!("object")));
if let Some(properties) = schema.get("properties") {
let props = properties.as_object().unwrap();
assert!(props.contains_key("name"));
assert!(props.contains_key("email"));
assert!(props.contains_key("age"));
assert!(props.contains_key("preferences"));
if let Some(name_schema) = props.get("name") {
let name_obj = name_schema.as_object().unwrap();
assert_eq!(name_obj.get("type"), Some(&json!("string")));
}
if let Some(email_schema) = props.get("email") {
let email_obj = email_schema.as_object().unwrap();
assert_eq!(email_obj.get("type"), Some(&json!("string")));
}
if let Some(age_schema) = props.get("age") {
let age_obj = age_schema.as_object().unwrap();
assert_eq!(age_obj.get("type"), Some(&json!("integer")));
}
}
}
#[tokio::test]
async fn test_enum_schema_generation() {
let schema = rmcp::handler::server::tool::schema_for_type::<Theme>();
assert!(
schema.contains_key("type")
|| schema.contains_key("oneOf")
|| schema.contains_key("enum")
);
let json = serde_json::to_string(&schema).unwrap();
assert!(!json.is_empty());
}
#[tokio::test]
async fn test_nested_structure_schema() {
let preferences_schema = rmcp::handler::server::tool::schema_for_type::<UserPreferences>();
assert!(preferences_schema.contains_key("type"));
assert_eq!(preferences_schema.get("type"), Some(&json!("object")));
if let Some(properties) = preferences_schema.get("properties") {
let props = properties.as_object().unwrap();
assert!(props.contains_key("theme"));
assert!(props.contains_key("notifications"));
assert!(props.contains_key("language"));
}
}
}
#[tokio::test]
async fn test_elicitation_multi_select_enum() {
let enum_schema = EnumSchema::builder(vec!["A".into(), "B".into(), "C".into()])
.multiselect()
.enum_titles(vec![
"A name".to_string(),
"B name".to_string(),
"C name".to_string(),
])
.expect("Number of title must match number of enum values")
.min_items(1)
.expect("Min items must be less than or equal to number of enum values")
.max_items(2)
.expect("Max items must be less than or equal to number of enum values")
.build();
let schema = ElicitationSchema::builder()
.required_enum_schema("choices", enum_schema)
.build()
.unwrap();
let request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Please provide your user information".to_string(),
requested_schema: schema,
};
let json = serde_json::to_value(&request).unwrap();
let deserialized: CreateElicitationRequestParams = serde_json::from_value(json).unwrap();
match deserialized {
CreateElicitationRequestParams::FormElicitationParams {
message,
requested_schema,
..
} => {
assert_eq!(message, "Please provide your user information");
assert_eq!(requested_schema.properties.len(), 1);
assert!(requested_schema.properties.contains_key("choices"));
assert_eq!(requested_schema.required, Some(vec!["choices".to_string()]));
assert!(matches!(
requested_schema.properties.get("choices").unwrap(),
PrimitiveSchema::Enum(EnumSchema::Multi(_))
));
if let Some(PrimitiveSchema::Enum(schema)) = requested_schema.properties.get("choices")
{
assert_eq!(
schema,
&EnumSchema::Multi(MultiSelectEnumSchema::Titled(
TitledMultiSelectEnumSchema::new(TitledItems {
any_of: vec![
ConstTitle {
const_: "A".to_string(),
title: "A name".to_string()
},
ConstTitle {
const_: "B".to_string(),
title: "B name".to_string()
},
ConstTitle {
const_: "C".to_string(),
title: "C name".to_string()
},
],
})
.with_min_items(1)
.with_max_items(2)
))
)
}
}
_ => panic!("Expected FormElicitationParam variant"),
}
}
#[tokio::test]
async fn test_elicitation_single_select_enum() {
let enum_schema = EnumSchema::builder(vec!["A".into(), "B".into(), "C".into()])
.enum_titles(vec![
"A name".to_string(),
"B name".to_string(),
"C name".to_string(),
])
.expect("Number of title must match number of enum values")
.build();
let schema = ElicitationSchema::builder()
.required_enum_schema("choices", enum_schema)
.build()
.unwrap();
let request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Please provide your user information".to_string(),
requested_schema: schema,
};
let json = serde_json::to_value(&request).unwrap();
let deserialized: CreateElicitationRequestParams = serde_json::from_value(json).unwrap();
match deserialized {
CreateElicitationRequestParams::FormElicitationParams {
message,
requested_schema,
..
} => {
assert_eq!(message, "Please provide your user information");
assert_eq!(requested_schema.properties.len(), 1);
assert!(requested_schema.properties.contains_key("choices"));
assert_eq!(requested_schema.required, Some(vec!["choices".to_string()]));
assert!(matches!(
requested_schema.properties.get("choices").unwrap(),
PrimitiveSchema::Enum(EnumSchema::Single(_))
));
if let Some(PrimitiveSchema::Enum(schema)) = requested_schema.properties.get("choices")
{
assert_eq!(
schema,
&EnumSchema::Single(SingleSelectEnumSchema::Titled(
TitledSingleSelectEnumSchema::new(vec![
ConstTitle {
const_: "A".to_string(),
title: "A name".to_string()
},
ConstTitle {
const_: "B".to_string(),
title: "B name".to_string()
},
ConstTitle {
const_: "C".to_string(),
title: "C name".to_string()
}
])
))
)
}
}
_ => panic!("Expected FormElicitationParam variant"),
}
}
#[cfg(all(feature = "client", feature = "server"))]
#[tokio::test]
async fn test_elicitation_direction_server_to_client() {
use rmcp::model::*;
use serde_json::json;
let schema = ElicitationSchema::builder()
.property(
"name",
PrimitiveSchema::String(StringSchema::new().description("Enter your name")),
)
.build()
.unwrap();
let elicitation_request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Please enter your name".to_string(),
requested_schema: schema,
};
let serialized = serde_json::to_value(&elicitation_request).unwrap();
assert_eq!(serialized["message"], "Please enter your name");
assert_eq!(serialized["requestedSchema"]["type"], "object");
let _server_request =
ServerRequest::CreateElicitationRequest(CreateElicitationRequest::new(elicitation_request));
let client_result = ClientResult::CreateElicitationResult(CreateElicitationResult {
action: ElicitationAction::Accept,
content: Some(json!("John Doe")),
meta: None,
});
match client_result {
ClientResult::CreateElicitationResult(result) => {
assert_eq!(result.action, ElicitationAction::Accept);
assert_eq!(result.content, Some(json!("John Doe")));
}
_ => panic!("CreateElicitationResult should be part of ClientResult"),
}
}
#[cfg(all(feature = "client", feature = "server"))]
#[tokio::test]
async fn test_elicitation_json_rpc_direction() {
use rmcp::model::*;
use serde_json::json;
let schema = ElicitationSchema::builder()
.property(
"continue",
PrimitiveSchema::Boolean(BooleanSchema::new().description("Do you want to continue?")),
)
.build()
.unwrap();
let server_request = ServerJsonRpcMessage::request(
ServerRequest::CreateElicitationRequest(CreateElicitationRequest::new(
CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Do you want to continue?".to_string(),
requested_schema: schema,
},
)),
RequestId::Number(1),
);
let server_json = serde_json::to_value(&server_request).unwrap();
assert_eq!(server_json["method"], "elicitation/create");
assert_eq!(server_json["id"], 1);
assert_eq!(server_json["params"]["message"], "Do you want to continue?");
let client_response = ClientJsonRpcMessage::response(
ClientResult::CreateElicitationResult(CreateElicitationResult {
action: ElicitationAction::Accept,
content: Some(json!(true)),
meta: None,
}),
RequestId::Number(1),
);
let client_json = serde_json::to_value(&client_response).unwrap();
assert_eq!(client_json["id"], 1);
if let Some(result) = client_json["result"].as_object() {
assert_eq!(result["action"], "accept");
assert_eq!(result["content"], true);
} else {
panic!("Client response should contain result");
}
}
#[cfg(all(feature = "client", feature = "server"))]
#[tokio::test]
async fn test_elicitation_actions_compliance() {
use rmcp::model::*;
let actions = [
ElicitationAction::Accept,
ElicitationAction::Decline,
ElicitationAction::Cancel,
];
for action in actions {
let result = CreateElicitationResult {
action: action.clone(),
content: match action {
ElicitationAction::Accept => Some(serde_json::json!("some data")),
_ => None,
},
meta: None,
};
let json = serde_json::to_value(&result).unwrap();
match action {
ElicitationAction::Accept => {
assert_eq!(json["action"], "accept");
assert!(json["content"].is_string());
}
ElicitationAction::Decline => {
assert_eq!(json["action"], "decline");
assert!(json.get("content").is_none() || json["content"].is_null());
}
ElicitationAction::Cancel => {
assert_eq!(json["action"], "cancel");
assert!(json.get("content").is_none() || json["content"].is_null());
}
}
}
}
#[tokio::test]
async fn test_elicitation_result_in_client_result() {
use rmcp::model::*;
let result = ClientResult::CreateElicitationResult(CreateElicitationResult {
action: ElicitationAction::Decline,
content: None,
meta: None,
});
match result {
ClientResult::CreateElicitationResult(elicit_result) => {
assert_eq!(elicit_result.action, ElicitationAction::Decline);
assert_eq!(elicit_result.content, None);
}
_ => panic!("CreateElicitationResult should be part of ClientResult"),
}
}
#[tokio::test]
async fn test_elicitation_capability_structure() {
let default_cap = ElicitationCapability::default();
assert!(default_cap.form.is_none());
assert!(default_cap.url.is_none());
let cap_with_validation = ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
};
assert_eq!(
cap_with_validation.form.as_ref().unwrap().schema_validation,
Some(true)
);
let cap_without_validation = ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(false),
}),
url: None,
};
assert_eq!(
cap_without_validation
.form
.as_ref()
.unwrap()
.schema_validation,
Some(false)
);
let json = serde_json::to_value(&cap_with_validation).unwrap();
assert_eq!(
json,
serde_json::json!({
"form": {
"schemaValidation": true
}
})
);
let deserialized: ElicitationCapability = serde_json::from_value(json).unwrap();
assert_eq!(
deserialized.form.as_ref().unwrap().schema_validation,
Some(true)
);
}
#[tokio::test]
async fn test_client_capabilities_with_elicitation() {
let capabilities = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
})
.build();
assert!(capabilities.elicitation.is_some());
assert_eq!(
capabilities
.elicitation
.as_ref()
.unwrap()
.form
.as_ref()
.unwrap()
.schema_validation,
Some(true)
);
let json = serde_json::to_value(&capabilities).unwrap();
assert!(
json["elicitation"]["form"]["schemaValidation"]
.as_bool()
.unwrap_or(false)
);
let capabilities_without = ClientCapabilities::default();
assert!(capabilities_without.elicitation.is_none());
}
#[tokio::test]
async fn test_initialize_request_with_elicitation() {
let init_param = InitializeRequestParams::new(
ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
})
.build(),
Implementation::new("test-client", "1.0.0"),
);
assert!(init_param.capabilities.elicitation.is_some());
assert_eq!(
init_param
.capabilities
.elicitation
.as_ref()
.unwrap()
.form
.as_ref()
.unwrap()
.schema_validation,
Some(true)
);
let json = serde_json::to_value(&init_param).unwrap();
assert!(
json["capabilities"]["elicitation"]["form"]["schemaValidation"]
.as_bool()
.unwrap_or(false)
);
}
#[tokio::test]
async fn test_capability_checking_logic() {
let client_with_capability = InitializeRequestParams::new(
ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
})
.build(),
Implementation::new("test-client", "1.0.0"),
);
let supports_elicitation = client_with_capability.capabilities.elicitation.is_some();
assert!(supports_elicitation);
let client_without_capability = InitializeRequestParams::new(
ClientCapabilities::default(),
Implementation::new("test-client", "1.0.0"),
);
let supports_elicitation = client_without_capability.capabilities.elicitation.is_some();
assert!(!supports_elicitation);
}
#[tokio::test]
async fn test_capability_not_supported_error_message() {
let error = ElicitationError::CapabilityNotSupported;
let message = format!("{}", error);
assert_eq!(
message,
"Client does not support elicitation - capability not declared during initialization"
);
}
#[tokio::test]
async fn test_elicitation_error_variants() {
let capability_error = ElicitationError::CapabilityNotSupported;
assert_eq!(
format!("{}", capability_error),
"Client does not support elicitation - capability not declared during initialization"
);
let user_declined = ElicitationError::UserDeclined;
assert_eq!(
format!("{}", user_declined),
"User explicitly declined the request"
);
let user_cancelled = ElicitationError::UserCancelled;
assert_eq!(
format!("{}", user_cancelled),
"User cancelled/dismissed the request"
);
let no_content = ElicitationError::NoContent;
assert_eq!(format!("{}", no_content), "No response content provided");
let service_error = ElicitationError::Service(ServiceError::UnexpectedResponse);
let message = format!("{}", service_error);
assert!(message.starts_with("Service error:"));
let json_error = serde_json::from_str::<i32>("\"not_an_integer\"").unwrap_err();
let data = serde_json::json!({"key": "value"});
let parse_error = ElicitationError::ParseError {
error: json_error,
data: data.clone(),
};
let message = format!("{}", parse_error);
assert!(message.starts_with("Failed to parse response data:"));
assert!(message.contains("Received data:"));
match capability_error {
ElicitationError::CapabilityNotSupported => {} _ => panic!("Should match CapabilityNotSupported"),
}
match user_declined {
ElicitationError::UserDeclined => {} _ => panic!("Should match UserDeclined"),
}
match user_cancelled {
ElicitationError::UserCancelled => {} _ => panic!("Should match UserCancelled"),
}
match no_content {
ElicitationError::NoContent => {} _ => panic!("Should match NoContent"),
}
}
#[tokio::test]
async fn test_elicitation_capability_serialization() {
use rmcp::model::ElicitationCapability;
let default_cap = ElicitationCapability::default();
let json = serde_json::to_value(&default_cap).unwrap();
assert_eq!(json, serde_json::json!({}));
let cap_with_validation = ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
};
let json = serde_json::to_value(&cap_with_validation).unwrap();
assert_eq!(
json,
serde_json::json!({
"form": {
"schemaValidation": true
}
})
);
let cap_without_validation = ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(false),
}),
url: None,
};
let json = serde_json::to_value(&cap_without_validation).unwrap();
assert_eq!(
json,
serde_json::json!({
"form": {
"schemaValidation": false
}
})
);
let deserialized: ElicitationCapability = serde_json::from_value(serde_json::json!({
"form":{"schemaValidation": true}
}))
.unwrap();
assert_eq!(deserialized.form.unwrap().schema_validation, Some(true));
}
#[tokio::test]
async fn test_client_capabilities_elicitation_builder() {
use rmcp::model::{ClientCapabilities, ElicitationCapability};
let caps = ClientCapabilities::builder().enable_elicitation().build();
assert!(caps.elicitation.is_some());
assert_eq!(caps.elicitation.as_ref().unwrap().form, None);
let caps_with_validation = ClientCapabilities::builder()
.enable_elicitation()
.enable_elicitation_schema_validation()
.build();
assert!(caps_with_validation.elicitation.is_some());
assert_eq!(
caps_with_validation
.elicitation
.as_ref()
.unwrap()
.form
.as_ref()
.unwrap()
.schema_validation,
Some(true)
);
let custom_elicitation = ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(false),
}),
url: None,
};
let caps_custom = ClientCapabilities::builder()
.enable_elicitation_with(custom_elicitation.clone())
.build();
assert!(caps_custom.elicitation.is_some());
assert_eq!(
caps_custom.elicitation.as_ref().unwrap(),
&custom_elicitation
);
}
#[tokio::test]
async fn test_create_elicitation_with_timeout_basic() {
use std::time::Duration;
let schema = ElicitationSchema::builder()
.required_property("name", PrimitiveSchema::String(StringSchema::new()))
.required_property("email", PrimitiveSchema::String(StringSchema::new()))
.build()
.unwrap();
let _params = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Enter your details".to_string(),
requested_schema: schema,
};
let timeout_short = Duration::from_millis(100);
let timeout_long = Duration::from_secs(30);
let timeout_none: Option<Duration> = None;
assert!(!timeout_short.is_zero());
assert!(!timeout_long.is_zero());
assert!(timeout_none.is_none());
assert_eq!(timeout_short.as_millis(), 100);
assert_eq!(timeout_long.as_secs(), 30);
}
#[tokio::test]
async fn test_elicit_with_timeout_method_signature() {
use std::time::Duration;
let timeout_values = vec![
None,
Some(Duration::from_millis(500)),
Some(Duration::from_secs(1)),
Some(Duration::from_secs(30)),
Some(Duration::from_secs(60)),
];
for timeout in timeout_values {
match timeout {
None => assert!(timeout.is_none()),
Some(duration) => {
assert!(duration > Duration::from_millis(0));
assert!(duration <= Duration::from_secs(300)); }
}
}
}
#[tokio::test]
async fn test_timeout_value_validation() {
use std::time::Duration;
let valid_timeouts = vec![
Duration::from_millis(1), Duration::from_millis(100), Duration::from_secs(1), Duration::from_secs(30), Duration::from_secs(300), ];
for timeout in valid_timeouts {
assert!(timeout >= Duration::from_millis(1));
assert!(timeout <= Duration::from_secs(300));
}
let zero_timeout = Duration::from_millis(0);
let very_long_timeout = Duration::from_secs(3600);
assert_eq!(zero_timeout, Duration::from_millis(0));
assert!(very_long_timeout > Duration::from_secs(300));
}
#[tokio::test]
async fn test_timeout_error_formatting() {
use std::time::Duration;
let timeout = Duration::from_secs(30);
let timeout_error = ServiceError::Timeout { timeout };
let error_string = format!("{}", timeout_error);
assert!(error_string.contains("timeout"));
assert!(error_string.contains("30"));
}
#[tokio::test]
async fn test_elicitation_timeout_error_conversion() {
use std::time::Duration;
let timeout = Duration::from_millis(500);
let service_timeout_error = ServiceError::Timeout { timeout };
let elicitation_error = ElicitationError::Service(service_timeout_error);
match elicitation_error {
ElicitationError::Service(ServiceError::Timeout { timeout: t }) => {
assert_eq!(t, timeout);
}
_ => panic!("Expected timeout error"),
}
}
#[tokio::test]
async fn test_peer_request_options_timeout() {
use std::time::Duration;
let timeout = Some(Duration::from_secs(15));
let mut options = PeerRequestOptions::default();
options.timeout = timeout;
assert_eq!(options.timeout, timeout);
assert!(options.meta.is_none());
let options_no_timeout = PeerRequestOptions::default();
assert!(options_no_timeout.timeout.is_none());
}
#[tokio::test]
async fn test_realistic_timeout_scenarios() {
use std::time::Duration;
let quick_timeout = Duration::from_secs(5);
assert!(quick_timeout >= Duration::from_secs(1));
assert!(quick_timeout <= Duration::from_secs(10));
let normal_timeout = Duration::from_secs(30);
assert!(normal_timeout >= Duration::from_secs(10));
assert!(normal_timeout <= Duration::from_secs(60));
let long_timeout = Duration::from_secs(120);
assert!(long_timeout >= Duration::from_secs(60));
assert!(long_timeout <= Duration::from_secs(300));
}
#[tokio::test]
async fn test_elicitation_action_error_mapping() {
use rmcp::{model::ElicitationAction, service::ElicitationError};
let test_cases = vec![
(ElicitationAction::Decline, "UserDeclined"),
(ElicitationAction::Cancel, "UserCancelled"),
];
for (action, _expected_error_type) in test_cases {
match action {
ElicitationAction::Accept => {
}
ElicitationAction::Decline => {
let error = ElicitationError::UserDeclined;
assert!(format!("{}", error).contains("explicitly declined"));
}
ElicitationAction::Cancel => {
let error = ElicitationError::UserCancelled;
assert!(format!("{}", error).contains("cancelled/dismissed"));
}
}
}
}
#[tokio::test]
async fn test_elicitation_action_semantics() {
use rmcp::model::ElicitationAction;
let actions = vec![
ElicitationAction::Accept,
ElicitationAction::Decline,
ElicitationAction::Cancel,
];
assert_eq!(actions.len(), 3);
for action in actions {
let serialized = serde_json::to_string(&action).expect("Should serialize");
let deserialized: ElicitationAction =
serde_json::from_str(&serialized).expect("Should deserialize");
match (action, deserialized) {
(ElicitationAction::Accept, ElicitationAction::Accept) => {}
(ElicitationAction::Decline, ElicitationAction::Decline) => {}
(ElicitationAction::Cancel, ElicitationAction::Cancel) => {}
_ => panic!("Action serialization round-trip failed"),
}
}
}
#[tokio::test]
async fn test_elicitation_type_safety() {
use rmcp::service::ElicitationSafe;
use schemars::JsonSchema;
#[derive(serde::Serialize, serde::Deserialize, JsonSchema)]
struct SafeType {
name: String,
value: i32,
}
rmcp::elicit_safe!(SafeType);
fn assert_elicitation_safe<T: ElicitationSafe>() {}
assert_elicitation_safe::<SafeType>();
let _schema = schemars::schema_for!(SafeType);
}
#[tokio::test]
async fn test_elicit_safe_macro() {
use schemars::JsonSchema;
#[derive(serde::Serialize, serde::Deserialize, JsonSchema)]
struct TypeA {
field_a: String,
}
#[derive(serde::Serialize, serde::Deserialize, JsonSchema)]
struct TypeB {
field_b: i32,
}
#[derive(serde::Serialize, serde::Deserialize, JsonSchema)]
struct TypeC {
field_c: bool,
}
rmcp::elicit_safe!(TypeA, TypeB, TypeC);
fn assert_all_safe<T: rmcp::service::ElicitationSafe>() {}
assert_all_safe::<TypeA>();
assert_all_safe::<TypeB>();
assert_all_safe::<TypeC>();
}
#[tokio::test]
async fn test_elicitation_safe_trait() {
use schemars::JsonSchema;
#[derive(serde::Serialize, serde::Deserialize, JsonSchema)]
struct ObjectType {
name: String,
count: usize,
active: bool,
}
rmcp::elicit_safe!(ObjectType);
let _schema = schemars::schema_for!(ObjectType);
}
#[tokio::test]
async fn test_elicitation_examples_compile() {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
#[derive(Serialize, Deserialize, JsonSchema)]
struct UserProfile {
name: String,
email: String,
}
rmcp::elicit_safe!(UserProfile);
fn _example_usage() {
fn _assert_safe<T: rmcp::service::ElicitationSafe>() {}
_assert_safe::<UserProfile>();
}
}
#[tokio::test]
async fn test_build_validation_required_field_not_in_properties() {
let result = ElicitationSchema::builder()
.property("email", PrimitiveSchema::String(StringSchema::email()))
.mark_required("nonexistent_field")
.build();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
"Required field does not exist in properties"
);
}
#[tokio::test]
async fn test_build_validation_required_field_exists() {
let result = ElicitationSchema::builder()
.property("email", PrimitiveSchema::String(StringSchema::email()))
.property("name", PrimitiveSchema::String(StringSchema::new()))
.mark_required("email")
.mark_required("name")
.build();
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 2);
assert_eq!(
schema.required,
Some(vec!["email".to_string(), "name".to_string()])
);
}
#[tokio::test]
#[should_panic(expected = "Invalid elicitation schema")]
async fn test_build_unchecked_panics_on_invalid() {
let _schema = ElicitationSchema::builder()
.property("email", PrimitiveSchema::String(StringSchema::email()))
.mark_required("nonexistent_field")
.build_unchecked();
}
#[tokio::test]
async fn test_convenience_methods_validation() {
let result = ElicitationSchema::builder()
.required_string_property("name", |s| s)
.required_email("email")
.build();
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 2);
assert!(
schema
.required
.as_ref()
.unwrap()
.contains(&"name".to_string())
);
assert!(
schema
.required
.as_ref()
.unwrap()
.contains(&"email".to_string())
);
}
#[tokio::test]
async fn test_typed_property_methods() {
let result = ElicitationSchema::builder()
.string_property("name", |s| s.length(1, 100))
.number_property("price", |n| n.range(0.0, 1000.0))
.integer_property("quantity", |i| i.range(1, 100))
.bool_property("in_stock", |b| b.with_default(true))
.build();
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 4);
if let Some(PrimitiveSchema::String(_)) = schema.properties.get("name") {
} else {
panic!("name should be StringSchema");
}
if let Some(PrimitiveSchema::Number(_)) = schema.properties.get("price") {
} else {
panic!("price should be NumberSchema");
}
if let Some(PrimitiveSchema::Integer(_)) = schema.properties.get("quantity") {
} else {
panic!("quantity should be IntegerSchema");
}
if let Some(PrimitiveSchema::Boolean(_)) = schema.properties.get("in_stock") {
} else {
panic!("in_stock should be BooleanSchema");
}
}
#[tokio::test]
async fn test_required_typed_property_methods() {
let result = ElicitationSchema::builder()
.required_string_property("name", |s| s)
.required_number_property("price", |n| n)
.required_integer_property("age", |i| i)
.required_bool_property("active", |b| b)
.build();
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 4);
assert_eq!(schema.required.as_ref().unwrap().len(), 4);
let required = schema.required.as_ref().unwrap();
assert!(required.contains(&"name".to_string()));
assert!(required.contains(&"price".to_string()));
assert!(required.contains(&"age".to_string()));
assert!(required.contains(&"active".to_string()));
}
#[tokio::test]
async fn test_url_elicitation_request_param_serialization() {
let request_param = CreateElicitationRequestParams::UrlElicitationParams {
meta: None,
message: "Please visit the following URL to complete verification".to_string(),
url: "https://example.com/verify".to_string(),
elicitation_id: "elicit-123".to_string(),
};
let json = serde_json::to_value(&request_param).unwrap();
let expected = json!({
"mode": "url",
"message": "Please visit the following URL to complete verification",
"url": "https://example.com/verify",
"elicitationId": "elicit-123"
});
assert_eq!(json, expected);
let deserialized: CreateElicitationRequestParams = serde_json::from_value(expected).unwrap();
match deserialized {
CreateElicitationRequestParams::UrlElicitationParams {
message,
url,
elicitation_id,
..
} => {
assert_eq!(
message,
"Please visit the following URL to complete verification"
);
assert_eq!(url, "https://example.com/verify");
assert_eq!(elicitation_id, "elicit-123");
}
_ => panic!("Expected UrlElicitationParam variant"),
}
}
#[tokio::test]
async fn test_url_elicitation_json_rpc_protocol() {
let request = JsonRpcRequest {
jsonrpc: JsonRpcVersion2_0,
id: RequestId::Number(1),
request: CreateElicitationRequest::new(
CreateElicitationRequestParams::UrlElicitationParams {
meta: None,
message: "Please authorize this action at the following URL".to_string(),
url: "https://auth.example.com/authorize/abc123".to_string(),
elicitation_id: "auth-request-456".to_string(),
},
),
};
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json["jsonrpc"], "2.0");
assert_eq!(json["id"], 1);
assert_eq!(json["method"], "elicitation/create");
assert_eq!(json["params"]["mode"], "url");
assert_eq!(
json["params"]["message"],
"Please authorize this action at the following URL"
);
assert_eq!(
json["params"]["url"],
"https://auth.example.com/authorize/abc123"
);
assert_eq!(json["params"]["elicitationId"], "auth-request-456");
let deserialized: JsonRpcRequest<CreateElicitationRequest> =
serde_json::from_value(json).unwrap();
assert_eq!(deserialized.id, RequestId::Number(1));
match &deserialized.request.params {
CreateElicitationRequestParams::UrlElicitationParams {
message,
url,
elicitation_id,
..
} => {
assert_eq!(message, "Please authorize this action at the following URL");
assert_eq!(url, "https://auth.example.com/authorize/abc123");
assert_eq!(elicitation_id, "auth-request-456");
}
_ => panic!("Expected UrlElicitationParam variant"),
}
}
#[tokio::test]
async fn test_elicitation_completion_notification() {
let notification_params = ElicitationResponseNotificationParam {
elicitation_id: "elicit-789".to_string(),
};
let json = serde_json::to_value(¬ification_params).unwrap();
let expected = json!({
"elicitationId": "elicit-789"
});
assert_eq!(json, expected);
let deserialized: ElicitationResponseNotificationParam =
serde_json::from_value(expected).unwrap();
assert_eq!(deserialized.elicitation_id, "elicit-789");
let notification = ElicitationCompletionNotification::new(notification_params);
let json = serde_json::to_value(¬ification).unwrap();
assert_eq!(json["method"], "notifications/elicitation/complete");
assert_eq!(json["params"]["elicitationId"], "elicit-789");
}
#[tokio::test]
async fn test_url_elicitation_capability() {
let url_cap = UrlElicitationCapability::default();
let json = serde_json::to_value(&url_cap).unwrap();
assert_eq!(json, json!({}));
let deserialized: UrlElicitationCapability = serde_json::from_value(json!({})).unwrap();
assert_eq!(deserialized, url_cap);
let elicitation_cap = ElicitationCapability {
form: None,
url: Some(UrlElicitationCapability::default()),
};
let json = serde_json::to_value(&elicitation_cap).unwrap();
assert_eq!(
json,
json!({
"url": {}
})
);
let both_cap = ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: Some(UrlElicitationCapability::default()),
};
let json = serde_json::to_value(&both_cap).unwrap();
assert_eq!(
json,
json!({
"form": {
"schemaValidation": true
},
"url": {}
})
);
}
#[tokio::test]
async fn test_elicitation_backward_compatibility_no_mode() {
let json_without_mode = json!({
"message": "Please enter your details",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
});
let deserialized: CreateElicitationRequestParams =
serde_json::from_value(json_without_mode).unwrap();
match deserialized {
CreateElicitationRequestParams::FormElicitationParams {
message,
requested_schema,
..
} => {
assert_eq!(message, "Please enter your details");
assert_eq!(requested_schema.properties.len(), 1);
assert!(requested_schema.properties.contains_key("name"));
}
_ => panic!("Expected FormElicitationParam for backward compatibility"),
}
}
#[tokio::test]
async fn test_elicitation_both_modes() {
let form_schema = ElicitationSchema::builder()
.required_property("email", PrimitiveSchema::String(StringSchema::email()))
.build()
.unwrap();
let form_request = CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Enter email".to_string(),
requested_schema: form_schema,
};
let form_json = serde_json::to_value(&form_request).unwrap();
assert_eq!(form_json["mode"], "form");
assert!(form_json.get("requestedSchema").is_some());
assert!(form_json.get("url").is_none());
let url_request = CreateElicitationRequestParams::UrlElicitationParams {
meta: None,
message: "Visit URL".to_string(),
url: "https://example.com".to_string(),
elicitation_id: "id-123".to_string(),
};
let url_json = serde_json::to_value(&url_request).unwrap();
assert_eq!(url_json["mode"], "url");
assert!(url_json.get("url").is_some());
assert!(url_json.get("elicitationId").is_some());
assert!(url_json.get("requestedSchema").is_none());
}
#[tokio::test]
async fn test_url_elicitation_required_error_code() {
assert_eq!(ErrorCode::URL_ELICITATION_REQUIRED.0, -32042);
let error_data = ErrorData::url_elicitation_required(
"URL elicitation is required for this operation",
Some(json!({
"url": "https://example.com/complete",
"elicitationId": "elicit-999"
})),
);
assert_eq!(error_data.code, ErrorCode::URL_ELICITATION_REQUIRED);
assert_eq!(
error_data.message,
"URL elicitation is required for this operation"
);
assert!(error_data.data.is_some());
let json = serde_json::to_value(&error_data).unwrap();
assert_eq!(json["code"], -32042);
assert_eq!(
json["message"],
"URL elicitation is required for this operation"
);
assert_eq!(json["data"]["url"], "https://example.com/complete");
assert_eq!(json["data"]["elicitationId"], "elicit-999");
}
#[tokio::test]
async fn test_client_capabilities_elicitation_modes() {
let form_only_caps = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
})
.build();
let json = serde_json::to_value(&form_only_caps).unwrap();
assert!(json["elicitation"]["form"].is_object());
assert!(
json["elicitation"]["url"].is_null()
|| !json["elicitation"].as_object().unwrap().contains_key("url")
);
let url_only_caps = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: None,
url: Some(UrlElicitationCapability::default()),
})
.build();
let json = serde_json::to_value(&url_only_caps).unwrap();
assert!(json["elicitation"]["url"].is_object());
assert!(
json["elicitation"]["form"].is_null()
|| !json["elicitation"]
.as_object()
.unwrap()
.contains_key("form")
);
let both_caps = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(false),
}),
url: Some(UrlElicitationCapability::default()),
})
.build();
let json = serde_json::to_value(&both_caps).unwrap();
assert!(json["elicitation"]["form"].is_object());
assert!(json["elicitation"]["url"].is_object());
}
#[tokio::test]
async fn test_elicitation_completion_in_server_notification() {
let notification_param = ElicitationResponseNotificationParam {
elicitation_id: "notify-123".to_string(),
};
let completion_notification =
ElicitationCompletionNotification::new(notification_param.clone());
let server_notification =
ServerNotification::ElicitationCompletionNotification(completion_notification);
let json = serde_json::to_value(&server_notification).unwrap();
assert_eq!(json["method"], "notifications/elicitation/complete");
assert_eq!(json["params"]["elicitationId"], "notify-123");
let deserialized: ServerNotification = serde_json::from_value(json).unwrap();
match deserialized {
ServerNotification::ElicitationCompletionNotification(notif) => {
assert_eq!(notif.params.elicitation_id, "notify-123");
}
_ => panic!("Expected ElicitationCompletionNotification variant"),
}
}
#[tokio::test]
async fn test_url_elicitation_action_workflow() {
let accept_result = CreateElicitationResult {
action: ElicitationAction::Accept,
content: None, meta: None,
};
let json = serde_json::to_value(&accept_result).unwrap();
assert_eq!(json["action"], "accept");
assert!(json.get("content").is_none() || json["content"].is_null());
let decline_result = CreateElicitationResult {
action: ElicitationAction::Decline,
content: None,
meta: None,
};
let json = serde_json::to_value(&decline_result).unwrap();
assert_eq!(json["action"], "decline");
let cancel_result = CreateElicitationResult {
action: ElicitationAction::Cancel,
content: None,
meta: None,
};
let json = serde_json::to_value(&cancel_result).unwrap();
assert_eq!(json["action"], "cancel");
}
#[tokio::test]
async fn test_elicitation_method_constants() {
assert_eq!(ElicitationCreateRequestMethod::VALUE, "elicitation/create");
assert_eq!(
ElicitationResponseNotificationMethod::VALUE,
"notifications/elicitation/response"
);
assert_eq!(
ElicitationCompletionNotificationMethod::VALUE,
"notifications/elicitation/complete"
);
}