Skip to content

Commit 5d9d5f7

Browse files
committed
test: add comprehensive unit tests for model types
Add 112+ non-async unit tests across model modules to increase coverage: - content.rs: 34 tests for RawContent variants, type discrimination, meta preservation - prompt.rs: 24 tests for Prompt, PromptArgument, role handling, meta behavior - resource.rs: 21 tests for Resource, ResourceTemplate, URI schemes, equality - tool.rs: 33 tests for Tool, ToolAnnotations, schema handling, default behaviors
1 parent 11093bc commit 5d9d5f7

File tree

10 files changed

+2136
-18
lines changed

10 files changed

+2136
-18
lines changed

crates/rmcp/src/error.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,75 @@ impl RmcpError {
5454
}
5555
}
5656
}
57+
58+
#[cfg(test)]
59+
mod tests {
60+
use std::io;
61+
62+
use super::*;
63+
use crate::model::{ErrorCode, ErrorData};
64+
65+
#[test]
66+
fn test_error_data_display_without_data() {
67+
let error = ErrorData {
68+
code: ErrorCode(-32600),
69+
message: "Invalid Request".into(),
70+
data: None,
71+
};
72+
assert_eq!(format!("{}", error), "-32600: Invalid Request");
73+
}
74+
75+
#[test]
76+
fn test_error_data_display_with_data() {
77+
let error = ErrorData {
78+
code: ErrorCode(-32600),
79+
message: "Invalid Request".into(),
80+
data: Some(serde_json::json!({"detail": "missing field"})),
81+
};
82+
assert_eq!(
83+
format!("{}", error),
84+
"-32600: Invalid Request({\"detail\":\"missing field\"})"
85+
);
86+
}
87+
88+
#[test]
89+
fn test_rmcp_error_transport_creation() {
90+
struct DummyTransport;
91+
let io_error = io::Error::other("connection failed");
92+
let error = RmcpError::transport_creation::<DummyTransport>(io_error);
93+
94+
match error {
95+
RmcpError::TransportCreation {
96+
into_transport_type_name,
97+
into_transport_type_id,
98+
..
99+
} => {
100+
assert!(into_transport_type_name.contains("DummyTransport"));
101+
assert_eq!(
102+
into_transport_type_id,
103+
std::any::TypeId::of::<DummyTransport>()
104+
);
105+
}
106+
_ => panic!("Expected TransportCreation variant"),
107+
}
108+
}
109+
110+
#[test]
111+
fn test_rmcp_error_display() {
112+
struct DummyTransport;
113+
let io_error = io::Error::other("connection failed");
114+
let error = RmcpError::transport_creation::<DummyTransport>(io_error);
115+
let display = format!("{}", error);
116+
assert!(display.contains("Transport creation error"));
117+
}
118+
119+
#[test]
120+
fn test_error_data_is_std_error() {
121+
let error = ErrorData {
122+
code: ErrorCode(-32600),
123+
message: "Invalid Request".into(),
124+
data: None,
125+
};
126+
let _: &dyn std::error::Error = &error;
127+
}
128+
}

crates/rmcp/src/model/annotated.rs

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,244 @@ pub trait AnnotateAble: sealed::Sealed {
222222
self.with_timestamp(Utc::now())
223223
}
224224
}
225+
226+
#[cfg(test)]
227+
mod tests {
228+
use super::*;
229+
230+
#[test]
231+
fn test_annotations_default() {
232+
let annotations = Annotations::default();
233+
assert_eq!(annotations.audience, None);
234+
assert_eq!(annotations.priority, None);
235+
assert_eq!(annotations.last_modified, None);
236+
}
237+
238+
#[test]
239+
fn test_annotations_for_resource() {
240+
let timestamp = Utc::now();
241+
let annotations = Annotations::for_resource(0.5, timestamp);
242+
assert_eq!(annotations.priority, Some(0.5));
243+
assert_eq!(annotations.last_modified, Some(timestamp));
244+
assert_eq!(annotations.audience, None);
245+
}
246+
247+
#[test]
248+
#[should_panic(expected = "Priority")]
249+
fn test_annotations_for_resource_invalid_priority_high() {
250+
let timestamp = Utc::now();
251+
Annotations::for_resource(1.5, timestamp);
252+
}
253+
254+
#[test]
255+
#[should_panic(expected = "Priority")]
256+
fn test_annotations_for_resource_invalid_priority_low() {
257+
let timestamp = Utc::now();
258+
Annotations::for_resource(-0.1, timestamp);
259+
}
260+
261+
#[test]
262+
fn test_annotated_new() {
263+
let content = RawTextContent {
264+
text: "test".to_string(),
265+
meta: None,
266+
};
267+
let annotated = Annotated::new(content.clone(), None);
268+
assert_eq!(annotated.raw, content);
269+
assert_eq!(annotated.annotations, None);
270+
}
271+
272+
#[test]
273+
fn test_annotated_deref() {
274+
let content = RawTextContent {
275+
text: "test".to_string(),
276+
meta: None,
277+
};
278+
let annotated = Annotated::new(content.clone(), None);
279+
assert_eq!(annotated.text, "test");
280+
}
281+
282+
#[test]
283+
fn test_annotated_deref_mut() {
284+
let content = RawTextContent {
285+
text: "test".to_string(),
286+
meta: None,
287+
};
288+
let mut annotated = Annotated::new(content, None);
289+
annotated.text = "modified".to_string();
290+
assert_eq!(annotated.text, "modified");
291+
}
292+
293+
#[test]
294+
fn test_annotated_remove_annotation() {
295+
let content = RawTextContent {
296+
text: "test".to_string(),
297+
meta: None,
298+
};
299+
let mut annotated = Annotated::new(content, Some(Annotations::default()));
300+
assert!(annotated.annotations.is_some());
301+
let removed = annotated.remove_annotation();
302+
assert!(removed.is_some());
303+
assert!(annotated.annotations.is_none());
304+
}
305+
306+
#[test]
307+
fn test_annotated_getters() {
308+
let timestamp = Utc::now();
309+
let annotations = Annotations {
310+
audience: Some(vec![Role::User]),
311+
priority: Some(0.7),
312+
last_modified: Some(timestamp),
313+
};
314+
let content = RawTextContent {
315+
text: "test".to_string(),
316+
meta: None,
317+
};
318+
let annotated = Annotated::new(content, Some(annotations));
319+
320+
assert_eq!(annotated.audience(), Some(&vec![Role::User]));
321+
assert_eq!(annotated.priority(), Some(0.7));
322+
assert_eq!(annotated.timestamp(), Some(timestamp));
323+
}
324+
325+
#[test]
326+
fn test_annotated_with_audience() {
327+
let content = RawTextContent {
328+
text: "test".to_string(),
329+
meta: None,
330+
};
331+
let annotated = Annotated::new(content, None);
332+
let with_audience = annotated.with_audience(vec![Role::User, Role::Assistant]);
333+
334+
assert_eq!(
335+
with_audience.audience(),
336+
Some(&vec![Role::User, Role::Assistant])
337+
);
338+
}
339+
340+
#[test]
341+
fn test_annotated_with_priority() {
342+
let content = RawTextContent {
343+
text: "test".to_string(),
344+
meta: None,
345+
};
346+
let annotated = Annotated::new(content, None);
347+
let with_priority = annotated.with_priority(0.9);
348+
349+
assert_eq!(with_priority.priority(), Some(0.9));
350+
}
351+
352+
#[test]
353+
fn test_annotated_with_timestamp() {
354+
let timestamp = Utc::now();
355+
let content = RawTextContent {
356+
text: "test".to_string(),
357+
meta: None,
358+
};
359+
let annotated = Annotated::new(content, None);
360+
let with_timestamp = annotated.with_timestamp(timestamp);
361+
362+
assert_eq!(with_timestamp.timestamp(), Some(timestamp));
363+
}
364+
365+
#[test]
366+
fn test_annotated_with_timestamp_now() {
367+
let content = RawTextContent {
368+
text: "test".to_string(),
369+
meta: None,
370+
};
371+
let annotated = Annotated::new(content, None);
372+
let with_timestamp = annotated.with_timestamp_now();
373+
374+
assert!(with_timestamp.timestamp().is_some());
375+
}
376+
377+
#[test]
378+
fn test_annotate_able_optional_annotate() {
379+
let content = RawTextContent {
380+
text: "test".to_string(),
381+
meta: None,
382+
};
383+
let annotated = content.optional_annotate(None);
384+
assert_eq!(annotated.annotations, None);
385+
}
386+
387+
#[test]
388+
fn test_annotate_able_annotate() {
389+
let content = RawTextContent {
390+
text: "test".to_string(),
391+
meta: None,
392+
};
393+
let annotations = Annotations::default();
394+
let annotated = content.annotate(annotations);
395+
assert!(annotated.annotations.is_some());
396+
}
397+
398+
#[test]
399+
fn test_annotate_able_no_annotation() {
400+
let content = RawTextContent {
401+
text: "test".to_string(),
402+
meta: None,
403+
};
404+
let annotated = content.no_annotation();
405+
assert_eq!(annotated.annotations, None);
406+
}
407+
408+
#[test]
409+
fn test_annotate_able_with_audience() {
410+
let content = RawTextContent {
411+
text: "test".to_string(),
412+
meta: None,
413+
};
414+
let annotated = content.with_audience(vec![Role::User]);
415+
assert_eq!(annotated.audience(), Some(&vec![Role::User]));
416+
}
417+
418+
#[test]
419+
fn test_annotate_able_with_priority() {
420+
let content = RawTextContent {
421+
text: "test".to_string(),
422+
meta: None,
423+
};
424+
let annotated = content.with_priority(0.5);
425+
assert_eq!(annotated.priority(), Some(0.5));
426+
}
427+
428+
#[test]
429+
fn test_annotate_able_with_timestamp() {
430+
let timestamp = Utc::now();
431+
let content = RawTextContent {
432+
text: "test".to_string(),
433+
meta: None,
434+
};
435+
let annotated = content.with_timestamp(timestamp);
436+
assert_eq!(annotated.timestamp(), Some(timestamp));
437+
}
438+
439+
#[test]
440+
fn test_annotate_able_with_timestamp_now() {
441+
let content = RawTextContent {
442+
text: "test".to_string(),
443+
meta: None,
444+
};
445+
let annotated = content.with_timestamp_now();
446+
assert!(annotated.timestamp().is_some());
447+
}
448+
449+
#[test]
450+
fn test_chaining_annotations() {
451+
let content = RawTextContent {
452+
text: "test".to_string(),
453+
meta: None,
454+
};
455+
let timestamp = Utc::now();
456+
let annotated = Annotated::new(content, None)
457+
.with_audience(vec![Role::User])
458+
.with_priority(0.8)
459+
.with_timestamp(timestamp);
460+
461+
assert_eq!(annotated.audience(), Some(&vec![Role::User]));
462+
assert_eq!(annotated.priority(), Some(0.8));
463+
assert_eq!(annotated.timestamp(), Some(timestamp));
464+
}
465+
}

0 commit comments

Comments
 (0)