Skip to main content

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}