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, Default)]
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
78pub struct MockServerDetector;
80
81impl MockServerDetector {
82 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 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
128pub struct MockIntegrationGenerator;
130
131impl MockIntegrationGenerator {
132 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 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 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 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}