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 background thread (omitted here). Prefer using the
87 /// // higher-level E2E harness helpers in this crate which join threads on drop.
88 ///
89 /// // Use client_transport for testing
90 /// ```
91 #[must_use]
92 pub fn build(self) -> (Router, MemoryTransport, MemoryTransport) {
93 // Create connected transport pair
94 let (client_transport, server_transport) = create_memory_transport_pair();
95
96 // Build empty router - handlers should be added via Router methods
97 let router = Router::new();
98
99 (router, client_transport, server_transport)
100 }
101
102 /// Builds a full `ServerBuilder` for more control.
103 ///
104 /// Use this when you need access to the full server builder API.
105 ///
106 /// # Example
107 ///
108 /// ```ignore
109 /// let (builder, client_transport, server_transport) = TestServer::builder()
110 /// .build_server_builder();
111 ///
112 /// // Customize further with the real server builder
113 /// let server = builder
114 /// .instructions("Test server")
115 /// .build();
116 /// ```
117 #[must_use]
118 pub fn build_server_builder(self) -> (ServerBuilder, MemoryTransport, MemoryTransport) {
119 let (client_transport, server_transport) = create_memory_transport_pair();
120
121 let mut builder = Server::new(&self.name, &self.version);
122
123 if let Some(timeout) = self.request_timeout {
124 builder = builder.request_timeout(timeout);
125 }
126
127 (builder, client_transport, server_transport)
128 }
129}
130
131/// Convenience wrapper for `TestServerBuilder`.
132pub struct TestServer;
133
134impl TestServer {
135 /// Creates a new test server builder.
136 ///
137 /// # Example
138 ///
139 /// ```ignore
140 /// let (router, client, server) = TestServer::builder()
141 /// .with_name("my-test-server")
142 /// .build();
143 /// ```
144 #[must_use]
145 pub fn builder() -> TestServerBuilder {
146 TestServerBuilder::new()
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_builder_defaults() {
156 let builder = TestServerBuilder::new();
157 assert_eq!(builder.name, "test-server");
158 assert_eq!(builder.version, "1.0.0");
159 }
160
161 #[test]
162 fn test_builder_customization() {
163 let builder = TestServerBuilder::new()
164 .with_name("custom-server")
165 .with_version("2.0.0")
166 .with_request_timeout(30);
167
168 assert_eq!(builder.name, "custom-server");
169 assert_eq!(builder.version, "2.0.0");
170 assert_eq!(builder.request_timeout, Some(30));
171 }
172
173 #[test]
174 fn test_build_creates_transport_pair() {
175 let (router, client_transport, server_transport) = TestServer::builder().build();
176
177 // Verify transports are not closed
178 assert!(!client_transport.is_closed());
179 assert!(!server_transport.is_closed());
180
181 // Router should be empty (no handlers added)
182 assert!(router.tools().is_empty());
183 }
184
185 // =========================================================================
186 // Additional coverage tests (bd-297e)
187 // =========================================================================
188
189 #[test]
190 fn default_trait_matches_new() {
191 let default_builder = TestServerBuilder::default();
192 let new_builder = TestServerBuilder::new();
193 assert_eq!(default_builder.name, new_builder.name);
194 assert_eq!(default_builder.version, new_builder.version);
195 assert_eq!(default_builder.request_timeout, new_builder.request_timeout);
196 }
197
198 #[test]
199 fn test_server_builder_convenience() {
200 let builder = TestServer::builder();
201 assert_eq!(builder.name, "test-server");
202 }
203
204 #[test]
205 fn build_server_builder_without_timeout() {
206 let (_builder, client, server) = TestServer::builder()
207 .with_name("sb-test")
208 .build_server_builder();
209
210 assert!(!client.is_closed());
211 assert!(!server.is_closed());
212 }
213
214 #[test]
215 fn build_server_builder_with_timeout() {
216 let (_builder, client, server) = TestServer::builder()
217 .with_request_timeout(60)
218 .build_server_builder();
219
220 assert!(!client.is_closed());
221 assert!(!server.is_closed());
222 }
223
224 #[test]
225 fn build_returns_independent_transports() {
226 let (_router, client, server) = TestServer::builder().build();
227
228 // Closing one should not close the other immediately in terms of is_closed()
229 // (MemoryTransport uses channels; closing one side makes recv on the other fail)
230 drop(client);
231 // server transport itself is not marked closed by dropping the peer
232 // (it's the channel that becomes disconnected, not the transport's own state)
233 assert!(!server.is_closed());
234 }
235}