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 = 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 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
141pub struct MockIntegrationGenerator;
143
144impl MockIntegrationGenerator {
145 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 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 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 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}