1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ParallelConfig {
12 pub count: u32,
14 pub collect_ids: bool,
16 pub max_batch_size: u32,
18 pub batch_delay_ms: u32,
20}
21
22impl Default for ParallelConfig {
23 fn default() -> Self {
24 Self {
25 count: 10,
26 collect_ids: true,
27 max_batch_size: 100, batch_delay_ms: 100,
29 }
30 }
31}
32
33impl ParallelConfig {
34 pub fn new(count: u32) -> Self {
36 Self {
37 count,
38 ..Default::default()
39 }
40 }
41
42 pub fn with_collect_ids(mut self, collect: bool) -> Self {
44 self.collect_ids = collect;
45 self
46 }
47
48 pub fn with_max_batch_size(mut self, size: u32) -> Self {
50 self.max_batch_size = size;
51 self
52 }
53
54 pub fn num_batches(&self) -> u32 {
56 if self.max_batch_size == 0 {
57 return 1;
58 }
59 (self.count + self.max_batch_size - 1) / self.max_batch_size
60 }
61
62 pub fn batch_size(&self, batch_index: u32) -> u32 {
64 let remaining = self.count - (batch_index * self.max_batch_size);
65 remaining.min(self.max_batch_size)
66 }
67}
68
69pub struct ParallelRequestGenerator;
71
72impl ParallelRequestGenerator {
73 pub fn generate_parallel_post(
78 config: &ParallelConfig,
79 path: &str,
80 body_template: &str,
81 id_field: &str,
82 ) -> String {
83 let mut code = String::new();
84
85 code.push_str("// Parallel resource creation\n");
87 code.push_str("const batchRequests = [];\n");
88 code.push_str(&format!(
89 "for (let i = 0; i < {}; i++) {{\n",
90 config.count
91 ));
92 code.push_str(" batchRequests.push({\n");
93 code.push_str(" method: 'POST',\n");
94 code.push_str(&format!(" url: `${{BASE_URL}}{}`", path));
95 code.push_str(",\n");
96 code.push_str(&format!(" body: JSON.stringify({}),\n", body_template));
97 code.push_str(" params: { headers }\n");
98 code.push_str(" });\n");
99 code.push_str("}\n\n");
100
101 if config.count > config.max_batch_size {
103 code.push_str("// Execute in batches to avoid overwhelming the server\n");
104 code.push_str("const createdIds = [];\n");
105 code.push_str(&format!(
106 "const batchSize = {};\n",
107 config.max_batch_size
108 ));
109 code.push_str("for (let batchStart = 0; batchStart < batchRequests.length; batchStart += batchSize) {\n");
110 code.push_str(" const batchEnd = Math.min(batchStart + batchSize, batchRequests.length);\n");
111 code.push_str(" const batch = batchRequests.slice(batchStart, batchEnd);\n");
112 code.push_str(" const responses = http.batch(batch);\n\n");
113
114 if config.collect_ids {
115 code.push_str(" // Collect IDs from responses\n");
116 code.push_str(" for (const res of responses) {\n");
117 code.push_str(" if (res.status >= 200 && res.status < 300) {\n");
118 code.push_str(" try {\n");
119 code.push_str(&format!(
120 " const id = res.json('{}');\n",
121 id_field
122 ));
123 code.push_str(" if (id) createdIds.push(id);\n");
124 code.push_str(" } catch (e) {\n");
125 code.push_str(" console.error('Failed to extract ID:', e);\n");
126 code.push_str(" }\n");
127 code.push_str(" }\n");
128 code.push_str(" }\n\n");
129 }
130
131 code.push_str(" // Check batch results\n");
132 code.push_str(" const batchSuccess = responses.every(r => r.status >= 200 && r.status < 300);\n");
133 code.push_str(" check(responses, {\n");
134 code.push_str(" 'batch creation successful': () => batchSuccess\n");
135 code.push_str(" });\n\n");
136
137 if config.batch_delay_ms > 0 {
138 code.push_str(&format!(
139 " sleep({});\n",
140 config.batch_delay_ms as f64 / 1000.0
141 ));
142 }
143 code.push_str("}\n");
144 } else {
145 code.push_str("// Execute all requests in parallel\n");
146 code.push_str("const responses = http.batch(batchRequests);\n\n");
147
148 if config.collect_ids {
149 code.push_str("// Collect IDs from responses\n");
150 code.push_str("const createdIds = [];\n");
151 code.push_str("for (const res of responses) {\n");
152 code.push_str(" if (res.status >= 200 && res.status < 300) {\n");
153 code.push_str(" try {\n");
154 code.push_str(&format!(
155 " const id = res.json('{}');\n",
156 id_field
157 ));
158 code.push_str(" if (id) createdIds.push(id);\n");
159 code.push_str(" } catch (e) {\n");
160 code.push_str(" console.error('Failed to extract ID:', e);\n");
161 code.push_str(" }\n");
162 code.push_str(" }\n");
163 code.push_str("}\n\n");
164 }
165
166 code.push_str("// Check all responses\n");
167 code.push_str("const allSuccess = responses.every(r => r.status >= 200 && r.status < 300);\n");
168 code.push_str("check(responses, {\n");
169 code.push_str(" 'parallel creation successful': () => allSuccess\n");
170 code.push_str("});\n");
171 }
172
173 code
174 }
175
176 pub fn generate_parallel_get(path_template: &str, id_param: &str) -> String {
178 let mut code = String::new();
179
180 code.push_str("// Parallel resource retrieval\n");
181 code.push_str("if (createdIds.length > 0) {\n");
182 code.push_str(" const getRequests = createdIds.map(id => ({\n");
183 code.push_str(" method: 'GET',\n");
184 code.push_str(&format!(
185 " url: `${{BASE_URL}}{}`.replace('{{{{{}}}}}', id),\n",
186 path_template, id_param
187 ));
188 code.push_str(" params: { headers }\n");
189 code.push_str(" }));\n\n");
190 code.push_str(" const getResponses = http.batch(getRequests);\n");
191 code.push_str(" const getSuccess = getResponses.every(r => r.status >= 200 && r.status < 300);\n");
192 code.push_str(" check(getResponses, {\n");
193 code.push_str(" 'parallel retrieval successful': () => getSuccess\n");
194 code.push_str(" });\n");
195 code.push_str("}\n");
196
197 code
198 }
199
200 pub fn generate_parallel_delete(path_template: &str, id_param: &str) -> String {
202 let mut code = String::new();
203
204 code.push_str("// Parallel resource cleanup\n");
205 code.push_str("if (createdIds.length > 0) {\n");
206 code.push_str(" const deleteRequests = createdIds.map(id => ({\n");
207 code.push_str(" method: 'DELETE',\n");
208 code.push_str(&format!(
209 " url: `${{BASE_URL}}{}`.replace('{{{{{}}}}}', id),\n",
210 path_template, id_param
211 ));
212 code.push_str(" params: { headers }\n");
213 code.push_str(" }));\n\n");
214 code.push_str(" const deleteResponses = http.batch(deleteRequests);\n");
215 code.push_str(" const deleteSuccess = deleteResponses.every(r => r.status >= 200 && r.status < 300);\n");
216 code.push_str(" check(deleteResponses, {\n");
217 code.push_str(" 'parallel cleanup successful': () => deleteSuccess\n");
218 code.push_str(" });\n");
219 code.push_str("}\n");
220
221 code
222 }
223
224 pub fn generate_batch_helper(config: &ParallelConfig) -> String {
226 let mut code = String::new();
227
228 code.push_str("// Parallel batch execution helpers\n");
229 code.push_str(&format!(
230 "const PARALLEL_BATCH_SIZE = {};\n",
231 config.max_batch_size
232 ));
233 code.push_str(&format!(
234 "const PARALLEL_COUNT = {};\n\n",
235 config.count
236 ));
237
238 code.push_str(r#"function executeBatch(requests) {
239 const results = [];
240 const batchSize = PARALLEL_BATCH_SIZE;
241
242 for (let i = 0; i < requests.length; i += batchSize) {
243 const batch = requests.slice(i, i + batchSize);
244 const responses = http.batch(batch);
245 results.push(...responses);
246 }
247
248 return results;
249}
250
251function collectIds(responses, idField = 'id') {
252 const ids = [];
253 for (const res of responses) {
254 if (res.status >= 200 && res.status < 300) {
255 try {
256 const id = res.json(idField);
257 if (id) ids.push(id);
258 } catch (e) {
259 // Ignore parse errors
260 }
261 }
262 }
263 return ids;
264}
265"#);
266
267 code
268 }
269
270 pub fn generate_complete_scenario(
272 config: &ParallelConfig,
273 base_path: &str,
274 detail_path: &str,
275 id_param: &str,
276 body_template: &str,
277 id_field: &str,
278 include_cleanup: bool,
279 ) -> String {
280 let mut code = String::new();
281
282 code.push_str(&Self::generate_parallel_post(config, base_path, body_template, id_field));
284 code.push_str("\n");
285
286 code.push_str(&Self::generate_parallel_get(detail_path, id_param));
288 code.push_str("\n");
289
290 if include_cleanup {
292 code.push_str(&Self::generate_parallel_delete(detail_path, id_param));
293 }
294
295 code
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_parallel_config_default() {
305 let config = ParallelConfig::default();
306 assert_eq!(config.count, 10);
307 assert!(config.collect_ids);
308 assert_eq!(config.max_batch_size, 100);
309 }
310
311 #[test]
312 fn test_parallel_config_new() {
313 let config = ParallelConfig::new(50);
314 assert_eq!(config.count, 50);
315 assert!(config.collect_ids);
316 }
317
318 #[test]
319 fn test_parallel_config_builders() {
320 let config = ParallelConfig::new(100)
321 .with_collect_ids(false)
322 .with_max_batch_size(25);
323
324 assert_eq!(config.count, 100);
325 assert!(!config.collect_ids);
326 assert_eq!(config.max_batch_size, 25);
327 }
328
329 #[test]
330 fn test_num_batches() {
331 let config = ParallelConfig::new(100).with_max_batch_size(30);
332 assert_eq!(config.num_batches(), 4); let config2 = ParallelConfig::new(50).with_max_batch_size(100);
335 assert_eq!(config2.num_batches(), 1);
336
337 let config3 = ParallelConfig::new(100).with_max_batch_size(100);
338 assert_eq!(config3.num_batches(), 1);
339 }
340
341 #[test]
342 fn test_batch_size() {
343 let config = ParallelConfig::new(100).with_max_batch_size(30);
344
345 assert_eq!(config.batch_size(0), 30);
346 assert_eq!(config.batch_size(1), 30);
347 assert_eq!(config.batch_size(2), 30);
348 assert_eq!(config.batch_size(3), 10); }
350
351 #[test]
352 fn test_generate_parallel_post_small_batch() {
353 let config = ParallelConfig::new(5);
354 let code = ParallelRequestGenerator::generate_parallel_post(
355 &config,
356 "/resources",
357 "{ name: `resource-${__VU}-${i}` }",
358 "id",
359 );
360
361 assert!(code.contains("batchRequests.push"));
362 assert!(code.contains("for (let i = 0; i < 5; i++)"));
363 assert!(code.contains("http.batch(batchRequests)"));
364 assert!(code.contains("res.json('id')"));
365 }
366
367 #[test]
368 fn test_generate_parallel_post_large_batch() {
369 let config = ParallelConfig::new(150).with_max_batch_size(50);
370 let code = ParallelRequestGenerator::generate_parallel_post(
371 &config,
372 "/resources",
373 "{ name: `resource-${i}` }",
374 "uuid",
375 );
376
377 assert!(code.contains("Execute in batches"));
378 assert!(code.contains("batchSize = 50"));
379 assert!(code.contains("batchStart + batchSize"));
380 assert!(code.contains("res.json('uuid')"));
381 }
382
383 #[test]
384 fn test_generate_parallel_post_no_collect() {
385 let config = ParallelConfig::new(10).with_collect_ids(false);
386 let code = ParallelRequestGenerator::generate_parallel_post(
387 &config,
388 "/resources",
389 "{ name: 'test' }",
390 "id",
391 );
392
393 assert!(!code.contains("createdIds.push"));
394 assert!(!code.contains("res.json"));
395 }
396
397 #[test]
398 fn test_generate_parallel_get() {
399 let code = ParallelRequestGenerator::generate_parallel_get("/resources/{id}", "id");
400
401 assert!(code.contains("Parallel resource retrieval"));
402 assert!(code.contains("createdIds.map"));
403 assert!(code.contains("method: 'GET'"));
404 assert!(code.contains("{{id}}") || code.contains("{id}"));
406 }
407
408 #[test]
409 fn test_generate_parallel_delete() {
410 let code = ParallelRequestGenerator::generate_parallel_delete("/resources/{resourceId}", "resourceId");
411
412 assert!(code.contains("Parallel resource cleanup"));
413 assert!(code.contains("method: 'DELETE'"));
414 assert!(code.contains("{{resourceId}}") || code.contains("{resourceId}"));
416 }
417
418 #[test]
419 fn test_generate_complete_scenario() {
420 let config = ParallelConfig::new(20);
421 let code = ParallelRequestGenerator::generate_complete_scenario(
422 &config,
423 "/users",
424 "/users/{id}",
425 "id",
426 "{ name: `user-${i}` }",
427 "id",
428 true,
429 );
430
431 assert!(code.contains("Parallel resource creation"));
432 assert!(code.contains("Parallel resource retrieval"));
433 assert!(code.contains("Parallel resource cleanup"));
434 }
435
436 #[test]
437 fn test_generate_complete_scenario_no_cleanup() {
438 let config = ParallelConfig::new(20);
439 let code = ParallelRequestGenerator::generate_complete_scenario(
440 &config,
441 "/users",
442 "/users/{id}",
443 "id",
444 "{ name: `user-${i}` }",
445 "id",
446 false,
447 );
448
449 assert!(code.contains("Parallel resource creation"));
450 assert!(code.contains("Parallel resource retrieval"));
451 assert!(!code.contains("Parallel resource cleanup"));
452 }
453}