fastmcp_rust/testing/
context.rs1use asupersync::{Budget, Cx};
7use fastmcp_core::{McpContext, SessionState};
8
9#[derive(Clone)]
26pub struct TestContext {
27 cx: Cx,
29 budget: Option<Budget>,
31 session_state: Option<SessionState>,
33}
34
35impl std::fmt::Debug for TestContext {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("TestContext")
38 .field("has_budget", &self.budget.is_some())
39 .field("has_session_state", &self.session_state.is_some())
40 .finish()
41 }
42}
43
44impl Default for TestContext {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl TestContext {
51 #[must_use]
59 pub fn new() -> Self {
60 Self {
61 cx: Cx::for_testing(),
62 budget: None,
63 session_state: None,
64 }
65 }
66
67 #[must_use]
79 pub fn with_budget_secs(mut self, secs: u64) -> Self {
80 self.budget = Some(Budget::with_deadline_secs(secs));
81 self
82 }
83
84 #[must_use]
90 pub fn with_budget_ms(mut self, ms: u64) -> Self {
91 let secs = (ms + 999) / 1000;
93 self.budget = Some(Budget::with_deadline_secs(secs));
94 self
95 }
96
97 #[must_use]
110 pub fn with_session_state(mut self, state: SessionState) -> Self {
111 self.session_state = Some(state);
112 self
113 }
114
115 #[must_use]
117 pub fn cx(&self) -> &Cx {
118 &self.cx
119 }
120
121 #[must_use]
123 pub fn cx_clone(&self) -> Cx {
124 self.cx.clone()
125 }
126
127 #[must_use]
129 pub fn budget(&self) -> Option<&Budget> {
130 self.budget.as_ref()
131 }
132
133 #[must_use]
149 pub fn mcp_context(&self, request_id: u64) -> McpContext {
150 if let Some(state) = &self.session_state {
151 McpContext::with_state(self.cx.clone(), request_id, state.clone())
152 } else {
153 McpContext::new(self.cx.clone(), request_id)
154 }
155 }
156
157 #[must_use]
164 pub fn mcp_context_with_state(&self, request_id: u64, state: SessionState) -> McpContext {
165 McpContext::with_state(self.cx.clone(), request_id, state)
166 }
167
168 #[must_use]
170 pub fn is_cancelled(&self) -> bool {
171 self.cx.is_cancel_requested()
172 }
173
174 pub fn checkpoint(&self) -> fastmcp_core::McpResult<()> {
178 if self.cx.is_cancel_requested() {
179 Err(fastmcp_core::McpError::request_cancelled())
180 } else {
181 Ok(())
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_context_creation() {
192 let ctx = TestContext::new();
193 assert!(ctx.budget().is_none());
194 assert!(!ctx.is_cancelled());
195 }
196
197 #[test]
198 fn test_context_with_budget() {
199 let ctx = TestContext::new().with_budget_secs(10);
200 assert!(ctx.budget().is_some());
201 }
202
203 #[test]
204 fn test_context_with_session_state() {
205 let state = SessionState::new();
206 let ctx = TestContext::new().with_session_state(state);
207 assert!(ctx.session_state.is_some());
208 }
209
210 #[test]
211 fn test_mcp_context_creation() {
212 let ctx = TestContext::new();
213 let mcp_ctx = ctx.mcp_context(42);
214 assert_eq!(mcp_ctx.request_id(), 42);
215 }
216
217 #[test]
218 fn test_mcp_context_with_shared_state() {
219 let state = SessionState::new();
220
221 {
223 let ctx = TestContext::new().with_session_state(state.clone());
224 let mcp_ctx = ctx.mcp_context(1);
225 mcp_ctx.set_state("test_key", "test_value".to_string());
226 }
227
228 {
230 let ctx = TestContext::new().with_session_state(state.clone());
231 let mcp_ctx = ctx.mcp_context(2);
232 let value: Option<String> = mcp_ctx.get_state("test_key");
233 assert_eq!(value, Some("test_value".to_string()));
234 }
235 }
236
237 #[test]
238 fn test_checkpoint_not_cancelled() {
239 let ctx = TestContext::new();
240 assert!(ctx.checkpoint().is_ok());
241 }
242
243 #[test]
248 fn default_matches_new() {
249 let def = TestContext::default();
250 let new = TestContext::new();
251 assert!(def.budget().is_none());
252 assert!(new.budget().is_none());
253 assert!(!def.is_cancelled());
254 }
255
256 #[test]
257 fn debug_output() {
258 let ctx = TestContext::new();
259 let debug = format!("{ctx:?}");
260 assert!(debug.contains("TestContext"));
261 assert!(debug.contains("has_budget"));
262 assert!(debug.contains("has_session_state"));
263 }
264
265 #[test]
266 fn clone_produces_independent_context() {
267 let ctx = TestContext::new().with_budget_secs(30);
268 let cloned = ctx.clone();
269 assert!(cloned.budget().is_some());
270 }
271
272 #[test]
273 fn with_budget_ms_sets_budget() {
274 let ctx = TestContext::new().with_budget_ms(5000);
275 assert!(ctx.budget().is_some());
276 }
277
278 #[test]
279 fn cx_and_cx_clone_accessors() {
280 let ctx = TestContext::new();
281 let _cx_ref = ctx.cx();
282 let _cx_owned = ctx.cx_clone();
283 }
284
285 #[test]
286 fn mcp_context_with_state_method() {
287 let state = SessionState::new();
288 let ctx = TestContext::new();
289 let mcp_ctx = ctx.mcp_context_with_state(99, state);
290 assert_eq!(mcp_ctx.request_id(), 99);
291 }
292}