Skip to main content

mockforge_bench/
mock_integration.rs

1//! Mock server integration for coordinated testing
2//!
3//! This module provides functionality to detect and integrate with
4//! MockForge mock servers for stateful testing scenarios.
5
6use crate::error::{BenchError, Result};
7use serde::{Deserialize, Serialize};
8
9/// Configuration for mock server integration
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct MockIntegrationConfig {
12    /// Whether the target is a MockForge mock server
13    pub is_mock_server: bool,
14    /// Enable stateful mode on the mock server
15    pub enable_stateful: bool,
16    /// VU-based ID generation mode
17    pub vu_based_ids: bool,
18    /// Collect mock server metrics after test
19    pub collect_metrics: bool,
20}
21
22impl Default for MockIntegrationConfig {
23    fn default() -> Self {
24        Self {
25            is_mock_server: false,
26            enable_stateful: true,
27            vu_based_ids: true,
28            collect_metrics: true,
29        }
30    }
31}
32
33impl MockIntegrationConfig {
34    /// Create config for mock server target
35    pub fn mock_server() -> Self {
36        Self {
37            is_mock_server: true,
38            ..Default::default()
39        }
40    }
41
42    /// Create config for real API target
43    pub fn real_api() -> Self {
44        Self {
45            is_mock_server: false,
46            enable_stateful: false,
47            vu_based_ids: false,
48            collect_metrics: false,
49        }
50    }
51
52    /// Enable or disable stateful mode
53    pub fn with_stateful(mut self, enabled: bool) -> Self {
54        self.enable_stateful = enabled;
55        self
56    }
57
58    /// Enable or disable VU-based ID generation
59    pub fn with_vu_based_ids(mut self, enabled: bool) -> Self {
60        self.vu_based_ids = enabled;
61        self
62    }
63}
64
65/// Mock server detection result
66#[derive(Debug, Clone)]
67pub struct MockServerInfo {
68    /// Whether the target is a MockForge mock server
69    pub is_mockforge: bool,
70    /// MockForge version (if detected)
71    pub version: Option<String>,
72    /// Available control endpoints
73    pub control_endpoints: Vec<String>,
74    /// Current stateful mode status
75    pub stateful_enabled: bool,
76}
77
78impl Default for MockServerInfo {
79    fn default() -> Self {
80        Self {
81            is_mockforge: false,
82            version: None,
83            control_endpoints: Vec::new(),
84            stateful_enabled: false,
85        }
86    }
87}
88
89/// Detects if a target URL is a MockForge mock server
90pub struct MockServerDetector;
91
92impl MockServerDetector {
93    /// Check if a URL points to a MockForge mock server
94    ///
95    /// Makes a request to `/__mockforge/info` endpoint to detect MockForge servers.
96    pub async fn detect(target_url: &str) -> Result<MockServerInfo> {
97        let client = reqwest::Client::builder()
98            .timeout(std::time::Duration::from_secs(5))
99            .build()
100            .map_err(|e| BenchError::Other(format!("Failed to create HTTP client: {}", e)))?;
101
102        let info_url = format!("{}/__mockforge/info", target_url.trim_end_matches('/'));
103
104        match client.get(&info_url).send().await {
105            Ok(response) if response.status().is_success() => {
106                let body: serde_json::Value =
107                    response.json().await.unwrap_or_else(|_| serde_json::json!({}));
108
109                Ok(MockServerInfo {
110                    is_mockforge: true,
111                    version: body.get("version").and_then(|v| v.as_str()).map(String::from),
112                    control_endpoints: vec![
113                        "/__mockforge/config".to_string(),
114                        "/__mockforge/state".to_string(),
115                        "/__mockforge/metrics".to_string(),
116                    ],
117                    stateful_enabled: body
118                        .get("stateful")
119                        .and_then(|v| v.as_bool())
120                        .unwrap_or(false),
121                })
122            }
123            _ => Ok(MockServerInfo::default()),
124        }
125    }
126
127    /// Quick check if target looks like a mock server (without HTTP request)
128    pub fn looks_like_mock_server(target_url: &str) -> bool {
129        let url_lower = target_url.to_lowercase();
130        url_lower.contains("mock")
131            || url_lower.contains("localhost")
132            || url_lower.contains("127.0.0.1")
133            || url_lower.contains(":3000")
134            || url_lower.contains(":8000")
135            || url_lower.contains(":8080")
136    }
137}
138
139/// Generates k6 JavaScript code for mock server integration
140pub struct MockIntegrationGenerator;
141
142impl MockIntegrationGenerator {
143    /// Generate k6 setup code for mock server
144    pub fn generate_setup(config: &MockIntegrationConfig) -> String {
145        if !config.is_mock_server {
146            return "// Real API target - no mock server setup needed\n".to_string();
147        }
148
149        let mut code = String::new();
150
151        code.push_str("// MockForge mock server integration\n");
152        code.push_str("export function setup() {\n");
153        code.push_str("  const configUrl = `${BASE_URL}/__mockforge/config`;\n");
154        code.push_str("  \n");
155
156        if config.enable_stateful {
157            code.push_str("  // Enable stateful mode for CRUD testing\n");
158            code.push_str("  const statefulConfig = {\n");
159            code.push_str("    stateful: true,\n");
160            if config.vu_based_ids {
161                code.push_str("    vuBasedIds: true,\n");
162            }
163            code.push_str("  };\n");
164            code.push_str("  \n");
165            code.push_str(
166                "  const configRes = http.post(configUrl, JSON.stringify(statefulConfig), {\n",
167            );
168            code.push_str("    headers: { 'Content-Type': 'application/json' }\n");
169            code.push_str("  });\n");
170            code.push_str("  \n");
171            code.push_str("  if (configRes.status !== 200) {\n");
172            code.push_str(
173                "    console.warn('Failed to configure mock server:', configRes.status);\n",
174            );
175            code.push_str("  }\n");
176        }
177
178        code.push_str("  \n");
179        code.push_str("  return { mockServerConfigured: true };\n");
180        code.push_str("}\n");
181
182        code
183    }
184
185    /// Generate k6 teardown code for collecting mock server metrics
186    pub fn generate_teardown(config: &MockIntegrationConfig) -> String {
187        if !config.is_mock_server || !config.collect_metrics {
188            return "// No mock server teardown needed\n".to_string();
189        }
190
191        let mut code = String::new();
192
193        code.push_str("// Collect mock server metrics after test\n");
194        code.push_str("export function teardown(data) {\n");
195        code.push_str("  if (!data.mockServerConfigured) return;\n");
196        code.push_str("  \n");
197        code.push_str("  const metricsUrl = `${BASE_URL}/__mockforge/metrics`;\n");
198        code.push_str("  const metricsRes = http.get(metricsUrl);\n");
199        code.push_str("  \n");
200        code.push_str("  if (metricsRes.status === 200) {\n");
201        code.push_str("    try {\n");
202        code.push_str("      const metrics = metricsRes.json();\n");
203        code.push_str("      console.log('\\n=== Mock Server Metrics ===');\n");
204        code.push_str("      console.log(`Total Requests: ${metrics.totalRequests || 0}`);\n");
205        code.push_str("      console.log(`Matched Routes: ${metrics.matchedRoutes || 0}`);\n");
206        code.push_str("      console.log(`Unmatched Routes: ${metrics.unmatchedRoutes || 0}`);\n");
207        code.push_str("      if (metrics.statefulOperations) {\n");
208        code.push_str("        console.log(`Stateful Creates: ${metrics.statefulOperations.creates || 0}`);\n");
209        code.push_str(
210            "        console.log(`Stateful Reads: ${metrics.statefulOperations.reads || 0}`);\n",
211        );
212        code.push_str("        console.log(`Stateful Updates: ${metrics.statefulOperations.updates || 0}`);\n");
213        code.push_str("        console.log(`Stateful Deletes: ${metrics.statefulOperations.deletes || 0}`);\n");
214        code.push_str("      }\n");
215        code.push_str("      console.log('===========================\\n');\n");
216        code.push_str("    } catch (e) {\n");
217        code.push_str("      console.warn('Failed to parse mock server metrics:', e);\n");
218        code.push_str("    }\n");
219        code.push_str("  }\n");
220        code.push_str("  \n");
221
222        if config.enable_stateful {
223            code.push_str("  // Reset mock server state\n");
224            code.push_str("  const resetUrl = `${BASE_URL}/__mockforge/state/reset`;\n");
225            code.push_str("  http.post(resetUrl);\n");
226        }
227
228        code.push_str("}\n");
229
230        code
231    }
232
233    /// Generate k6 code for VU-based consistent IDs
234    pub fn generate_vu_id_helper() -> String {
235        r#"// Generate consistent VU-based ID for mock server
236function getVuBasedId(prefix = 'resource') {
237  return `${prefix}-vu${__VU}-${__ITER}`;
238}
239
240// Store created resources for cleanup
241const createdResources = [];
242
243function trackResource(id) {
244  createdResources.push(id);
245}
246"#
247        .to_string()
248    }
249
250    /// Generate k6 code for mock server health check
251    pub fn generate_health_check() -> String {
252        r#"// Check mock server health before starting
253function checkMockServerHealth() {
254  const healthUrl = `${BASE_URL}/__mockforge/health`;
255  const res = http.get(healthUrl, { timeout: '5s' });
256
257  if (res.status !== 200) {
258    console.error('Mock server health check failed:', res.status);
259    return false;
260  }
261
262  return true;
263}
264"#
265        .to_string()
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_mock_integration_config_default() {
275        let config = MockIntegrationConfig::default();
276        assert!(!config.is_mock_server);
277        assert!(config.enable_stateful);
278        assert!(config.vu_based_ids);
279        assert!(config.collect_metrics);
280    }
281
282    #[test]
283    fn test_mock_integration_config_mock_server() {
284        let config = MockIntegrationConfig::mock_server();
285        assert!(config.is_mock_server);
286        assert!(config.enable_stateful);
287    }
288
289    #[test]
290    fn test_mock_integration_config_real_api() {
291        let config = MockIntegrationConfig::real_api();
292        assert!(!config.is_mock_server);
293        assert!(!config.enable_stateful);
294        assert!(!config.vu_based_ids);
295        assert!(!config.collect_metrics);
296    }
297
298    #[test]
299    fn test_mock_integration_config_builders() {
300        let config = MockIntegrationConfig::mock_server()
301            .with_stateful(false)
302            .with_vu_based_ids(false);
303
304        assert!(config.is_mock_server);
305        assert!(!config.enable_stateful);
306        assert!(!config.vu_based_ids);
307    }
308
309    #[test]
310    fn test_looks_like_mock_server() {
311        assert!(MockServerDetector::looks_like_mock_server("http://localhost:3000"));
312        assert!(MockServerDetector::looks_like_mock_server("http://127.0.0.1:8080"));
313        assert!(MockServerDetector::looks_like_mock_server("http://mock-api.local"));
314        assert!(!MockServerDetector::looks_like_mock_server("https://api.example.com"));
315    }
316
317    #[test]
318    fn test_generate_setup_real_api() {
319        let config = MockIntegrationConfig::real_api();
320        let code = MockIntegrationGenerator::generate_setup(&config);
321        assert!(code.contains("no mock server setup"));
322    }
323
324    #[test]
325    fn test_generate_setup_mock_server() {
326        let config = MockIntegrationConfig::mock_server();
327        let code = MockIntegrationGenerator::generate_setup(&config);
328        assert!(code.contains("export function setup()"));
329        assert!(code.contains("__mockforge/config"));
330        assert!(code.contains("stateful: true"));
331    }
332
333    #[test]
334    fn test_generate_setup_with_vu_based_ids() {
335        let config = MockIntegrationConfig::mock_server().with_vu_based_ids(true);
336        let code = MockIntegrationGenerator::generate_setup(&config);
337        assert!(code.contains("vuBasedIds: true"));
338    }
339
340    #[test]
341    fn test_generate_teardown_real_api() {
342        let config = MockIntegrationConfig::real_api();
343        let code = MockIntegrationGenerator::generate_teardown(&config);
344        assert!(code.contains("No mock server teardown"));
345    }
346
347    #[test]
348    fn test_generate_teardown_mock_server() {
349        let config = MockIntegrationConfig::mock_server();
350        let code = MockIntegrationGenerator::generate_teardown(&config);
351        assert!(code.contains("export function teardown"));
352        assert!(code.contains("__mockforge/metrics"));
353        assert!(code.contains("Mock Server Metrics"));
354    }
355
356    #[test]
357    fn test_generate_teardown_with_state_reset() {
358        let config = MockIntegrationConfig::mock_server().with_stateful(true);
359        let code = MockIntegrationGenerator::generate_teardown(&config);
360        assert!(code.contains("__mockforge/state/reset"));
361    }
362
363    #[test]
364    fn test_generate_vu_id_helper() {
365        let code = MockIntegrationGenerator::generate_vu_id_helper();
366        assert!(code.contains("getVuBasedId"));
367        assert!(code.contains("__VU"));
368        assert!(code.contains("__ITER"));
369        assert!(code.contains("createdResources"));
370    }
371
372    #[test]
373    fn test_generate_health_check() {
374        let code = MockIntegrationGenerator::generate_health_check();
375        assert!(code.contains("checkMockServerHealth"));
376        assert!(code.contains("__mockforge/health"));
377    }
378
379    #[test]
380    fn test_mock_server_info_default() {
381        let info = MockServerInfo::default();
382        assert!(!info.is_mockforge);
383        assert!(info.version.is_none());
384        assert!(info.control_endpoints.is_empty());
385        assert!(!info.stateful_enabled);
386    }
387}