Skip to content

Commit 397e9ac

Browse files
committed
test: add server initialization tests for pre-init requests
1 parent 7ae5ebc commit 397e9ac

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// cargo test --features "client" --package rmcp -- server_init
2+
#![cfg(feature = "client")]
3+
mod common;
4+
5+
use common::handlers::TestServer;
6+
use rmcp::{
7+
ServiceExt,
8+
model::{ClientJsonRpcMessage, ServerJsonRpcMessage, ServerResult},
9+
service::ServerInitializeError,
10+
transport::{IntoTransport, Transport},
11+
};
12+
13+
fn msg(raw: &str) -> ClientJsonRpcMessage {
14+
serde_json::from_str(raw).expect("invalid test message JSON")
15+
}
16+
17+
fn init_request() -> ClientJsonRpcMessage {
18+
msg(r#"{
19+
"jsonrpc": "2.0",
20+
"id": 1,
21+
"method": "initialize",
22+
"params": {
23+
"protocolVersion": "2025-11-25",
24+
"capabilities": {},
25+
"clientInfo": { "name": "test-client", "version": "0.0.1" }
26+
}
27+
}"#)
28+
}
29+
30+
fn initialized_notification() -> ClientJsonRpcMessage {
31+
msg(r#"{ "jsonrpc": "2.0", "method": "notifications/initialized" }"#)
32+
}
33+
34+
fn set_level_request(id: u64) -> ClientJsonRpcMessage {
35+
msg(&format!(
36+
r#"{{ "jsonrpc": "2.0", "id": {id}, "method": "logging/setLevel", "params": {{ "level": "info" }} }}"#
37+
))
38+
}
39+
40+
fn ping_request(id: u64) -> ClientJsonRpcMessage {
41+
msg(&format!(
42+
r#"{{ "jsonrpc": "2.0", "id": {id}, "method": "ping" }}"#
43+
))
44+
}
45+
46+
fn list_tools_request(id: u64) -> ClientJsonRpcMessage {
47+
msg(&format!(
48+
r#"{{ "jsonrpc": "2.0", "id": {id}, "method": "tools/list" }}"#
49+
))
50+
}
51+
52+
async fn do_initialize(client: &mut impl Transport<rmcp::RoleClient>) {
53+
client.send(init_request()).await.unwrap();
54+
let _response = client.receive().await.unwrap();
55+
}
56+
57+
// Server responds with EmptyResult to setLevel received before initialized.
58+
#[tokio::test]
59+
async fn server_init_set_level_response_is_empty_result() {
60+
let (server_transport, client_transport) = tokio::io::duplex(4096);
61+
let _server = tokio::spawn(async move { TestServer::new().serve(server_transport).await });
62+
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
63+
64+
do_initialize(&mut client).await;
65+
client.send(set_level_request(2)).await.unwrap();
66+
67+
let response = client.receive().await.unwrap();
68+
assert!(
69+
matches!(
70+
response,
71+
ServerJsonRpcMessage::Response(ref r)
72+
if matches!(r.result, ServerResult::EmptyResult(_))
73+
),
74+
"expected EmptyResult for setLevel, got: {response:?}"
75+
);
76+
}
77+
78+
// Server initializes successfully when setLevel is sent before the initialized notification.
79+
#[tokio::test]
80+
async fn server_init_succeeds_after_set_level_before_initialized() {
81+
let (server_transport, client_transport) = tokio::io::duplex(4096);
82+
let server_handle =
83+
tokio::spawn(async move { TestServer::new().serve(server_transport).await });
84+
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
85+
86+
do_initialize(&mut client).await;
87+
client.send(set_level_request(2)).await.unwrap();
88+
let _response = client.receive().await.unwrap();
89+
client.send(initialized_notification()).await.unwrap();
90+
91+
let result = server_handle.await.unwrap();
92+
assert!(
93+
result.is_ok(),
94+
"server should initialize successfully after setLevel"
95+
);
96+
result.unwrap().cancel().await.unwrap();
97+
}
98+
99+
// Server responds with EmptyResult to ping received before initialized.
100+
#[tokio::test]
101+
async fn server_init_ping_response_is_empty_result() {
102+
let (server_transport, client_transport) = tokio::io::duplex(4096);
103+
let _server = tokio::spawn(async move { TestServer::new().serve(server_transport).await });
104+
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
105+
106+
do_initialize(&mut client).await;
107+
client.send(ping_request(2)).await.unwrap();
108+
109+
let response = client.receive().await.unwrap();
110+
assert!(
111+
matches!(
112+
response,
113+
ServerJsonRpcMessage::Response(ref r)
114+
if matches!(r.result, ServerResult::EmptyResult(_))
115+
),
116+
"expected EmptyResult for ping, got: {response:?}"
117+
);
118+
}
119+
120+
// Server initializes successfully when ping is sent before the initialized notification.
121+
#[tokio::test]
122+
async fn server_init_succeeds_after_ping_before_initialized() {
123+
let (server_transport, client_transport) = tokio::io::duplex(4096);
124+
let server_handle =
125+
tokio::spawn(async move { TestServer::new().serve(server_transport).await });
126+
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
127+
128+
do_initialize(&mut client).await;
129+
client.send(ping_request(2)).await.unwrap();
130+
let _response = client.receive().await.unwrap();
131+
client.send(initialized_notification()).await.unwrap();
132+
133+
let result = server_handle.await.unwrap();
134+
assert!(
135+
result.is_ok(),
136+
"server should initialize successfully after ping"
137+
);
138+
result.unwrap().cancel().await.unwrap();
139+
}
140+
141+
// Server returns ExpectedInitializedNotification for any other message before initialized.
142+
#[tokio::test]
143+
async fn server_init_rejects_unexpected_message_before_initialized() {
144+
let (server_transport, client_transport) = tokio::io::duplex(4096);
145+
let server_handle =
146+
tokio::spawn(async move { TestServer::new().serve(server_transport).await });
147+
let mut client = IntoTransport::<rmcp::RoleClient, _, _>::into_transport(client_transport);
148+
149+
do_initialize(&mut client).await;
150+
client.send(list_tools_request(2)).await.unwrap();
151+
152+
let result = server_handle.await.unwrap();
153+
assert!(
154+
matches!(
155+
result,
156+
Err(ServerInitializeError::ExpectedInitializedNotification(_))
157+
),
158+
"expected ExpectedInitializedNotification error"
159+
);
160+
}

0 commit comments

Comments
 (0)