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