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 = response
107                    .json()
108                    .await
109                    .unwrap_or_else(|_| serde_json::json!({}));
110
111                Ok(MockServerInfo {
112                    is_mockforge: true,
113                    version: body.get("version").and_then(|v| v.as_str()).map(String::from),
114                    control_endpoints: vec![
115                        "/__mockforge/config".to_string(),
116                        "/__mockforge/state".to_string(),
117                        "/__mockforge/metrics".to_string(),
118                    ],
119                    stateful_enabled: body
120                        .get("stateful")
121                        .and_then(|v| v.as_bool())
122                        .unwrap_or(false),
123                })
124            }
125            _ => Ok(MockServerInfo::default()),
126        }
127    }
128
129    /// Quick check if target looks like a mock server (without HTTP request)
130    pub fn looks_like_mock_server(target_url: &str) -> bool {
131        let url_lower = target_url.to_lowercase();
132        url_lower.contains("mock")
133            || url_lower.contains("localhost")
134            || url_lower.contains("127.0.0.1")
135            || url_lower.contains(":3000")
136            || url_lower.contains(":8000")
137            || url_lower.contains(":8080")
138    }
139}
140
141/// Generates k6 JavaScript code for mock server integration
142pub struct MockIntegrationGenerator;
143
144impl MockIntegrationGenerator {
145    /// Generate k6 setup code for mock server
146    pub fn generate_setup(config: &MockIntegrationConfig) -> String {
147        if !config.is_mock_server {
148            return "// Real API target - no mock server setup needed\n".to_string();
149        }
150
151        let mut code = String::new();
152
153        code.push_str("// MockForge mock server integration\n");
154        code.push_str("export function setup() {\n");
155        code.push_str("  const configUrl = `${BASE_URL}/__mockforge/config`;\n");
156        code.push_str("  \n");
157
158        if config.enable_stateful {
159            code.push_str("  // Enable stateful mode for CRUD testing\n");
160            code.push_str("  const statefulConfig = {\n");
161            code.push_str("    stateful: true,\n");
162            if config.vu_based_ids {
163                code.push_str("    vuBasedIds: true,\n");
164            }
165            code.push_str("  };\n");
166            code.push_str("  \n");
167            code.push_str("  const configRes = http.post(configUrl, JSON.stringify(statefulConfig), {\n");
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("    console.warn('Failed to configure mock server:', configRes.status);\n");
173            code.push_str("  }\n");
174        }
175
176        code.push_str("  \n");
177        code.push_str("  return { mockServerConfigured: true };\n");
178        code.push_str("}\n");
179
180        code
181    }
182
183    /// Generate k6 teardown code for collecting mock server metrics
184    pub fn generate_teardown(config: &MockIntegrationConfig) -> String {
185        if !config.is_mock_server || !config.collect_metrics {
186            return "// No mock server teardown needed\n".to_string();
187        }
188
189        let mut code = String::new();
190
191        code.push_str("// Collect mock server metrics after test\n");
192        code.push_str("export function teardown(data) {\n");
193        code.push_str("  if (!data.mockServerConfigured) return;\n");
194        code.push_str("  \n");
195        code.push_str("  const metricsUrl = `${BASE_URL}/__mockforge/metrics`;\n");
196        code.push_str("  const metricsRes = http.get(metricsUrl);\n");
197        code.push_str("  \n");
198        code.push_str("  if (metricsRes.status === 200) {\n");
199        code.push_str("    try {\n");
200        code.push_str("      const metrics = metricsRes.json();\n");
201        code.push_str("      console.log('\\n=== Mock Server Metrics ===');\n");
202        code.push_str("      console.log(`Total Requests: ${metrics.totalRequests || 0}`);\n");
203        code.push_str("      console.log(`Matched Routes: ${metrics.matchedRoutes || 0}`);\n");
204        code.push_str("      console.log(`Unmatched Routes: ${metrics.unmatchedRoutes || 0}`);\n");
205        code.push_str("      if (metrics.statefulOperations) {\n");
206        code.push_str("        console.log(`Stateful Creates: ${metrics.statefulOperations.creates || 0}`);\n");
207        code.push_str("        console.log(`Stateful Reads: ${metrics.statefulOperations.reads || 0}`);\n");
208        code.push_str("        console.log(`Stateful Updates: ${metrics.statefulOperations.updates || 0}`);\n");
209        code.push_str("        console.log(`Stateful Deletes: ${metrics.statefulOperations.deletes || 0}`);\n");
210        code.push_str("      }\n");
211        code.push_str("      console.log('===========================\\n');\n");
212        code.push_str("    } catch (e) {\n");
213        code.push_str("      console.warn('Failed to parse mock server metrics:', e);\n");
214        code.push_str("    }\n");
215        code.push_str("  }\n");
216        code.push_str("  \n");
217
218        if config.enable_stateful {
219            code.push_str("  // Reset mock server state\n");
220            code.push_str("  const resetUrl = `${BASE_URL}/__mockforge/state/reset`;\n");
221            code.push_str("  http.post(resetUrl);\n");
222        }
223
224        code.push_str("}\n");
225
226        code
227    }
228
229    /// Generate k6 code for VU-based consistent IDs
230    pub fn generate_vu_id_helper() -> String {
231        r#"// Generate consistent VU-based ID for mock server
232function getVuBasedId(prefix = 'resource') {
233  return `${prefix}-vu${__VU}-${__ITER}`;
234}
235
236// Store created resources for cleanup
237const createdResources = [];
238
239function trackResource(id) {
240  createdResources.push(id);
241}
242"#
243        .to_string()
244    }
245
246    /// Generate k6 code for mock server health check
247    pub fn generate_health_check() -> String {
248        r#"// Check mock server health before starting
249function checkMockServerHealth() {
250  const healthUrl = `${BASE_URL}/__mockforge/health`;
251  const res = http.get(healthUrl, { timeout: '5s' });
252
253  if (res.status !== 200) {
254    console.error('Mock server health check failed:', res.status);
255    return false;
256  }
257
258  return true;
259}
260"#
261        .to_string()
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_mock_integration_config_default() {
271        let config = MockIntegrationConfig::default();
272        assert!(!config.is_mock_server);
273        assert!(config.enable_stateful);
274        assert!(config.vu_based_ids);
275        assert!(config.collect_metrics);
276    }
277
278    #[test]
279    fn test_mock_integration_config_mock_server() {
280        let config = MockIntegrationConfig::mock_server();
281        assert!(config.is_mock_server);
282        assert!(config.enable_stateful);
283    }
284
285    #[test]
286    fn test_mock_integration_config_real_api() {
287        let config = MockIntegrationConfig::real_api();
288        assert!(!config.is_mock_server);
289        assert!(!config.enable_stateful);
290        assert!(!config.vu_based_ids);
291        assert!(!config.collect_metrics);
292    }
293
294    #[test]
295    fn test_mock_integration_config_builders() {
296        let config = MockIntegrationConfig::mock_server()
297            .with_stateful(false)
298            .with_vu_based_ids(false);
299
300        assert!(config.is_mock_server);
301        assert!(!config.enable_stateful);
302        assert!(!config.vu_based_ids);
303    }
304
305    #[test]
306    fn test_looks_like_mock_server() {
307        assert!(MockServerDetector::looks_like_mock_server("http://localhost:3000"));
308        assert!(MockServerDetector::looks_like_mock_server("http://127.0.0.1:8080"));
309        assert!(MockServerDetector::looks_like_mock_server("http://mock-api.local"));
310        assert!(!MockServerDetector::looks_like_mock_server("https://api.example.com"));
311    }
312
313    #[test]
314    fn test_generate_setup_real_api() {
315        let config = MockIntegrationConfig::real_api();
316        let code = MockIntegrationGenerator::generate_setup(&config);
317        assert!(code.contains("no mock server setup"));
318    }
319
320    #[test]
321    fn test_generate_setup_mock_server() {
322        let config = MockIntegrationConfig::mock_server();
323        let code = MockIntegrationGenerator::generate_setup(&config);
324        assert!(code.contains("export function setup()"));
325        assert!(code.contains("__mockforge/config"));
326        assert!(code.contains("stateful: true"));
327    }
328
329    #[test]
330    fn test_generate_setup_with_vu_based_ids() {
331        let config = MockIntegrationConfig::mock_server().with_vu_based_ids(true);
332        let code = MockIntegrationGenerator::generate_setup(&config);
333        assert!(code.contains("vuBasedIds: true"));
334    }
335
336    #[test]
337    fn test_generate_teardown_real_api() {
338        let config = MockIntegrationConfig::real_api();
339        let code = MockIntegrationGenerator::generate_teardown(&config);
340        assert!(code.contains("No mock server teardown"));
341    }
342
343    #[test]
344    fn test_generate_teardown_mock_server() {
345        let config = MockIntegrationConfig::mock_server();
346        let code = MockIntegrationGenerator::generate_teardown(&config);
347        assert!(code.contains("export function teardown"));
348        assert!(code.contains("__mockforge/metrics"));
349        assert!(code.contains("Mock Server Metrics"));
350    }
351
352    #[test]
353    fn test_generate_teardown_with_state_reset() {
354        let config = MockIntegrationConfig::mock_server().with_stateful(true);
355        let code = MockIntegrationGenerator::generate_teardown(&config);
356        assert!(code.contains("__mockforge/state/reset"));
357    }
358
359    #[test]
360    fn test_generate_vu_id_helper() {
361        let code = MockIntegrationGenerator::generate_vu_id_helper();
362        assert!(code.contains("getVuBasedId"));
363        assert!(code.contains("__VU"));
364        assert!(code.contains("__ITER"));
365        assert!(code.contains("createdResources"));
366    }
367
368    #[test]
369    fn test_generate_health_check() {
370        let code = MockIntegrationGenerator::generate_health_check();
371        assert!(code.contains("checkMockServerHealth"));
372        assert!(code.contains("__mockforge/health"));
373    }
374
375    #[test]
376    fn test_mock_server_info_default() {
377        let info = MockServerInfo::default();
378        assert!(!info.is_mockforge);
379        assert!(info.version.is_none());
380        assert!(info.control_endpoints.is_empty());
381        assert!(!info.stateful_enabled);
382    }
383}