use crate::checks::draft::testkit::{client, findings_for, server, trace};
const REFERENCES: &str = "transport.cancel-notification-references-request";
const AFTER: &str = "transport.no-messages-after-cancel-notification";
fn cancel(seq: u64, params: &str) -> String {
client(
seq,
&format!(r#"{{"jsonrpc":"2.0","method":"notifications/cancelled"{params}}}"#),
)
}
fn request(seq: u64, id: u64, token: Option<&str>) -> String {
let meta = token.map_or_else(String::new, |token| {
format!(r#","params":{{"_meta":{{"progressToken":"{token}"}}}}"#)
});
client(
seq,
&format!(r#"{{"jsonrpc":"2.0","id":{id},"method":"tools/call"{meta}}}"#),
)
}
fn progress(seq: u64, token: &str) -> String {
server(
seq,
&format!(
r#"{{"jsonrpc":"2.0","method":"notifications/progress","params":{{"progressToken":"{token}","progress":1}}}}"#
),
)
}
#[test]
fn a_cancellation_must_name_a_request() {
for params in [
"",
r#","params":{}"#,
r#","params":{"reason":"user"}"#,
r#","params":{"requestId":null}"#,
] {
let findings = findings_for(REFERENCES, &cancel(0, params));
assert_eq!(findings.len(), 1, "params {params:?}: {findings:?}");
assert!(findings[0].contains("requestId"), "{findings:?}");
}
}
#[test]
fn a_cancellation_naming_a_request_conforms() {
for params in [
r#","params":{"requestId":1}"#,
r#","params":{"requestId":"abc"}"#,
r#","params":{"requestId":1,"reason":"user"}"#,
] {
assert!(
findings_for(REFERENCES, &cancel(0, params)).is_empty(),
"params {params:?}"
);
}
}
#[test]
fn only_the_clients_cancellations_are_judged() {
let from_server = server(0, r#"{"jsonrpc":"2.0","method":"notifications/cancelled"}"#);
assert!(findings_for(REFERENCES, &from_server).is_empty());
let as_request = client(
0,
r#"{"jsonrpc":"2.0","id":1,"method":"notifications/cancelled"}"#,
);
assert!(findings_for(REFERENCES, &as_request).is_empty());
}
#[test]
fn other_notifications_are_not_this_clauses_business() {
let unrelated = client(0, r#"{"jsonrpc":"2.0","method":"notifications/progress"}"#);
assert!(findings_for(REFERENCES, &unrelated).is_empty());
}
#[test]
fn answering_a_cancelled_request_is_the_violation() {
let session = trace(&[
request(0, 1, None),
cancel(1, r#","params":{"requestId":1}"#),
server(
2,
r#"{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete"}}"#,
),
]);
let findings = findings_for(AFTER, &session);
assert_eq!(findings.len(), 1, "{findings:?}");
assert!(findings[0].contains("a response"), "{findings:?}");
assert!(findings[0].contains("seq 1"), "{findings:?}");
let errored = session.replace(
r#"{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete"}}"#,
r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"x"}}"#,
);
assert_eq!(findings_for(AFTER, &errored).len(), 1);
}
#[test]
fn answering_before_the_cancellation_conforms() {
let session = trace(&[
request(0, 1, None),
server(
1,
r#"{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete"}}"#,
),
cancel(2, r#","params":{"requestId":1}"#),
]);
assert!(findings_for(AFTER, &session).is_empty());
}
#[test]
fn a_different_request_may_still_be_answered() {
let session = trace(&[
request(0, 1, None),
request(1, 2, None),
cancel(2, r#","params":{"requestId":1}"#),
server(
3,
r#"{"jsonrpc":"2.0","id":2,"result":{"resultType":"complete"}}"#,
),
]);
assert!(findings_for(AFTER, &session).is_empty());
}
#[test]
fn progress_for_a_cancelled_request_is_a_further_message() {
let session = trace(&[
request(0, 1, Some("tok-1")),
cancel(1, r#","params":{"requestId":1}"#),
progress(2, "tok-1"),
]);
let findings = findings_for(AFTER, &session);
assert_eq!(findings.len(), 1, "{findings:?}");
assert!(findings[0].contains("progress"), "{findings:?}");
let other = trace(&[
request(0, 1, Some("tok-1")),
request(1, 2, Some("tok-2")),
cancel(2, r#","params":{"requestId":1}"#),
progress(3, "tok-2"),
]);
assert!(findings_for(AFTER, &other).is_empty());
let early = trace(&[
request(0, 1, Some("tok-1")),
progress(1, "tok-1"),
cancel(2, r#","params":{"requestId":1}"#),
]);
assert!(findings_for(AFTER, &early).is_empty());
}
#[test]
fn a_cancellation_for_a_request_the_recording_never_saw_still_binds() {
let session = trace(&[
cancel(0, r#","params":{"requestId":9}"#),
server(
1,
r#"{"jsonrpc":"2.0","id":9,"result":{"resultType":"complete"}}"#,
),
]);
assert_eq!(findings_for(AFTER, &session).len(), 1);
}
#[test]
fn a_session_without_cancellation_is_not_judged() {
let session = trace(&[
request(0, 1, Some("tok-1")),
progress(1, "tok-1"),
server(
2,
r#"{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete"}}"#,
),
]);
assert!(findings_for(AFTER, &session).is_empty());
assert!(findings_for(REFERENCES, &session).is_empty());
}
#[test]
fn a_cancellation_naming_no_request_cancels_nothing_here() {
let session = trace(&[
request(0, 1, None),
cancel(1, r#","params":{}"#),
server(
2,
r#"{"jsonrpc":"2.0","id":1,"result":{"resultType":"complete"}}"#,
),
]);
assert!(findings_for(AFTER, &session).is_empty());
}