fastmcp_rust/testing/
assertions.rs1use fastmcp_protocol::{JSONRPC_VERSION, JsonRpcMessage, JsonRpcRequest, JsonRpcResponse};
6
7pub fn assert_json_rpc_valid(message: &JsonRpcMessage) {
25 match message {
26 JsonRpcMessage::Request(req) => {
27 assert_eq!(
28 req.jsonrpc.as_ref(),
29 JSONRPC_VERSION,
30 "JSON-RPC version must be 2.0"
31 );
32 assert!(
33 !req.method.is_empty(),
34 "JSON-RPC request must have a method"
35 );
36 }
37 JsonRpcMessage::Response(resp) => {
38 assert_eq!(
39 resp.jsonrpc.as_ref(),
40 JSONRPC_VERSION,
41 "JSON-RPC version must be 2.0"
42 );
43 let has_result = resp.result.is_some();
45 let has_error = resp.error.is_some();
46 assert!(
47 has_result || has_error,
48 "JSON-RPC response must have either result or error"
49 );
50 assert!(
51 !(has_result && has_error),
52 "JSON-RPC response cannot have both result and error"
53 );
54 }
55 }
56}
57
58pub fn assert_json_rpc_success(response: &JsonRpcResponse) {
71 assert!(
72 response.error.is_none(),
73 "Expected success response but got error: {:?}",
74 response.error
75 );
76 assert!(
77 response.result.is_some(),
78 "Success response must have a result"
79 );
80}
81
82pub fn assert_json_rpc_error(response: &JsonRpcResponse, expected_code: Option<i32>) {
103 assert!(
104 response.error.is_some(),
105 "Expected error response but got success"
106 );
107 assert!(
108 response.result.is_none(),
109 "Error response should not have a result"
110 );
111
112 if let Some(expected) = expected_code {
113 let actual = response.error.as_ref().unwrap().code;
114 assert_eq!(
115 actual, expected,
116 "Expected error code {expected} but got {actual}"
117 );
118 }
119}
120
121pub fn assert_mcp_compliant(method: &str, response: &JsonRpcResponse) {
148 assert_json_rpc_valid(&JsonRpcMessage::Response(response.clone()));
150
151 if response.error.is_some() {
153 return;
154 }
155
156 let result = response.result.as_ref().expect("Response must have result");
157
158 match method {
160 "initialize" => {
161 assert!(
162 result.get("protocolVersion").is_some(),
163 "Initialize response must have protocolVersion"
164 );
165 assert!(
166 result.get("capabilities").is_some(),
167 "Initialize response must have capabilities"
168 );
169 assert!(
170 result.get("serverInfo").is_some(),
171 "Initialize response must have serverInfo"
172 );
173 }
174 "tools/list" => {
175 assert!(
176 result.get("tools").is_some(),
177 "tools/list response must have tools array"
178 );
179 assert!(result["tools"].is_array(), "tools must be an array");
180 }
181 "tools/call" => {
182 assert!(
183 result.get("content").is_some(),
184 "tools/call response must have content"
185 );
186 assert!(result["content"].is_array(), "content must be an array");
187 }
188 "resources/list" => {
189 assert!(
190 result.get("resources").is_some(),
191 "resources/list response must have resources array"
192 );
193 assert!(result["resources"].is_array(), "resources must be an array");
194 }
195 "resources/read" => {
196 assert!(
197 result.get("contents").is_some(),
198 "resources/read response must have contents"
199 );
200 assert!(result["contents"].is_array(), "contents must be an array");
201 }
202 "resources/templates/list" => {
203 assert!(
204 result.get("resourceTemplates").is_some(),
205 "resources/templates/list response must have resourceTemplates"
206 );
207 assert!(
208 result["resourceTemplates"].is_array(),
209 "resourceTemplates must be an array"
210 );
211 }
212 "prompts/list" => {
213 assert!(
214 result.get("prompts").is_some(),
215 "prompts/list response must have prompts array"
216 );
217 assert!(result["prompts"].is_array(), "prompts must be an array");
218 }
219 "prompts/get" => {
220 assert!(
221 result.get("messages").is_some(),
222 "prompts/get response must have messages"
223 );
224 assert!(result["messages"].is_array(), "messages must be an array");
225 }
226 _ => {
227 }
229 }
230}
231
232pub fn assert_tool_valid(tool: &serde_json::Value) {
238 assert!(tool.get("name").is_some(), "Tool must have a name");
239 assert!(tool["name"].is_string(), "Tool name must be a string");
240 if let Some(schema) = tool.get("inputSchema") {
242 assert!(schema.is_object(), "inputSchema must be an object");
243 }
244}
245
246pub fn assert_resource_valid(resource: &serde_json::Value) {
252 assert!(resource.get("uri").is_some(), "Resource must have a uri");
253 assert!(resource["uri"].is_string(), "Resource uri must be a string");
254 assert!(resource.get("name").is_some(), "Resource must have a name");
255 assert!(
256 resource["name"].is_string(),
257 "Resource name must be a string"
258 );
259}
260
261pub fn assert_prompt_valid(prompt: &serde_json::Value) {
267 assert!(prompt.get("name").is_some(), "Prompt must have a name");
268 assert!(prompt["name"].is_string(), "Prompt name must be a string");
269}
270
271pub fn assert_content_valid(content: &serde_json::Value) {
277 assert!(content.get("type").is_some(), "Content must have a type");
278 let content_type = content["type"].as_str().expect("type must be a string");
279 match content_type {
280 "text" => {
281 assert!(
282 content.get("text").is_some(),
283 "Text content must have text field"
284 );
285 }
286 "image" => {
287 assert!(
288 content.get("data").is_some(),
289 "Image content must have data field"
290 );
291 assert!(
292 content.get("mimeType").is_some(),
293 "Image content must have mimeType field"
294 );
295 }
296 "audio" => {
297 assert!(
298 content.get("data").is_some(),
299 "Audio content must have data field"
300 );
301 assert!(
302 content.get("mimeType").is_some(),
303 "Audio content must have mimeType field"
304 );
305 }
306 "resource" => {
307 assert!(
308 content.get("resource").is_some(),
309 "Resource content must have resource field"
310 );
311 }
312 _ => {
313 }
315 }
316}
317
318pub fn assert_is_notification(request: &JsonRpcRequest) {
326 assert!(
327 request.id.is_none(),
328 "Notification must not have an id field"
329 );
330}
331
332pub fn assert_is_request(request: &JsonRpcRequest) {
340 assert!(request.id.is_some(), "Request must have an id field");
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use fastmcp_protocol::RequestId;
347
348 #[test]
349 fn test_valid_request() {
350 let request = JsonRpcRequest::new("test/method", None, 1i64);
351 assert_json_rpc_valid(&JsonRpcMessage::Request(request));
352 }
353
354 #[test]
355 fn test_valid_success_response() {
356 let response = JsonRpcResponse::success(RequestId::Number(1), serde_json::json!({}));
357 assert_json_rpc_valid(&JsonRpcMessage::Response(response.clone()));
358 assert_json_rpc_success(&response);
359 }
360
361 #[test]
362 fn test_valid_error_response() {
363 let error = fastmcp_protocol::JsonRpcError {
364 code: -32601,
365 message: "Method not found".to_string(),
366 data: None,
367 };
368 let response = JsonRpcResponse {
369 jsonrpc: std::borrow::Cow::Borrowed(JSONRPC_VERSION),
370 id: Some(RequestId::Number(1)),
371 result: None,
372 error: Some(error),
373 };
374 assert_json_rpc_valid(&JsonRpcMessage::Response(response.clone()));
375 assert_json_rpc_error(&response, Some(-32601));
376 }
377
378 #[test]
379 fn test_mcp_compliant_tools_list() {
380 let response = JsonRpcResponse::success(
381 RequestId::Number(1),
382 serde_json::json!({
383 "tools": []
384 }),
385 );
386 assert_mcp_compliant("tools/list", &response);
387 }
388
389 #[test]
390 fn test_mcp_compliant_initialize() {
391 let response = JsonRpcResponse::success(
392 RequestId::Number(1),
393 serde_json::json!({
394 "protocolVersion": "2024-11-05",
395 "capabilities": {},
396 "serverInfo": {
397 "name": "test",
398 "version": "1.0"
399 }
400 }),
401 );
402 assert_mcp_compliant("initialize", &response);
403 }
404
405 #[test]
406 fn test_valid_tool() {
407 let tool = serde_json::json!({
408 "name": "my_tool",
409 "description": "A test tool",
410 "inputSchema": {
411 "type": "object"
412 }
413 });
414 assert_tool_valid(&tool);
415 }
416
417 #[test]
418 fn test_valid_resource() {
419 let resource = serde_json::json!({
420 "uri": "file:///test.txt",
421 "name": "Test File"
422 });
423 assert_resource_valid(&resource);
424 }
425
426 #[test]
427 fn test_valid_prompt() {
428 let prompt = serde_json::json!({
429 "name": "greeting",
430 "description": "A greeting prompt"
431 });
432 assert_prompt_valid(&prompt);
433 }
434
435 #[test]
436 fn test_valid_text_content() {
437 let content = serde_json::json!({
438 "type": "text",
439 "text": "Hello, world!"
440 });
441 assert_content_valid(&content);
442 }
443
444 #[test]
445 fn test_is_notification() {
446 let notification = JsonRpcRequest::notification("test", None);
447 assert_is_notification(¬ification);
448 }
449
450 #[test]
451 fn test_is_request() {
452 let request = JsonRpcRequest::new("test", None, 1i64);
453 assert_is_request(&request);
454 }
455}