@testsmith/perfornium 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +360 -0
  2. package/dist/cli/cli.d.ts +2 -0
  3. package/dist/cli/cli.js +192 -0
  4. package/dist/cli/commands/distributed.d.ts +11 -0
  5. package/dist/cli/commands/distributed.js +179 -0
  6. package/dist/cli/commands/import.d.ts +23 -0
  7. package/dist/cli/commands/import.js +461 -0
  8. package/dist/cli/commands/init.d.ts +7 -0
  9. package/dist/cli/commands/init.js +923 -0
  10. package/dist/cli/commands/mock.d.ts +7 -0
  11. package/dist/cli/commands/mock.js +281 -0
  12. package/dist/cli/commands/report.d.ts +5 -0
  13. package/dist/cli/commands/report.js +70 -0
  14. package/dist/cli/commands/run.d.ts +12 -0
  15. package/dist/cli/commands/run.js +260 -0
  16. package/dist/cli/commands/validate.d.ts +3 -0
  17. package/dist/cli/commands/validate.js +35 -0
  18. package/dist/cli/commands/worker.d.ts +27 -0
  19. package/dist/cli/commands/worker.js +320 -0
  20. package/dist/config/index.d.ts +2 -0
  21. package/dist/config/index.js +20 -0
  22. package/dist/config/parser.d.ts +19 -0
  23. package/dist/config/parser.js +330 -0
  24. package/dist/config/types/global-config.d.ts +74 -0
  25. package/dist/config/types/global-config.js +2 -0
  26. package/dist/config/types/hooks.d.ts +58 -0
  27. package/dist/config/types/hooks.js +3 -0
  28. package/dist/config/types/import-types.d.ts +33 -0
  29. package/dist/config/types/import-types.js +2 -0
  30. package/dist/config/types/index.d.ts +11 -0
  31. package/dist/config/types/index.js +27 -0
  32. package/dist/config/types/load-config.d.ts +32 -0
  33. package/dist/config/types/load-config.js +9 -0
  34. package/dist/config/types/output-config.d.ts +10 -0
  35. package/dist/config/types/output-config.js +2 -0
  36. package/dist/config/types/report-config.d.ts +10 -0
  37. package/dist/config/types/report-config.js +2 -0
  38. package/dist/config/types/runtime-types.d.ts +6 -0
  39. package/dist/config/types/runtime-types.js +2 -0
  40. package/dist/config/types/scenario-config.d.ts +30 -0
  41. package/dist/config/types/scenario-config.js +2 -0
  42. package/dist/config/types/step-types.d.ts +139 -0
  43. package/dist/config/types/step-types.js +2 -0
  44. package/dist/config/types/test-configuration.d.ts +18 -0
  45. package/dist/config/types/test-configuration.js +2 -0
  46. package/dist/config/types/worker-config.d.ts +12 -0
  47. package/dist/config/types/worker-config.js +2 -0
  48. package/dist/config/validator.d.ts +19 -0
  49. package/dist/config/validator.js +198 -0
  50. package/dist/core/csv-data-provider.d.ts +47 -0
  51. package/dist/core/csv-data-provider.js +265 -0
  52. package/dist/core/hooks-manager.d.ts +33 -0
  53. package/dist/core/hooks-manager.js +129 -0
  54. package/dist/core/index.d.ts +5 -0
  55. package/dist/core/index.js +11 -0
  56. package/dist/core/script-executor.d.ts +14 -0
  57. package/dist/core/script-executor.js +290 -0
  58. package/dist/core/step-executor.d.ts +41 -0
  59. package/dist/core/step-executor.js +680 -0
  60. package/dist/core/test-runner.d.ts +34 -0
  61. package/dist/core/test-runner.js +465 -0
  62. package/dist/core/threshold-evaluator.d.ts +43 -0
  63. package/dist/core/threshold-evaluator.js +170 -0
  64. package/dist/core/virtual-user-pool.d.ts +42 -0
  65. package/dist/core/virtual-user-pool.js +136 -0
  66. package/dist/core/virtual-user.d.ts +51 -0
  67. package/dist/core/virtual-user.js +488 -0
  68. package/dist/distributed/coordinator.d.ts +34 -0
  69. package/dist/distributed/coordinator.js +158 -0
  70. package/dist/distributed/health-monitor.d.ts +18 -0
  71. package/dist/distributed/health-monitor.js +72 -0
  72. package/dist/distributed/load-distributor.d.ts +17 -0
  73. package/dist/distributed/load-distributor.js +106 -0
  74. package/dist/distributed/remote-worker.d.ts +37 -0
  75. package/dist/distributed/remote-worker.js +241 -0
  76. package/dist/distributed/result-aggregator.d.ts +43 -0
  77. package/dist/distributed/result-aggregator.js +146 -0
  78. package/dist/dsl/index.d.ts +3 -0
  79. package/dist/dsl/index.js +11 -0
  80. package/dist/dsl/test-builder.d.ts +111 -0
  81. package/dist/dsl/test-builder.js +514 -0
  82. package/dist/importers/har-importer.d.ts +17 -0
  83. package/dist/importers/har-importer.js +172 -0
  84. package/dist/importers/open-api-importer.d.ts +23 -0
  85. package/dist/importers/open-api-importer.js +181 -0
  86. package/dist/importers/wsdl-importer.d.ts +42 -0
  87. package/dist/importers/wsdl-importer.js +440 -0
  88. package/dist/index.d.ts +5 -0
  89. package/dist/index.js +17 -0
  90. package/dist/load-patterns/arrivals.d.ts +7 -0
  91. package/dist/load-patterns/arrivals.js +118 -0
  92. package/dist/load-patterns/base.d.ts +9 -0
  93. package/dist/load-patterns/base.js +2 -0
  94. package/dist/load-patterns/basic.d.ts +7 -0
  95. package/dist/load-patterns/basic.js +117 -0
  96. package/dist/load-patterns/stepping.d.ts +6 -0
  97. package/dist/load-patterns/stepping.js +122 -0
  98. package/dist/metrics/collector.d.ts +72 -0
  99. package/dist/metrics/collector.js +662 -0
  100. package/dist/metrics/types.d.ts +135 -0
  101. package/dist/metrics/types.js +2 -0
  102. package/dist/outputs/base.d.ts +7 -0
  103. package/dist/outputs/base.js +2 -0
  104. package/dist/outputs/csv.d.ts +13 -0
  105. package/dist/outputs/csv.js +163 -0
  106. package/dist/outputs/graphite.d.ts +13 -0
  107. package/dist/outputs/graphite.js +126 -0
  108. package/dist/outputs/influxdb.d.ts +12 -0
  109. package/dist/outputs/influxdb.js +82 -0
  110. package/dist/outputs/json.d.ts +14 -0
  111. package/dist/outputs/json.js +107 -0
  112. package/dist/outputs/streaming-csv.d.ts +37 -0
  113. package/dist/outputs/streaming-csv.js +254 -0
  114. package/dist/outputs/streaming-json.d.ts +43 -0
  115. package/dist/outputs/streaming-json.js +353 -0
  116. package/dist/outputs/webhook.d.ts +16 -0
  117. package/dist/outputs/webhook.js +96 -0
  118. package/dist/protocols/base.d.ts +33 -0
  119. package/dist/protocols/base.js +2 -0
  120. package/dist/protocols/rest/handler.d.ts +67 -0
  121. package/dist/protocols/rest/handler.js +776 -0
  122. package/dist/protocols/soap/handler.d.ts +12 -0
  123. package/dist/protocols/soap/handler.js +165 -0
  124. package/dist/protocols/web/core-web-vitals.d.ts +121 -0
  125. package/dist/protocols/web/core-web-vitals.js +373 -0
  126. package/dist/protocols/web/handler.d.ts +50 -0
  127. package/dist/protocols/web/handler.js +706 -0
  128. package/dist/recorder/native-recorder.d.ts +14 -0
  129. package/dist/recorder/native-recorder.js +533 -0
  130. package/dist/recorder/scenario-recorder.d.ts +55 -0
  131. package/dist/recorder/scenario-recorder.js +296 -0
  132. package/dist/reporting/constants.d.ts +94 -0
  133. package/dist/reporting/constants.js +82 -0
  134. package/dist/reporting/enhanced-html-generator.d.ts +55 -0
  135. package/dist/reporting/enhanced-html-generator.js +965 -0
  136. package/dist/reporting/generator.d.ts +42 -0
  137. package/dist/reporting/generator.js +1217 -0
  138. package/dist/reporting/statistics.d.ts +144 -0
  139. package/dist/reporting/statistics.js +742 -0
  140. package/dist/reporting/templates/enhanced-report.hbs +2812 -0
  141. package/dist/reporting/templates/html.hbs +2453 -0
  142. package/dist/utils/faker-manager.d.ts +55 -0
  143. package/dist/utils/faker-manager.js +166 -0
  144. package/dist/utils/file-manager.d.ts +33 -0
  145. package/dist/utils/file-manager.js +154 -0
  146. package/dist/utils/handlebars-manager.d.ts +42 -0
  147. package/dist/utils/handlebars-manager.js +172 -0
  148. package/dist/utils/logger.d.ts +16 -0
  149. package/dist/utils/logger.js +46 -0
  150. package/dist/utils/template.d.ts +80 -0
  151. package/dist/utils/template.js +513 -0
  152. package/dist/utils/test-output-writer.d.ts +56 -0
  153. package/dist/utils/test-output-writer.js +643 -0
  154. package/dist/utils/time.d.ts +3 -0
  155. package/dist/utils/time.js +23 -0
  156. package/dist/utils/timestamp-helper.d.ts +17 -0
  157. package/dist/utils/timestamp-helper.js +53 -0
  158. package/dist/workers/manager.d.ts +18 -0
  159. package/dist/workers/manager.js +95 -0
  160. package/dist/workers/server.d.ts +21 -0
  161. package/dist/workers/server.js +205 -0
  162. package/dist/workers/worker.d.ts +19 -0
  163. package/dist/workers/worker.js +147 -0
  164. package/package.json +102 -0
@@ -0,0 +1,440 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WSDLImporter = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ class WSDLImporter {
40
+ constructor(wsdlContent) {
41
+ this.selectedSoapVersions = [];
42
+ this.wsdlContent = wsdlContent;
43
+ }
44
+ detectSoapVersions() {
45
+ const versions = [];
46
+ // Find SOAP 1.1 bindings
47
+ const soap11BindingRegex = /<wsdl:binding[^>]*name="([^"]*)"[^>]*>[\s\S]*?<soap:binding[^>]*>[\s\S]*?<\/wsdl:binding>/g;
48
+ let soap11Match;
49
+ while ((soap11Match = soap11BindingRegex.exec(this.wsdlContent)) !== null) {
50
+ if (!versions.some(v => v.bindingName === soap11Match[1])) {
51
+ versions.push({
52
+ version: '1.1',
53
+ bindingName: soap11Match[1],
54
+ description: 'SOAP 1.1 (Legacy, widely supported)'
55
+ });
56
+ }
57
+ }
58
+ // Find SOAP 1.2 bindings
59
+ const soap12BindingRegex = /<wsdl:binding[^>]*name="([^"]*)"[^>]*>[\s\S]*?<soap12:binding[^>]*>[\s\S]*?<\/wsdl:binding>/g;
60
+ let soap12Match;
61
+ while ((soap12Match = soap12BindingRegex.exec(this.wsdlContent)) !== null) {
62
+ if (!versions.some(v => v.bindingName === soap12Match[1])) {
63
+ versions.push({
64
+ version: '1.2',
65
+ bindingName: soap12Match[1],
66
+ description: 'SOAP 1.2 (Modern, more features)'
67
+ });
68
+ }
69
+ }
70
+ return versions;
71
+ }
72
+ setSoapVersions(versions) {
73
+ this.selectedSoapVersions = versions;
74
+ }
75
+ extractServices() {
76
+ const services = [];
77
+ try {
78
+ const serviceElements = this.extractServiceElements();
79
+ for (const serviceElement of serviceElements) {
80
+ const serviceName = this.extractAttribute(serviceElement.content, 'name') || 'UnknownService';
81
+ const ports = this.extractPortElements(serviceElement.content);
82
+ for (const port of ports) {
83
+ const bindingName = this.extractAttribute(port.content, 'binding')?.replace('tns:', '') || '';
84
+ if (!this.isBindingVersionSelected(bindingName)) {
85
+ continue;
86
+ }
87
+ const endpoint = this.extractEndpointFromPort(port.content);
88
+ const soapVersion = this.getSoapVersionFromBinding(bindingName);
89
+ const operations = this.extractOperationsFromBinding(bindingName);
90
+ for (const operation of operations) {
91
+ services.push(this.createImportableEndpoint({
92
+ serviceName,
93
+ bindingName,
94
+ endpoint,
95
+ soapVersion,
96
+ operation
97
+ }));
98
+ }
99
+ }
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.error('Error parsing WSDL:', error);
104
+ }
105
+ return services;
106
+ }
107
+ generateOutputFilename(baseFilename) {
108
+ if (!fs.existsSync(baseFilename)) {
109
+ return baseFilename;
110
+ }
111
+ const ext = path.extname(baseFilename);
112
+ const nameWithoutExt = path.basename(baseFilename, ext);
113
+ const dir = path.dirname(baseFilename);
114
+ let counter = 1;
115
+ let newFilename;
116
+ do {
117
+ newFilename = path.join(dir, `${nameWithoutExt}_${counter}${ext}`);
118
+ counter++;
119
+ } while (fs.existsSync(newFilename));
120
+ return newFilename;
121
+ }
122
+ generateScenarios(selectedServices) {
123
+ return selectedServices.map(service => {
124
+ const operationName = service.name.split('.').pop() || 'Unknown';
125
+ const args = this.generateArgsFromSchema(service.requestBody?.schema);
126
+ const soapVersion = service.tags?.find(tag => tag.startsWith('SOAP-'))?.replace('SOAP-', '') || '1.1';
127
+ return {
128
+ name: `test_${service.name.toLowerCase().replace(/\./g, '_')}`,
129
+ steps: [{
130
+ name: service.name,
131
+ type: 'soap',
132
+ operation: operationName,
133
+ args: args,
134
+ body: service.requestBody?.example || this.generateSOAPExample(operationName, service.requestBody?.schema, soapVersion),
135
+ extract: this.generateSOAPExtractions(operationName)
136
+ }]
137
+ };
138
+ });
139
+ }
140
+ getWsdlUrl(selectedServices) {
141
+ return selectedServices.length > 0 ? this.extractWsdlUrl(selectedServices[0]) : '';
142
+ }
143
+ // Private helper methods
144
+ extractServiceElements() {
145
+ const serviceRegex = /<wsdl:service[^>]*name="([^"]*)"[^>]*>([\s\S]*?)<\/wsdl:service>/g;
146
+ const services = [];
147
+ let serviceMatch;
148
+ while ((serviceMatch = serviceRegex.exec(this.wsdlContent)) !== null) {
149
+ services.push({
150
+ name: serviceMatch[1],
151
+ content: serviceMatch[2]
152
+ });
153
+ }
154
+ return services;
155
+ }
156
+ extractPortElements(serviceContent) {
157
+ const portRegex = /<wsdl:port[^>]*name="([^"]*)"[^>]*>([\s\S]*?)<\/wsdl:port>/g;
158
+ const ports = [];
159
+ let portMatch;
160
+ while ((portMatch = portRegex.exec(serviceContent)) !== null) {
161
+ ports.push({
162
+ name: portMatch[1],
163
+ content: portMatch[0] // Include the full port element
164
+ });
165
+ }
166
+ return ports;
167
+ }
168
+ extractEndpointFromPort(portContent) {
169
+ const soapAddressMatch = portContent.match(/location="([^"]*)"/);
170
+ return soapAddressMatch ? soapAddressMatch[1] : '/soap';
171
+ }
172
+ isBindingVersionSelected(bindingName) {
173
+ if (this.selectedSoapVersions.length === 0) {
174
+ return true;
175
+ }
176
+ const soapVersion = this.getSoapVersionFromBinding(bindingName);
177
+ return this.selectedSoapVersions.includes(soapVersion);
178
+ }
179
+ getSoapVersionFromBinding(bindingName) {
180
+ const bindingRegex = new RegExp(`<wsdl:binding[^>]*name="${bindingName}"[^>]*>([\\s\\S]*?)<\\/wsdl:binding>`, 'i');
181
+ const bindingMatch = this.wsdlContent.match(bindingRegex);
182
+ if (bindingMatch && bindingMatch[1].includes('soap12:binding')) {
183
+ return '1.2';
184
+ }
185
+ return '1.1';
186
+ }
187
+ extractOperationsFromBinding(bindingName) {
188
+ const bindingRegex = new RegExp(`<wsdl:binding[^>]*name="${bindingName}"[^>]*>([\\s\\S]*?)<\\/wsdl:binding>`, 'i');
189
+ const bindingMatch = this.wsdlContent.match(bindingRegex);
190
+ if (!bindingMatch)
191
+ return [];
192
+ const operations = [];
193
+ const operationRegex = /<wsdl:operation[^>]*name="([^"]*)"[^>]*>([\s\S]*?)<\/wsdl:operation>/g;
194
+ let operationMatch;
195
+ while ((operationMatch = operationRegex.exec(bindingMatch[1])) !== null) {
196
+ const operationName = operationMatch[1];
197
+ const operationContent = operationMatch[2];
198
+ const soapAction = this.extractSoapAction(operationContent);
199
+ const documentation = this.getOperationDocumentation(operationName);
200
+ const inputSchema = this.parseSchemaElement(operationName);
201
+ const outputSchema = this.parseSchemaElement(`${operationName}Response`);
202
+ operations.push({
203
+ name: operationName,
204
+ soapAction,
205
+ documentation,
206
+ inputSchema,
207
+ outputSchema
208
+ });
209
+ }
210
+ return operations;
211
+ }
212
+ extractSoapAction(operationContent) {
213
+ const soapActionMatch = operationContent.match(/soapAction="([^"]*)"/);
214
+ return soapActionMatch ? soapActionMatch[1] : '';
215
+ }
216
+ getOperationDocumentation(operationName) {
217
+ const operationRegex = new RegExp(`<wsdl:operation[^>]*name="${operationName}"[^>]*>([\\s\\S]*?)<\\/wsdl:operation>`, 'i');
218
+ const operationMatch = this.wsdlContent.match(operationRegex);
219
+ if (operationMatch) {
220
+ const docMatch = operationMatch[1].match(/<wsdl:documentation[^>]*>([\s\S]*?)<\/wsdl:documentation>/i);
221
+ return docMatch ? docMatch[1].trim() : undefined;
222
+ }
223
+ return undefined;
224
+ }
225
+ parseSchemaElement(elementName) {
226
+ const elementRegex = new RegExp(`<s:element[^>]*name="${elementName}"[^>]*>([\\s\\S]*?)<\\/s:element>`, 'i');
227
+ const elementMatch = this.wsdlContent.match(elementRegex);
228
+ if (!elementMatch)
229
+ return { type: 'object', properties: {} };
230
+ const sequenceMatch = elementMatch[1].match(/<s:sequence[^>]*>([\s\S]*?)<\/s:sequence>/i);
231
+ if (!sequenceMatch)
232
+ return { type: 'object', properties: {} };
233
+ const fields = {};
234
+ const fieldRegex = /<s:element[^>]*name="([^"]*)"[^>]*type="([^"]*)"[^>]*minOccurs="([^"]*)"[^>]*\/?>|<s:element[^>]*name="([^"]*)"[^>]*type="([^"]*)"[^>]*>/g;
235
+ let fieldMatch;
236
+ while ((fieldMatch = fieldRegex.exec(sequenceMatch[1])) !== null) {
237
+ const name = fieldMatch[1] || fieldMatch[4];
238
+ const type = fieldMatch[2] || fieldMatch[5];
239
+ const minOccurs = fieldMatch[3];
240
+ if (name && type) {
241
+ fields[name] = {
242
+ type: this.mapXsdType(type),
243
+ required: minOccurs === '1',
244
+ description: `${name} parameter`
245
+ };
246
+ }
247
+ }
248
+ return { type: 'object', properties: fields };
249
+ }
250
+ mapXsdType(xsdType) {
251
+ const typeMap = {
252
+ 's:int': 'integer',
253
+ 's:string': 'string',
254
+ 's:double': 'number',
255
+ 's:float': 'number',
256
+ 's:boolean': 'boolean',
257
+ 's:dateTime': 'string'
258
+ };
259
+ return typeMap[xsdType] || 'string';
260
+ }
261
+ createImportableEndpoint(config) {
262
+ const { serviceName, bindingName, endpoint, soapVersion, operation } = config;
263
+ return {
264
+ id: `${serviceName}_${bindingName}_${operation.name}_${soapVersion}`,
265
+ name: `${serviceName}.${operation.name}`,
266
+ method: 'POST',
267
+ path: this.getPathFromUrl(endpoint),
268
+ description: `${operation.documentation || `${operation.name} operation`} (SOAP ${soapVersion})`,
269
+ requestBody: {
270
+ contentType: this.getContentType(soapVersion),
271
+ schema: operation.inputSchema,
272
+ example: this.generateSOAPExample(operation.name, operation.inputSchema, soapVersion),
273
+ required: true
274
+ },
275
+ responses: [{
276
+ statusCode: 200,
277
+ contentType: this.getResponseContentType(soapVersion),
278
+ schema: operation.outputSchema,
279
+ example: this.generateSOAPResponseExample(operation.name, operation.outputSchema, soapVersion)
280
+ }],
281
+ parameters: this.generateSOAPParameters(operation.soapAction, soapVersion),
282
+ tags: ['SOAP', serviceName, `SOAP-${soapVersion}`],
283
+ selected: false
284
+ };
285
+ }
286
+ getContentType(soapVersion) {
287
+ return soapVersion === '1.2'
288
+ ? 'application/soap+xml; charset=utf-8'
289
+ : 'text/xml; charset=utf-8';
290
+ }
291
+ getResponseContentType(soapVersion) {
292
+ return soapVersion === '1.2' ? 'application/soap+xml' : 'text/xml';
293
+ }
294
+ generateSOAPParameters(soapAction, soapVersion) {
295
+ if (soapVersion === '1.2') {
296
+ return [{
297
+ name: 'Content-Type',
298
+ in: 'header',
299
+ required: true,
300
+ type: 'string',
301
+ description: 'SOAP 1.2 Content Type with action',
302
+ example: `application/soap+xml; charset=utf-8; action="${soapAction}"`
303
+ }];
304
+ }
305
+ else {
306
+ return [{
307
+ name: 'SOAPAction',
308
+ in: 'header',
309
+ required: true,
310
+ type: 'string',
311
+ description: 'SOAP 1.1 Action header',
312
+ example: `"${soapAction}"`
313
+ }];
314
+ }
315
+ }
316
+ generateSOAPExample(operationName, schema, soapVersion) {
317
+ const targetNamespace = this.getTargetNamespace();
318
+ const params = this.generateSampleParameters(schema);
319
+ if (soapVersion === '1.2') {
320
+ return `<?xml version="1.0" encoding="utf-8"?>
321
+ <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
322
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
323
+ xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
324
+ <soap12:Body>
325
+ <${operationName} xmlns="${targetNamespace}">
326
+ ${params}
327
+ </${operationName}>
328
+ </soap12:Body>
329
+ </soap12:Envelope>`;
330
+ }
331
+ else {
332
+ return `<?xml version="1.0" encoding="utf-8"?>
333
+ <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
334
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
335
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
336
+ <soap:Body>
337
+ <${operationName} xmlns="${targetNamespace}">
338
+ ${params}
339
+ </${operationName}>
340
+ </soap:Body>
341
+ </soap:Envelope>`;
342
+ }
343
+ }
344
+ generateSOAPResponseExample(operationName, schema, soapVersion) {
345
+ const targetNamespace = this.getTargetNamespace();
346
+ const responseParams = this.generateSampleParameters(schema);
347
+ const responseElementName = `${operationName}Response`;
348
+ if (soapVersion === '1.2') {
349
+ return `<?xml version="1.0" encoding="utf-8"?>
350
+ <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
351
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
352
+ xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
353
+ <soap12:Body>
354
+ <${responseElementName} xmlns="${targetNamespace}">
355
+ ${responseParams}
356
+ </${responseElementName}>
357
+ </soap12:Body>
358
+ </soap12:Envelope>`;
359
+ }
360
+ else {
361
+ return `<?xml version="1.0" encoding="utf-8"?>
362
+ <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
363
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
364
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
365
+ <soap:Body>
366
+ <${responseElementName} xmlns="${targetNamespace}">
367
+ ${responseParams}
368
+ </${responseElementName}>
369
+ </soap:Body>
370
+ </soap:Envelope>`;
371
+ }
372
+ }
373
+ generateSampleParameters(schema) {
374
+ if (!schema?.properties)
375
+ return '';
376
+ return Object.entries(schema.properties)
377
+ .map(([name, prop]) => {
378
+ const sampleValue = this.getSampleValue(prop.type);
379
+ return `<${name}>${sampleValue}</${name}>`;
380
+ })
381
+ .join('\n ');
382
+ }
383
+ getSampleValue(type) {
384
+ switch (type) {
385
+ case 'integer': return 1;
386
+ case 'number': return 1.0;
387
+ case 'boolean': return 'true';
388
+ default: return 'sample';
389
+ }
390
+ }
391
+ generateArgsFromSchema(schema) {
392
+ if (!schema?.properties)
393
+ return {};
394
+ const args = {};
395
+ Object.entries(schema.properties).forEach(([name, prop]) => {
396
+ args[name] = this.getSampleValue(prop.type);
397
+ });
398
+ return args;
399
+ }
400
+ extractWsdlUrl(service) {
401
+ // Extract base URL from path and construct WSDL URL
402
+ const basePath = service.path.replace('.asmx', '');
403
+ return `http://www.dneonline.com${basePath}.asmx?wsdl`;
404
+ }
405
+ generateSOAPExtractions(operationName) {
406
+ return [
407
+ {
408
+ name: 'soap_fault',
409
+ type: 'regex',
410
+ expression: '<soap:Fault[^>]*>([\\s\\S]*?)</soap:Fault>',
411
+ optional: true
412
+ },
413
+ {
414
+ name: `${operationName.toLowerCase()}_result`,
415
+ type: 'regex',
416
+ expression: `<${operationName}Result[^>]*>([^<]*)</${operationName}Result>`
417
+ }
418
+ ];
419
+ }
420
+ getPathFromUrl(url) {
421
+ try {
422
+ const urlObj = new URL(url);
423
+ return urlObj.pathname;
424
+ }
425
+ catch {
426
+ const pathMatch = url.match(/^https?:\/\/[^\/]+(.*)$/);
427
+ return pathMatch ? pathMatch[1] : '/calculator.asmx';
428
+ }
429
+ }
430
+ getTargetNamespace() {
431
+ const namespaceMatch = this.wsdlContent.match(/targetNamespace="([^"]*)"/);
432
+ return namespaceMatch ? namespaceMatch[1] : 'http://tempuri.org/';
433
+ }
434
+ extractAttribute(xmlString, attributeName) {
435
+ const regex = new RegExp(`${attributeName}="([^"]*)"`, 'i');
436
+ const match = xmlString.match(regex);
437
+ return match ? match[1] : null;
438
+ }
439
+ }
440
+ exports.WSDLImporter = WSDLImporter;
@@ -0,0 +1,5 @@
1
+ export { test, testData, TestBuilder, ScenarioBuilder, LoadBuilder, load } from './dsl/test-builder';
2
+ export type { ScenarioContext } from './dsl/test-builder';
3
+ export { TestRunner } from './core/test-runner';
4
+ export { ConfigParser } from './config/parser';
5
+ export type { TestConfiguration, Scenario, Step, VUContext } from './config/types';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ // src/index.ts (builds to dist/index.js)
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ConfigParser = exports.TestRunner = exports.load = exports.LoadBuilder = exports.ScenarioBuilder = exports.TestBuilder = exports.testData = exports.test = void 0;
5
+ // Export everything from test-builder
6
+ var test_builder_1 = require("./dsl/test-builder");
7
+ Object.defineProperty(exports, "test", { enumerable: true, get: function () { return test_builder_1.test; } });
8
+ Object.defineProperty(exports, "testData", { enumerable: true, get: function () { return test_builder_1.testData; } });
9
+ Object.defineProperty(exports, "TestBuilder", { enumerable: true, get: function () { return test_builder_1.TestBuilder; } });
10
+ Object.defineProperty(exports, "ScenarioBuilder", { enumerable: true, get: function () { return test_builder_1.ScenarioBuilder; } });
11
+ Object.defineProperty(exports, "LoadBuilder", { enumerable: true, get: function () { return test_builder_1.LoadBuilder; } });
12
+ Object.defineProperty(exports, "load", { enumerable: true, get: function () { return test_builder_1.load; } });
13
+ // Export TestRunner and ConfigParser
14
+ var test_runner_1 = require("./core/test-runner");
15
+ Object.defineProperty(exports, "TestRunner", { enumerable: true, get: function () { return test_runner_1.TestRunner; } });
16
+ var parser_1 = require("./config/parser");
17
+ Object.defineProperty(exports, "ConfigParser", { enumerable: true, get: function () { return parser_1.ConfigParser; } });
@@ -0,0 +1,7 @@
1
+ import { LoadPattern, VUFactory } from './base';
2
+ export declare class ArrivalsPattern implements LoadPattern {
3
+ execute(config: any, vuFactory: VUFactory): Promise<void>;
4
+ private createAndRunVU;
5
+ private createVU;
6
+ private runVUForDuration;
7
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ArrivalsPattern = void 0;
4
+ const time_1 = require("../utils/time");
5
+ const logger_1 = require("../utils/logger");
6
+ class ArrivalsPattern {
7
+ async execute(config, vuFactory) {
8
+ const rate = config.rate; // users per second
9
+ const duration = (0, time_1.parseTime)(config.duration || '5m');
10
+ const rampUp = (0, time_1.parseTime)(config.ramp_up || '0s');
11
+ const vuDuration = (0, time_1.parseTime)(config.vu_duration || '30s'); // How long each VU runs
12
+ if (!rate || rate <= 0) {
13
+ throw new Error('Arrivals pattern requires a positive rate');
14
+ }
15
+ logger_1.logger.info(`đŸŽ¯ Arrivals: ${rate} users/sec, test duration: ${(duration / 1000).toFixed(1)}s, VU duration: ${(vuDuration / 1000).toFixed(1)}s`);
16
+ const targetIntervalMs = 1000 / rate;
17
+ let vuId = 0;
18
+ let currentRate = 0;
19
+ const testStartTime = Date.now();
20
+ const testEndTime = testStartTime + duration;
21
+ // Gradually ramp up to target rate
22
+ if (rampUp > 0) {
23
+ const rampSteps = Math.ceil(rampUp / 1000); // 1 second intervals
24
+ const rateIncrement = rate / rampSteps;
25
+ for (let step = 1; step <= rampSteps && Date.now() < testEndTime; step++) {
26
+ currentRate = Math.min(rate, step * rateIncrement);
27
+ const stepInterval = 1000 / currentRate;
28
+ logger_1.logger.debug(`📈 Ramp-up step ${step}/${rampSteps}: ${currentRate.toFixed(2)} users/sec`);
29
+ const stepEndTime = Math.min(Date.now() + 1000, testEndTime);
30
+ while (Date.now() < stepEndTime) {
31
+ const vuStartTime = Date.now();
32
+ // CRITICAL: Create and run VU but don't block the arrivals rate
33
+ this.createAndRunVU(vuFactory, ++vuId, vuDuration, vuStartTime);
34
+ await (0, time_1.sleep)(stepInterval);
35
+ if (Date.now() >= testEndTime) {
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ }
41
+ // Run at target rate for remaining duration
42
+ const remainingTestTime = testEndTime - Date.now();
43
+ if (remainingTestTime > 0) {
44
+ logger_1.logger.debug(`đŸŽ¯ Running at target rate: ${rate} users/sec for ${(remainingTestTime / 1000).toFixed(1)}s`);
45
+ while (Date.now() < testEndTime) {
46
+ const vuStartTime = Date.now();
47
+ // CRITICAL: Create and run VU but don't block the arrivals rate
48
+ this.createAndRunVU(vuFactory, ++vuId, vuDuration, vuStartTime);
49
+ await (0, time_1.sleep)(targetIntervalMs);
50
+ }
51
+ }
52
+ // Wait for the last VUs to complete (they might run beyond test end time)
53
+ const lastVUEndTime = Date.now() + vuDuration;
54
+ const finalWaitTime = Math.max(lastVUEndTime - Date.now(), 0);
55
+ if (finalWaitTime > 0) {
56
+ logger_1.logger.debug(`âŗ Waiting ${(finalWaitTime / 1000).toFixed(1)}s for last VUs to complete...`);
57
+ await (0, time_1.sleep)(finalWaitTime);
58
+ }
59
+ logger_1.logger.debug('✅ Arrivals pattern completed');
60
+ }
61
+ async createAndRunVU(vuFactory, vuId, durationMs, startTime) {
62
+ // Run VU creation and execution asynchronously to not block arrivals rate
63
+ (async () => {
64
+ try {
65
+ logger_1.logger.debug(`Creating VU ${vuId}...`);
66
+ const vu = await this.createVU(vuFactory, vuId);
67
+ logger_1.logger.debug(`VU ${vuId} ready`);
68
+ // Record VU start for metrics and reporting
69
+ const metrics = vuFactory.getMetrics();
70
+ metrics.recordVUStart(vu.getId());
71
+ logger_1.logger.debug(`👤 Started VU ${vu.getId()}`);
72
+ // Run VU for its individual duration
73
+ await this.runVUForDuration(vu, durationMs, startTime);
74
+ }
75
+ catch (error) {
76
+ const errorMessage = error instanceof Error ? error.message : String(error);
77
+ if (errorMessage.includes('terminated due to CSV data exhaustion')) {
78
+ logger_1.logger.info(`âšī¸ VU ${vuId} terminated due to CSV data exhaustion`);
79
+ }
80
+ else {
81
+ logger_1.logger.error(`❌ Failed to create/run VU ${vuId}:`, error);
82
+ }
83
+ }
84
+ })();
85
+ }
86
+ async createVU(vuFactory, id) {
87
+ // Handle both sync and async factories
88
+ const vu = vuFactory.create(id);
89
+ if (vu instanceof Promise) {
90
+ return await vu;
91
+ }
92
+ return vu;
93
+ }
94
+ async runVUForDuration(vu, durationMs, startTime) {
95
+ const endTime = startTime + durationMs;
96
+ while (Date.now() < endTime && vu.isRunning()) {
97
+ try {
98
+ await vu.executeScenarios();
99
+ if (Date.now() >= endTime) {
100
+ break;
101
+ }
102
+ await (0, time_1.sleep)(100); // Small pause between iterations
103
+ }
104
+ catch (error) {
105
+ const errorMessage = error instanceof Error ? error.message : String(error);
106
+ if (errorMessage.includes('terminated due to CSV data exhaustion')) {
107
+ logger_1.logger.info(`âšī¸ VU ${vu.getId()} terminated due to CSV data exhaustion`);
108
+ break; // Exit the loop, VU is already stopped
109
+ }
110
+ logger_1.logger.error(`❌ VU ${vu.getId()} error:`, error);
111
+ break;
112
+ }
113
+ }
114
+ // VU cleanup happens automatically when it goes out of scope
115
+ logger_1.logger.debug(`🏁 VU ${vu.getId()} completed after ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
116
+ }
117
+ }
118
+ exports.ArrivalsPattern = ArrivalsPattern;
@@ -0,0 +1,9 @@
1
+ import { VirtualUser } from '../core';
2
+ import { MetricsCollector } from '../metrics/collector';
3
+ export interface LoadPattern {
4
+ execute(config: any, vuFactory: VUFactory): Promise<void>;
5
+ }
6
+ export interface VUFactory {
7
+ create: (id: number) => VirtualUser | Promise<VirtualUser>;
8
+ getMetrics: () => MetricsCollector;
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ import { LoadPattern, VUFactory } from './base';
2
+ export declare class BasicPattern implements LoadPattern {
3
+ execute(config: any, vuFactory: VUFactory): Promise<void>;
4
+ private createVU;
5
+ private runVUOnce;
6
+ private runVUForDuration;
7
+ }