use pmcp::types::{ToolAnnotations, ToolInfo};
use serde_json::json;
#[test]
fn test_tool_annotations_builder_pattern() {
let annotations = ToolAnnotations::new()
.with_title("My Tool")
.with_read_only(true)
.with_destructive(false)
.with_idempotent(true)
.with_open_world(false);
assert_eq!(annotations.title, Some("My Tool".to_string()));
assert_eq!(annotations.read_only_hint, Some(true));
assert_eq!(annotations.destructive_hint, Some(false));
assert_eq!(annotations.idempotent_hint, Some(true));
assert_eq!(annotations.open_world_hint, Some(false));
}
#[test]
fn test_output_schema_on_tool_info() {
let output_schema = json!({
"type": "object",
"properties": {
"count": { "type": "integer" },
"items": { "type": "array", "items": { "type": "string" } }
},
"required": ["count", "items"]
});
let annotations = ToolAnnotations::new()
.with_read_only(true)
.with_output_type_name("SearchResult");
let tool = ToolInfo::with_annotations(
"search",
Some("Search items".to_string()),
json!({"type": "object"}),
annotations,
)
.with_output_schema(output_schema.clone());
assert_eq!(tool.output_schema, Some(output_schema));
let ann = tool.annotations.as_ref().unwrap();
assert_eq!(ann.output_type_name, Some("SearchResult".to_string()));
}
#[test]
fn test_tool_info_json_serialization_with_output_schema() {
let output_schema = json!({
"type": "object",
"properties": {
"result": { "type": "string" }
}
});
let annotations = ToolAnnotations::new()
.with_read_only(true)
.with_output_type_name("MyResult");
let tool = ToolInfo::with_annotations(
"echo",
Some("Echo input".to_string()),
json!({"type": "object", "properties": {"input": {"type": "string"}}}),
annotations,
)
.with_output_schema(output_schema);
let json = serde_json::to_value(&tool).unwrap();
assert!(json["outputSchema"].is_object());
assert_eq!(
json["outputSchema"]["properties"]["result"]["type"],
"string"
);
assert_eq!(json["annotations"]["pmcp:outputTypeName"], "MyResult");
assert_eq!(json["annotations"]["readOnlyHint"], true);
assert!(json["annotations"].get("pmcp:outputSchema").is_none());
}
#[test]
fn test_tool_annotations_json_deserialization() {
let json = json!({
"readOnlyHint": true,
"destructiveHint": false,
"pmcp:outputTypeName": "QueryResult"
});
let annotations: ToolAnnotations = serde_json::from_value(json).unwrap();
assert_eq!(annotations.read_only_hint, Some(true));
assert_eq!(annotations.destructive_hint, Some(false));
assert_eq!(
annotations.output_type_name,
Some("QueryResult".to_string())
);
}
#[test]
fn test_tool_info_with_annotations() {
let output_schema = json!({
"type": "object",
"properties": { "count": { "type": "integer" } }
});
let annotations = ToolAnnotations::new()
.with_read_only(true)
.with_output_type_name("CountResult");
let tool = ToolInfo::with_annotations(
"count_items",
Some("Count items in a collection".to_string()),
json!({
"type": "object",
"properties": {
"collection": { "type": "string" }
},
"required": ["collection"]
}),
annotations,
)
.with_output_schema(output_schema.clone());
assert_eq!(tool.name, "count_items");
assert_eq!(tool.output_schema, Some(output_schema));
let ann = tool.annotations.as_ref().unwrap();
assert_eq!(ann.read_only_hint, Some(true));
assert_eq!(ann.output_type_name, Some("CountResult".to_string()));
}
#[test]
fn test_tool_info_serialization_with_annotations() {
let annotations = ToolAnnotations::new()
.with_read_only(true)
.with_output_type_name("StringResult");
let tool = ToolInfo::with_annotations(
"echo",
Some("Echo input".to_string()),
json!({"type": "object", "properties": {"input": {"type": "string"}}}),
annotations,
)
.with_output_schema(json!({"type": "string"}));
let json = serde_json::to_value(&tool).unwrap();
assert_eq!(json["name"], "echo");
assert_eq!(json["description"], "Echo input");
assert!(json["inputSchema"].is_object());
assert_eq!(json["outputSchema"], json!({"type": "string"}));
assert!(json["annotations"].is_object());
assert_eq!(json["annotations"]["readOnlyHint"], true);
assert_eq!(json["annotations"]["pmcp:outputTypeName"], "StringResult");
}
#[test]
fn test_tool_info_without_annotations() {
let tool = ToolInfo::new(
"simple_tool",
Some("A simple tool".to_string()),
json!({"type": "object"}),
);
let json = serde_json::to_value(&tool).unwrap();
assert!(json.get("annotations").is_none());
assert!(json.get("outputSchema").is_none());
}
#[test]
fn test_empty_annotations_not_serialized() {
let annotations = ToolAnnotations::new();
let json = serde_json::to_value(&annotations).unwrap();
assert!(json.as_object().unwrap().is_empty());
}
#[test]
fn test_round_trip_serialization() {
let output_schema = json!({
"type": "object",
"properties": {
"id": { "type": "string" },
"values": { "type": "array", "items": { "type": "number" } }
},
"required": ["id"]
});
let original_annotations = ToolAnnotations::new()
.with_title("Test Tool")
.with_read_only(true)
.with_destructive(false)
.with_idempotent(true)
.with_open_world(true)
.with_output_type_name("ComplexResult");
let original = ToolInfo::with_annotations(
"test",
Some("Test tool".to_string()),
json!({"type": "object"}),
original_annotations,
)
.with_output_schema(output_schema.clone());
let json = serde_json::to_string(&original).unwrap();
let restored: ToolInfo = serde_json::from_str(&json).unwrap();
assert_eq!(original.output_schema, restored.output_schema);
let orig_ann = original.annotations.as_ref().unwrap();
let rest_ann = restored.annotations.as_ref().unwrap();
assert_eq!(orig_ann.title, rest_ann.title);
assert_eq!(orig_ann.read_only_hint, rest_ann.read_only_hint);
assert_eq!(orig_ann.destructive_hint, rest_ann.destructive_hint);
assert_eq!(orig_ann.idempotent_hint, rest_ann.idempotent_hint);
assert_eq!(orig_ann.open_world_hint, rest_ann.open_world_hint);
assert_eq!(orig_ann.output_type_name, rest_ann.output_type_name);
}
#[test]
fn test_partial_annotations_deserialization() {
let json = json!({
"readOnlyHint": true
});
let annotations: ToolAnnotations = serde_json::from_value(json).unwrap();
assert_eq!(annotations.read_only_hint, Some(true));
assert_eq!(annotations.destructive_hint, None);
assert_eq!(annotations.output_type_name, None);
}
#[test]
fn test_output_schema_json_format() {
let input_schema = json!({
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
});
let output_schema = json!({
"type": "object",
"properties": {
"rows": { "type": "array" },
"count": { "type": "integer" }
},
"required": ["rows", "count"]
});
let tool = ToolInfo::with_annotations(
"search",
Some("Search items".to_string()),
input_schema.clone(),
ToolAnnotations::new()
.with_read_only(true)
.with_output_type_name("SearchResult"),
)
.with_output_schema(output_schema.clone());
let json = serde_json::to_value(&tool).unwrap();
assert_eq!(json["inputSchema"], input_schema);
assert_eq!(json["outputSchema"], output_schema);
assert_eq!(json["annotations"]["pmcp:outputTypeName"], "SearchResult");
assert_eq!(json["annotations"]["readOnlyHint"], true);
assert!(json["annotations"].get("outputSchema").is_none());
assert!(json["annotations"].get("pmcp:outputSchema").is_none());
let tool_no_output = ToolInfo::new(
"simple",
Some("Simple tool".to_string()),
json!({"type": "object"}),
);
let json_no_output = serde_json::to_value(&tool_no_output).unwrap();
assert!(json_no_output.get("outputSchema").is_none());
}
#[test]
fn test_output_schema_complex_types() {
let schema = json!({
"type": "object",
"properties": {
"users": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" },
"roles": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["id", "name"]
}
},
"total_count": { "type": "integer" },
"has_more": { "type": "boolean" }
},
"required": ["users", "total_count", "has_more"]
});
let annotations = ToolAnnotations::new().with_output_type_name("UserListResponse");
let tool = ToolInfo::with_annotations(
"list_users",
Some("List users".to_string()),
json!({"type": "object"}),
annotations,
)
.with_output_schema(schema.clone());
let json = serde_json::to_value(&tool).unwrap();
let restored: ToolInfo = serde_json::from_value(json).unwrap();
assert_eq!(restored.output_schema, Some(schema));
let ann = restored.annotations.as_ref().unwrap();
assert_eq!(ann.output_type_name, Some("UserListResponse".to_string()));
}