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