#![cfg(test)]
use proptest::prelude::*;
use serde_json::{Value, json};
use crate::cache::{inspect_request, inspect_whole_body, render_with_id};
use crate::layer::testing::terminal_response;
use mcp_usage_core::{Call, Method};
fn hostile_stream() -> impl Strategy<Value = Vec<u8>> {
prop_oneof![
any::<Vec<u8>>(),
prop::collection::vec(
prop_oneof![
Just(b"data: ".to_vec()),
Just(b"data:".to_vec()),
Just(b"\n".to_vec()),
Just(b"\r\n".to_vec()),
Just(b"\r".to_vec()),
Just(b"\r\r".to_vec()),
Just(b"\n\n".to_vec()),
Just(b": keepalive".to_vec()),
Just(br#"{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete"}}"#.to_vec()),
Just(br#"{"error":{"code":-32020}}"#.to_vec()),
Just(b"{".to_vec()),
Just(b"\0".to_vec()),
".{0,16}".prop_map(String::into_bytes),
],
0..24,
)
.prop_map(|parts| parts.concat()),
]
}
fn request_body() -> impl Strategy<Value = Vec<u8>> {
let id = prop_oneof![
Just(None),
Just(Some(json!(null))),
Just(Some(json!(1))),
Just(Some(json!("abc"))),
Just(Some(json!(1.5))),
Just(Some(json!([1]))),
Just(Some(json!({"a": 1}))),
];
let params = prop_oneof![
Just(None),
Just(Some(json!(null))),
Just(Some(json!([1, 2]))),
Just(Some(json!("scalar"))),
Just(Some(json!({}))),
Just(Some(json!({"taskId": "task-1"}))),
Just(Some(json!({"taskId": 7}))),
Just(Some(json!({"taskId": null}))),
Just(Some(json!({"inputResponses": null}))),
Just(Some(json!({"inputResponses": []}))),
Just(Some(json!({"requestState": {"cursor": "x"}}))),
Just(Some(
json!({"taskId": "t", "requestState": null, "extra": {"deep": [1]}})
)),
];
(id, params, any::<bool>()).prop_map(|(id, params, with_method)| {
let mut body = serde_json::Map::new();
body.insert("jsonrpc".to_owned(), json!("2.0"));
if with_method {
body.insert("method".to_owned(), json!("tools/call"));
}
if let Some(id) = id {
body.insert("id".to_owned(), id);
}
if let Some(params) = params {
body.insert("params".to_owned(), params);
}
Value::Object(body).to_string().into_bytes()
})
}
fn buffered_terminal_response(bytes: &[u8]) -> Option<Value> {
let text = std::str::from_utf8(bytes)
.ok()?
.replace("\r\n", "\n")
.replace('\r', "\n");
let mut terminal = None;
for event in text.split("\n\n") {
let data = event
.lines()
.filter_map(|line| line.strip_prefix("data:").map(str::trim_start))
.collect::<Vec<_>>()
.join("\n");
if data.is_empty() {
continue;
}
if let Ok(value) = serde_json::from_str::<Value>(&data)
&& (value.get("result").is_some() || value.get("error").is_some())
{
terminal = Some(value);
}
}
terminal
}
fn sse_stream() -> impl Strategy<Value = Vec<u8>> {
let terminator = prop_oneof![Just("\n"), Just("\r\n"), Just("\r")];
let line = prop_oneof![
Just(r#"data: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete"}}"#),
Just(r#"data: {"jsonrpc":"2.0","id":1,"#),
Just(r#"data: "result":{"resultType":"complete"}}"#),
Just(r#"data: {"error":{"code":-32020}}"#),
Just("data: 0"),
Just("data:"),
Just("event: message"),
Just("id: 7"),
Just(": keepalive"),
Just(""),
];
(terminator, prop::collection::vec(line, 0..12))
.prop_map(|(terminator, lines)| lines.join(terminator).into_bytes())
}
fn cached_body() -> impl Strategy<Value = Value> {
prop_oneof![
Just(json!({"jsonrpc": "2.0", "id": 1, "result": {"resultType": "complete"}})),
Just(json!({"jsonrpc": "2.0", "result": {"tools": [{"name": "a"}]}})),
Just(json!({"a\"b": "c\\d\ne", "result": {"id": "inner"}, "id": null})),
Just(json!({})),
Just(json!({"id": {"was": "an object"}})),
Just(json!([1, 2, 3])),
Just(json!("scalar")),
Just(json!(null)),
]
}
fn content_type() -> impl Strategy<Value = String> {
prop_oneof![
Just("application/json".to_owned()),
Just("text/event-stream".to_owned()),
Just("text/event-stream; charset=utf-8".to_owned()),
Just("Application/JSON".to_owned()),
Just("application/jsonp".to_owned()),
Just(String::new()),
".{0,24}",
]
}
proptest! {
#[test]
fn reading_a_terminal_response_never_panics(
media in content_type(),
bytes in hostile_stream(),
) {
let terminal = terminal_response(&media, &bytes);
if let Some(value) = terminal {
prop_assert!(
value.get("result").is_some() || value.get("error").is_some(),
"a terminal response must carry a result or an error: {value}"
);
}
}
#[test]
fn the_streaming_sse_reader_agrees_with_a_buffered_one(
bytes in prop_oneof![hostile_stream(), sse_stream()],
) {
prop_assert_eq!(
terminal_response("text/event-stream", &bytes),
buffered_terminal_response(&bytes)
);
}
#[test]
fn unrecognized_media_types_are_never_terminal(bytes in hostile_stream()) {
for media in ["application/jsonp", "text/plain", "", "application/octet-stream"] {
prop_assert!(terminal_response(media, &bytes).is_none());
}
}
#[test]
fn inspecting_a_request_never_panics(
bytes in any::<Vec<u8>>(),
named in any::<bool>(),
) {
let call = Call::new(
Method::ToolsList,
named.then(|| "tool".to_owned()),
);
let metadata = inspect_request(&call, &bytes);
if metadata.is_continuation {
prop_assert!(metadata.cache_key.is_none());
}
}
#[test]
fn the_narrow_request_parse_agrees_with_the_whole_body_parse(
bytes in prop_oneof![request_body().boxed(), any::<Vec<u8>>().boxed()],
method in prop_oneof![
Just(Method::ToolsCall),
Just(Method::TasksGet),
Just(Method::TasksUpdate),
Just(Method::TasksCancel),
Just(Method::PromptsGet),
Just(Method::SubscriptionsListen),
Just(Method::Other("custom/thing".to_owned())),
],
named in any::<bool>(),
) {
prop_assume!(!method.is_cacheable());
let call = Call::new(method, named.then(|| "tool".to_owned()));
prop_assert_eq!(
inspect_request(&call, &bytes),
inspect_whole_body(&call, &bytes)
);
}
#[test]
fn rendering_a_cached_body_agrees_with_cloning_and_overwriting(
body in cached_body(),
id in prop_oneof![
Just(None),
Just(Some(json!(null))),
Just(Some(json!(1))),
Just(Some(json!("caller"))),
Just(Some(json!({"nested": [1, 2]}))),
],
) {
let expected = {
let mut owned = body.clone();
if let (Some(id), Some(object)) = (id.as_ref(), owned.as_object_mut()) {
object.insert("id".to_owned(), id.clone());
}
owned
};
let rendered: Value = serde_json::from_str(&render_with_id(&body, id.as_ref()))
.expect("the rendered body must be valid JSON");
prop_assert_eq!(rendered, expected);
}
#[test]
fn cache_keys_require_header_and_body_agreement(
header_name in "[a-z]{1,8}",
body_name in "[a-z]{1,8}",
) {
let call = Call::new(Method::ResourcesRead, Some(header_name.clone()));
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "resources/read",
"params": { "uri": body_name },
});
let metadata = inspect_request(&call, body.to_string().as_bytes());
prop_assert_eq!(metadata.cache_key.is_some(), header_name == body_name);
}
}