Skip to main content

fastmcp_rust/testing/fixtures/
resources.rs

1//! Sample resource definitions for testing.
2//!
3//! Provides pre-built resource fixtures with various characteristics:
4//! - Simple text resources
5//! - JSON configuration resources
6//! - Binary resources (images, etc.)
7//! - Large resources for stress testing
8
9use fastmcp_protocol::{Resource, ResourceTemplate};
10use serde_json::json;
11
12/// Creates a simple text file resource.
13///
14/// # Example
15///
16/// ```ignore
17/// use fastmcp_rust::testing::fixtures::resources::text_file_resource;
18///
19/// let resource = text_file_resource();
20/// assert!(resource.uri.starts_with("file://"));
21/// ```
22#[must_use]
23pub fn text_file_resource() -> Resource {
24    Resource {
25        uri: "file:///test/sample.txt".to_string(),
26        name: "sample.txt".to_string(),
27        description: Some("A sample text file for testing".to_string()),
28        mime_type: Some("text/plain".to_string()),
29        icon: None,
30        version: Some("1.0.0".to_string()),
31        tags: vec!["text".to_string(), "sample".to_string()],
32    }
33}
34
35/// Creates a JSON configuration resource.
36///
37/// # Example
38///
39/// ```ignore
40/// use fastmcp_rust::testing::fixtures::resources::config_json_resource;
41///
42/// let resource = config_json_resource();
43/// assert_eq!(resource.mime_type, Some("application/json".to_string()));
44/// ```
45#[must_use]
46pub fn config_json_resource() -> Resource {
47    Resource {
48        uri: "file:///config/settings.json".to_string(),
49        name: "settings.json".to_string(),
50        description: Some("Application configuration in JSON format".to_string()),
51        mime_type: Some("application/json".to_string()),
52        icon: None,
53        version: Some("2.1.0".to_string()),
54        tags: vec!["config".to_string(), "json".to_string()],
55    }
56}
57
58/// Creates a log file resource.
59///
60/// # Example
61///
62/// ```ignore
63/// use fastmcp_rust::testing::fixtures::resources::log_file_resource;
64///
65/// let resource = log_file_resource();
66/// assert!(resource.name.contains("log"));
67/// ```
68#[must_use]
69pub fn log_file_resource() -> Resource {
70    Resource {
71        uri: "file:///var/log/app.log".to_string(),
72        name: "app.log".to_string(),
73        description: Some("Application log file".to_string()),
74        mime_type: Some("text/plain".to_string()),
75        icon: None,
76        version: None,
77        tags: vec!["log".to_string(), "monitoring".to_string()],
78    }
79}
80
81/// Creates a database resource.
82///
83/// # Example
84///
85/// ```ignore
86/// use fastmcp_rust::testing::fixtures::resources::database_resource;
87///
88/// let resource = database_resource();
89/// assert!(resource.uri.starts_with("db://"));
90/// ```
91#[must_use]
92pub fn database_resource() -> Resource {
93    Resource {
94        uri: "db://localhost/test_db/users".to_string(),
95        name: "users table".to_string(),
96        description: Some("User records from the database".to_string()),
97        mime_type: Some("application/json".to_string()),
98        icon: None,
99        version: None,
100        tags: vec!["database".to_string(), "users".to_string()],
101    }
102}
103
104/// Creates an HTTP API resource.
105///
106/// # Example
107///
108/// ```ignore
109/// use fastmcp_rust::testing::fixtures::resources::api_resource;
110///
111/// let resource = api_resource();
112/// assert!(resource.uri.starts_with("https://"));
113/// ```
114#[must_use]
115pub fn api_resource() -> Resource {
116    Resource {
117        uri: "https://api.example.com/v1/data".to_string(),
118        name: "API Data".to_string(),
119        description: Some("Data from external API".to_string()),
120        mime_type: Some("application/json".to_string()),
121        icon: None,
122        version: Some("1.0.0".to_string()),
123        tags: vec!["api".to_string(), "external".to_string()],
124    }
125}
126
127/// Creates a minimal resource with no optional fields.
128#[must_use]
129pub fn minimal_resource() -> Resource {
130    Resource {
131        uri: "file:///minimal".to_string(),
132        name: "minimal".to_string(),
133        description: None,
134        mime_type: None,
135        icon: None,
136        version: None,
137        tags: vec![],
138    }
139}
140
141/// Creates a binary image resource.
142#[must_use]
143pub fn image_resource() -> Resource {
144    Resource {
145        uri: "file:///images/logo.png".to_string(),
146        name: "logo.png".to_string(),
147        description: Some("Application logo image".to_string()),
148        mime_type: Some("image/png".to_string()),
149        icon: None,
150        version: Some("1.0.0".to_string()),
151        tags: vec!["image".to_string(), "binary".to_string()],
152    }
153}
154
155/// Returns a collection of all sample resources.
156#[must_use]
157pub fn all_sample_resources() -> Vec<Resource> {
158    vec![
159        text_file_resource(),
160        config_json_resource(),
161        log_file_resource(),
162        database_resource(),
163        api_resource(),
164        minimal_resource(),
165        image_resource(),
166    ]
167}
168
169// ============================================================================
170// Resource Templates
171// ============================================================================
172
173/// Creates a file path template.
174///
175/// # Example
176///
177/// ```ignore
178/// use fastmcp_rust::testing::fixtures::resources::file_path_template;
179///
180/// let template = file_path_template();
181/// assert!(template.uri_template.contains("{path}"));
182/// ```
183#[must_use]
184pub fn file_path_template() -> ResourceTemplate {
185    ResourceTemplate {
186        uri_template: "file:///{path}".to_string(),
187        name: "File Path".to_string(),
188        description: Some("Access any file by path".to_string()),
189        mime_type: None,
190        icon: None,
191        version: Some("1.0.0".to_string()),
192        tags: vec!["file".to_string(), "template".to_string()],
193    }
194}
195
196/// Creates a database table template.
197#[must_use]
198pub fn database_table_template() -> ResourceTemplate {
199    ResourceTemplate {
200        uri_template: "db://{host}/{database}/{table}".to_string(),
201        name: "Database Table".to_string(),
202        description: Some("Access any database table".to_string()),
203        mime_type: Some("application/json".to_string()),
204        icon: None,
205        version: Some("1.0.0".to_string()),
206        tags: vec!["database".to_string(), "template".to_string()],
207    }
208}
209
210/// Creates an API endpoint template.
211#[must_use]
212pub fn api_endpoint_template() -> ResourceTemplate {
213    ResourceTemplate {
214        uri_template: "https://api.example.com/{version}/{resource}".to_string(),
215        name: "API Endpoint".to_string(),
216        description: Some("Access versioned API endpoints".to_string()),
217        mime_type: Some("application/json".to_string()),
218        icon: None,
219        version: Some("1.0.0".to_string()),
220        tags: vec!["api".to_string(), "template".to_string()],
221    }
222}
223
224/// Creates a user profile template.
225#[must_use]
226pub fn user_profile_template() -> ResourceTemplate {
227    ResourceTemplate {
228        uri_template: "user://{user_id}/profile".to_string(),
229        name: "User Profile".to_string(),
230        description: Some("Access user profile by ID".to_string()),
231        mime_type: Some("application/json".to_string()),
232        icon: None,
233        version: None,
234        tags: vec!["user".to_string(), "profile".to_string()],
235    }
236}
237
238/// Returns all sample resource templates.
239#[must_use]
240pub fn all_sample_templates() -> Vec<ResourceTemplate> {
241    vec![
242        file_path_template(),
243        database_table_template(),
244        api_endpoint_template(),
245        user_profile_template(),
246    ]
247}
248
249// ============================================================================
250// Resource Content Generators
251// ============================================================================
252
253/// Generates sample text content.
254#[must_use]
255pub fn sample_text_content() -> String {
256    "Hello, World!\nThis is sample text content for testing.\nLine 3.\nLine 4.".to_string()
257}
258
259/// Generates sample JSON configuration content.
260#[must_use]
261pub fn sample_json_config() -> serde_json::Value {
262    json!({
263        "version": "1.0.0",
264        "settings": {
265            "debug": false,
266            "log_level": "info",
267            "max_connections": 100
268        },
269        "features": {
270            "experimental": false,
271            "beta": true
272        },
273        "database": {
274            "host": "localhost",
275            "port": 5432,
276            "name": "test_db"
277        }
278    })
279}
280
281/// Generates sample log content.
282#[must_use]
283pub fn sample_log_content() -> String {
284    [
285        "[2024-01-15 10:30:00] INFO: Server started",
286        "[2024-01-15 10:30:01] INFO: Listening on port 8080",
287        "[2024-01-15 10:30:05] DEBUG: Connection accepted from 127.0.0.1",
288        "[2024-01-15 10:30:06] INFO: Request processed in 15ms",
289        "[2024-01-15 10:30:10] WARN: High memory usage detected",
290        "[2024-01-15 10:30:15] ERROR: Connection timeout",
291    ]
292    .join("\n")
293}
294
295/// Generates large text content for stress testing.
296///
297/// # Arguments
298///
299/// * `size_kb` - Approximate size in kilobytes
300#[must_use]
301pub fn large_text_content(size_kb: usize) -> String {
302    let line = "This is a line of text for stress testing. ".repeat(10);
303    let lines_needed = (size_kb * 1024) / line.len() + 1;
304    (0..lines_needed)
305        .map(|i| format!("[Line {i:06}] {line}"))
306        .collect::<Vec<_>>()
307        .join("\n")
308}
309
310/// Builder for customizing resource fixtures.
311#[derive(Debug, Clone)]
312pub struct ResourceBuilder {
313    uri: String,
314    name: String,
315    description: Option<String>,
316    mime_type: Option<String>,
317    version: Option<String>,
318    tags: Vec<String>,
319}
320
321impl ResourceBuilder {
322    /// Creates a new resource builder.
323    #[must_use]
324    pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
325        Self {
326            uri: uri.into(),
327            name: name.into(),
328            description: None,
329            mime_type: None,
330            version: None,
331            tags: Vec::new(),
332        }
333    }
334
335    /// Sets the description.
336    #[must_use]
337    pub fn description(mut self, desc: impl Into<String>) -> Self {
338        self.description = Some(desc.into());
339        self
340    }
341
342    /// Sets the MIME type.
343    #[must_use]
344    pub fn mime_type(mut self, mime: impl Into<String>) -> Self {
345        self.mime_type = Some(mime.into());
346        self
347    }
348
349    /// Sets the version.
350    #[must_use]
351    pub fn version(mut self, version: impl Into<String>) -> Self {
352        self.version = Some(version.into());
353        self
354    }
355
356    /// Sets tags.
357    #[must_use]
358    pub fn tags(mut self, tags: Vec<String>) -> Self {
359        self.tags = tags;
360        self
361    }
362
363    /// Builds the resource.
364    #[must_use]
365    pub fn build(self) -> Resource {
366        Resource {
367            uri: self.uri,
368            name: self.name,
369            description: self.description,
370            mime_type: self.mime_type,
371            icon: None,
372            version: self.version,
373            tags: self.tags,
374        }
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_text_file_resource() {
384        let resource = text_file_resource();
385        assert!(resource.uri.starts_with("file://"));
386        assert_eq!(resource.mime_type, Some("text/plain".to_string()));
387    }
388
389    #[test]
390    fn test_config_json_resource() {
391        let resource = config_json_resource();
392        assert_eq!(resource.mime_type, Some("application/json".to_string()));
393    }
394
395    #[test]
396    fn test_database_resource() {
397        let resource = database_resource();
398        assert!(resource.uri.starts_with("db://"));
399    }
400
401    #[test]
402    fn test_api_resource() {
403        let resource = api_resource();
404        assert!(resource.uri.starts_with("https://"));
405    }
406
407    #[test]
408    fn test_minimal_resource() {
409        let resource = minimal_resource();
410        assert!(resource.description.is_none());
411        assert!(resource.mime_type.is_none());
412        assert!(resource.tags.is_empty());
413    }
414
415    #[test]
416    fn test_all_sample_resources() {
417        let resources = all_sample_resources();
418        assert!(resources.len() >= 5);
419
420        // Verify uniqueness of URIs
421        let uris: Vec<_> = resources.iter().map(|r| &r.uri).collect();
422        let unique: std::collections::HashSet<_> = uris.iter().collect();
423        assert_eq!(uris.len(), unique.len());
424    }
425
426    #[test]
427    fn test_file_path_template() {
428        let template = file_path_template();
429        assert!(template.uri_template.contains("{path}"));
430    }
431
432    #[test]
433    fn test_database_table_template() {
434        let template = database_table_template();
435        assert!(template.uri_template.contains("{host}"));
436        assert!(template.uri_template.contains("{database}"));
437        assert!(template.uri_template.contains("{table}"));
438    }
439
440    #[test]
441    fn test_all_sample_templates() {
442        let templates = all_sample_templates();
443        assert!(templates.len() >= 3);
444    }
445
446    #[test]
447    fn test_sample_text_content() {
448        let content = sample_text_content();
449        assert!(content.contains("Hello, World!"));
450    }
451
452    #[test]
453    fn test_sample_json_config() {
454        let config = sample_json_config();
455        assert!(config.get("version").is_some());
456        assert!(config.get("settings").is_some());
457    }
458
459    #[test]
460    fn test_sample_log_content() {
461        let log = sample_log_content();
462        assert!(log.contains("INFO"));
463        assert!(log.contains("ERROR"));
464    }
465
466    #[test]
467    fn test_large_text_content() {
468        let content = large_text_content(10);
469        // Should be approximately 10KB
470        assert!(content.len() >= 9 * 1024);
471        assert!(content.len() <= 12 * 1024);
472    }
473
474    #[test]
475    fn test_resource_builder() {
476        let resource = ResourceBuilder::new("file:///test", "test")
477            .description("A test resource")
478            .mime_type("text/plain")
479            .version("1.0.0")
480            .tags(vec!["test".to_string()])
481            .build();
482
483        assert_eq!(resource.uri, "file:///test");
484        assert_eq!(resource.name, "test");
485        assert_eq!(resource.description, Some("A test resource".to_string()));
486    }
487}