mockforge_bench/
mock_integration.rs1use crate::error::{BenchError, Result};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct MockIntegrationConfig {
12 pub is_mock_server: bool,
14 pub enable_stateful: bool,
16 pub vu_based_ids: bool,
18 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 pub fn mock_server() -> Self {
36 Self {
37 is_mock_server: true,
38 ..Default::default()
39 }
40 }
41
42 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 pub fn with_stateful(mut self, enabled: bool) -> Self {
54 self.enable_stateful = enabled;
55 self
56 }
57
58 pub fn with_vu_based_ids(mut self, enabled: bool) -> Self {
60 self.vu_based_ids = enabled;
61 self
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct MockServerInfo {
68 pub is_mockforge: bool,
70 pub version: Option<String>,
72 pub control_endpoints: Vec<String>,
74 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
89pub struct MockServerDetector;
91
92impl MockServerDetector {
93 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 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
139pub struct MockIntegrationGenerator;
141
142impl MockIntegrationGenerator {
143 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 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 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 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}