use mcp_usage_core::peek::{self, ResultType, TaskStatus};
use mcp_usage_core::{Method, name, version};
use proptest::prelude::*;
use serde_json::{Value, json};
fn hostile_text() -> impl Strategy<Value = String> {
prop_oneof![
any::<String>(),
"[-=?0-9a-zA-Z/+:.]{0,64}",
"=\\?base64\\?[A-Za-z0-9+/=]{0,64}\\?=",
"[0-9]{4}-[0-9]{2}-[0-9]{2}",
"\\PC{0,64}",
]
}
proptest! {
#[test]
fn decoding_a_name_never_panics(raw in hostile_text()) {
match name::decode(&raw) {
Ok(decoded) => {
if matches!(decoded, std::borrow::Cow::Borrowed(_)) {
prop_assert!(!name::is_sentinel(&raw));
} else {
prop_assert!(name::is_sentinel(&raw));
}
}
Err(_) => prop_assert!(name::is_sentinel(&raw)),
}
}
#[test]
fn names_survive_the_sentinel_round_trip(value in any::<String>()) {
let encoded = name::encode(&value);
prop_assert!(name::is_sentinel(&encoded));
let decoded = name::decode(&encoded).unwrap();
prop_assert_eq!(decoded.as_ref(), value.as_str());
}
#[test]
fn validating_a_protocol_version_never_panics(raw in hostile_text()) {
let verdict = version::validate_header(Some(&raw));
if verdict.is_ok() {
prop_assert!(version::ProtocolVersion::parse(&raw).is_some());
prop_assert!(raw.as_str() >= version::HEADER_VALIDATION_SINCE);
}
}
#[test]
fn methods_round_trip_through_their_wire_form(raw in hostile_text()) {
let method = Method::parse(&raw);
prop_assert_eq!(method.as_str(), raw.as_str());
if matches!(method, Method::Other(_)) {
prop_assert!(!method.is_discovery());
prop_assert!(!method.is_task_drive());
prop_assert!(!method.supports_mrtr());
prop_assert!(!method.is_cacheable());
}
}
#[test]
fn unknown_task_statuses_are_never_terminal(raw in hostile_text()) {
let status = TaskStatus::parse(&raw);
if matches!(status, TaskStatus::Unknown(_)) {
prop_assert!(!status.is_terminal());
prop_assert!(!status.delivered(), "an unknown status must not bill");
}
prop_assert_eq!(status.to_string(), raw);
}
}
fn hostile_json() -> impl Strategy<Value = Value> {
let leaf = prop_oneof![
Just(Value::Null),
any::<bool>().prop_map(Value::from),
any::<i64>().prop_map(Value::from),
".{0,32}".prop_map(Value::from),
];
leaf.prop_recursive(4, 32, 4, |inner| {
prop_oneof![
prop::collection::vec(inner.clone(), 0..4).prop_map(Value::from),
prop::collection::hash_map(
prop_oneof![
Just("params".to_owned()),
Just("result".to_owned()),
Just("error".to_owned()),
Just("resultType".to_owned()),
Just("status".to_owned()),
Just("taskId".to_owned()),
Just("inputResponses".to_owned()),
Just("requestState".to_owned()),
".{0,8}",
],
inner,
0..6,
)
.prop_map(|map| Value::Object(map.into_iter().collect())),
]
})
}
proptest! {
#[test]
fn peeking_at_arbitrary_json_never_panics(body in hostile_json()) {
let request = peek::request(&body);
let params = body.get("params");
let expected = params.is_some_and(|params| {
params.get("inputResponses").is_some_and(|v| !v.is_null())
|| params.get("requestState").is_some_and(|v| !v.is_null())
});
prop_assert_eq!(request.is_continuation, expected);
let response = peek::response(&body);
if response.is_error {
prop_assert_eq!(response.result_type, ResultType::Absent);
prop_assert!(response.task.is_none());
}
}
#[test]
fn unrecognized_result_types_are_never_complete(discriminant in ".{0,24}") {
let body = json!({ "result": { "resultType": discriminant } });
let peeked = peek::response(&body);
prop_assert!(!peeked.is_error);
match peeked.result_type {
ResultType::Complete => prop_assert_eq!(discriminant.as_str(), "complete"),
ResultType::InputRequired => prop_assert_eq!(discriminant.as_str(), "input_required"),
ResultType::Task => prop_assert_eq!(discriminant.as_str(), "task"),
ResultType::Unknown(other) => prop_assert_eq!(other, discriminant),
ResultType::Absent => prop_assert!(false, "a present discriminant cannot be absent"),
}
}
}