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 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}