fastmcp_rust/testing/server.rs
1//! Test server builder for creating servers with real handlers.
2//!
3//! Provides a builder pattern for constructing test servers that use
4//! actual handler implementations via MemoryTransport.
5
6use fastmcp_server::{Router, Server, ServerBuilder};
7use fastmcp_transport::memory::{MemoryTransport, create_memory_transport_pair};
8
9/// Builder for creating test servers with real handlers.
10///
11/// Creates servers that communicate via `MemoryTransport` for
12/// in-process testing without subprocess spawning.
13///
14/// # Example
15///
16/// ```ignore
17/// use fastmcp_rust::testing::prelude::*;
18///
19/// // Create a simple test server
20/// let (router, client_transport, server_transport) = TestServer::builder()
21/// .with_name("test-server")
22/// .build();
23///
24/// // Use client_transport to communicate with server
25/// ```
26pub struct TestServerBuilder {
27 /// Server name.
28 name: String,
29 /// Server version.
30 version: String,
31 /// Request timeout in seconds.
32 request_timeout: Option<u64>,
33}
34
35impl Default for TestServerBuilder {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl TestServerBuilder {
42 /// Creates a new test server builder with default settings.
43 #[must_use]
44 pub fn new() -> Self {
45 Self {
46 name: "test-server".to_string(),
47 version: "1.0.0".to_string(),
48 request_timeout: None,
49 }
50 }
51
52 /// Sets the server name.
53 #[must_use]
54 pub fn with_name(mut self, name: impl Into<String>) -> Self {
55 self.name = name.into();
56 self
57 }
58
59 /// Sets the server version.
60 #[must_use]
61 pub fn with_version(mut self, version: impl Into<String>) -> Self {
62 self.version = version.into();
63 self
64 }
65
66 /// Sets the request timeout in seconds.
67 #[must_use]
68 pub fn with_request_timeout(mut self, secs: u64) -> Self {
69 self.request_timeout = Some(secs);
70 self
71 }
72
73 /// Builds the test server and returns it with a client transport.
74 ///
75 /// Returns a tuple of:
76 /// - `Router`: The configured router (use with server run loop)
77 /// - `MemoryTransport`: Client-side transport for sending requests
78 /// - `MemoryTransport`: Server-side transport for the server run loop
79 ///
80 /// # Example
81 ///
82 /// ```ignore
83 /// let (router, client_transport, server_transport) = TestServer::builder()
84 /// .build();
85 ///
86 /// // Run server in a thread
87 /// std::thread::spawn(move || {
88 /// // Use router and server_transport
89 /// });
90 ///
91 /// // Use client_transport for testing
92 /// ```
93 #[must_use]
94 pub fn build(self) -> (Router, MemoryTransport, MemoryTransport) {
95 // Create connected transport pair
96 let (client_transport, server_transport) = create_memory_transport_pair();
97
98 // Build empty router - handlers should be added via Router methods
99 let router = Router::new();
100
101 (router, client_transport, server_transport)
102 }
103
104 /// Builds a full `ServerBuilder` for more control.
105 ///
106 /// Use this when you need access to the full server builder API.
107 ///
108 /// # Example
109 ///
110 /// ```ignore
111 /// let (builder, client_transport, server_transport) = TestServer::builder()
112 /// .build_server_builder();
113 ///
114 /// // Customize further with the real server builder
115 /// let server = builder
116 /// .instructions("Test server")
117 /// .build();
118 /// ```
119 #[must_use]
120 pub fn build_server_builder(self) -> (ServerBuilder, MemoryTransport, MemoryTransport) {
121 let (client_transport, server_transport) = create_memory_transport_pair();
122
123 let mut builder = Server::new(&self.name, &self.version);
124
125 if let Some(timeout) = self.request_timeout {
126 builder = builder.request_timeout(timeout);
127 }
128
129 (builder, client_transport, server_transport)
130 }
131}
132
133/// Convenience wrapper for `TestServerBuilder`.
134pub struct TestServer;
135
136impl TestServer {
137 /// Creates a new test server builder.
138 ///
139 /// # Example
140 ///
141 /// ```ignore
142 /// let (router, client, server) = TestServer::builder()
143 /// .with_name("my-test-server")
144 /// .build();
145 /// ```
146 #[must_use]
147 pub fn builder() -> TestServerBuilder {
148 TestServerBuilder::new()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_builder_defaults() {
158 let builder = TestServerBuilder::new();
159 assert_eq!(builder.name, "test-server");
160 assert_eq!(builder.version, "1.0.0");
161 }
162
163 #[test]
164 fn test_builder_customization() {
165 let builder = TestServerBuilder::new()
166 .with_name("custom-server")
167 .with_version("2.0.0")
168 .with_request_timeout(30);
169
170 assert_eq!(builder.name, "custom-server");
171 assert_eq!(builder.version, "2.0.0");
172 assert_eq!(builder.request_timeout, Some(30));
173 }
174
175 #[test]
176 fn test_build_creates_transport_pair() {
177 let (router, client_transport, server_transport) = TestServer::builder().build();
178
179 // Verify transports are not closed
180 assert!(!client_transport.is_closed());
181 assert!(!server_transport.is_closed());
182
183 // Router should be empty (no handlers added)
184 assert!(router.tools().is_empty());
185 }
186}