@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,353 @@
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.StreamingJSONOutput = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const file_manager_1 = require("../utils/file-manager");
40
+ const logger_1 = require("../utils/logger");
41
+ class StreamingJSONOutput {
42
+ constructor(filePath, config) {
43
+ this.pendingResults = [];
44
+ this.totalResults = 0;
45
+ this.currentFileSize = 0;
46
+ this.rotationIndex = 0;
47
+ this.isFirstWrite = true;
48
+ this.testStartTime = Date.now();
49
+ this.DEFAULT_CONFIG = {
50
+ format: 'ndjson',
51
+ batchSize: 50,
52
+ flushInterval: 3000,
53
+ prettify: false,
54
+ includeMetadata: true,
55
+ rotateSize: 100 * 1024 * 1024, // 100MB
56
+ maxFiles: 3
57
+ };
58
+ this.filePath = filePath;
59
+ this.config = { ...this.DEFAULT_CONFIG, ...config };
60
+ }
61
+ async initialize() {
62
+ // Process template in file path
63
+ this.filePath = file_manager_1.FileManager.processFilePath(this.filePath);
64
+ // Ensure directory exists
65
+ const dir = path.dirname(this.filePath);
66
+ if (!fs.existsSync(dir)) {
67
+ fs.mkdirSync(dir, { recursive: true });
68
+ }
69
+ // Create write stream
70
+ this.fileStream = fs.createWriteStream(this.filePath, { flags: 'w' });
71
+ // Write file header based on format
72
+ await this.writeHeader();
73
+ // Start flush timer
74
+ if (this.config.flushInterval > 0) {
75
+ this.flushTimer = setInterval(() => {
76
+ this.flushPending();
77
+ }, this.config.flushInterval);
78
+ }
79
+ logger_1.logger.info(`📊 Streaming JSON output initialized: ${this.filePath}`);
80
+ logger_1.logger.debug(`📊 Format: ${this.config.format}, batch: ${this.config.batchSize}`);
81
+ }
82
+ async writeHeader() {
83
+ if (!this.fileStream)
84
+ return;
85
+ switch (this.config.format) {
86
+ case 'json-array':
87
+ if (this.config.includeMetadata) {
88
+ const metadata = {
89
+ _metadata: {
90
+ format: 'perfornium-results',
91
+ version: '1.0',
92
+ test_start: new Date(this.testStartTime).toISOString(),
93
+ generator: 'perfornium-streaming-json'
94
+ },
95
+ results: []
96
+ };
97
+ this.fileStream.write('{\n');
98
+ this.fileStream.write(` "_metadata": ${JSON.stringify(metadata._metadata, null, 2).replace(/\n/g, '\n ')},\n`);
99
+ this.fileStream.write(' "results": [\n');
100
+ }
101
+ else {
102
+ this.fileStream.write('[\n');
103
+ }
104
+ break;
105
+ case 'json-stream':
106
+ if (this.config.includeMetadata) {
107
+ const metadata = {
108
+ type: 'metadata',
109
+ format: 'perfornium-results',
110
+ version: '1.0',
111
+ test_start: new Date(this.testStartTime).toISOString(),
112
+ generator: 'perfornium-streaming-json'
113
+ };
114
+ this.fileStream.write(JSON.stringify(metadata) + '\n');
115
+ }
116
+ break;
117
+ case 'ndjson':
118
+ default:
119
+ // NDJSON doesn't need a header, but we can add metadata as first line
120
+ if (this.config.includeMetadata) {
121
+ const metadata = {
122
+ _type: 'metadata',
123
+ format: 'perfornium-results',
124
+ version: '1.0',
125
+ test_start: new Date(this.testStartTime).toISOString(),
126
+ generator: 'perfornium-streaming-json'
127
+ };
128
+ this.fileStream.write(JSON.stringify(metadata) + '\n');
129
+ }
130
+ break;
131
+ }
132
+ this.currentFileSize = this.fileStream.bytesWritten || 0;
133
+ }
134
+ async writeResult(result) {
135
+ // Add processed timestamp and normalize data
136
+ const processedResult = {
137
+ ...result,
138
+ timestamp: result.timestamp || Date.now(),
139
+ response_time: result.response_time || result.duration || 0,
140
+ name: result.name || result.action || 'request',
141
+ iteration: result.iteration || 0
142
+ };
143
+ this.pendingResults.push(processedResult);
144
+ this.totalResults++;
145
+ if (this.pendingResults.length >= this.config.batchSize) {
146
+ await this.flushPending();
147
+ }
148
+ }
149
+ async flushPending() {
150
+ if (this.pendingResults.length === 0 || !this.fileStream)
151
+ return;
152
+ try {
153
+ // Check if we need to rotate the file
154
+ if (this.config.rotateSize > 0 && this.currentFileSize > this.config.rotateSize) {
155
+ await this.rotateFile();
156
+ }
157
+ const recordsToWrite = [...this.pendingResults];
158
+ this.pendingResults = [];
159
+ // Write records based on format
160
+ await this.writeRecords(recordsToWrite);
161
+ this.currentFileSize = this.fileStream.bytesWritten || 0;
162
+ logger_1.logger.debug(`📊 Wrote ${recordsToWrite.length} JSON results (total: ${this.totalResults})`);
163
+ }
164
+ catch (error) {
165
+ logger_1.logger.error('❌ Failed to write JSON batch:', error);
166
+ // Re-add results to pending for retry
167
+ this.pendingResults.unshift(...this.pendingResults);
168
+ }
169
+ }
170
+ async writeRecords(records) {
171
+ if (!this.fileStream)
172
+ return;
173
+ switch (this.config.format) {
174
+ case 'json-array':
175
+ for (let i = 0; i < records.length; i++) {
176
+ const record = records[i];
177
+ const isLast = i === records.length - 1;
178
+ const json = this.config.prettify
179
+ ? JSON.stringify(record, null, 2).replace(/\n/g, '\n ')
180
+ : JSON.stringify(record);
181
+ if (!this.isFirstWrite) {
182
+ this.fileStream.write(',\n');
183
+ }
184
+ this.fileStream.write(` ${json}`);
185
+ this.isFirstWrite = false;
186
+ }
187
+ break;
188
+ case 'json-stream':
189
+ for (const record of records) {
190
+ const streamRecord = {
191
+ type: 'result',
192
+ timestamp: Date.now(),
193
+ data: record
194
+ };
195
+ const json = this.config.prettify
196
+ ? JSON.stringify(streamRecord, null, 2)
197
+ : JSON.stringify(streamRecord);
198
+ this.fileStream.write(json + '\n');
199
+ }
200
+ break;
201
+ case 'ndjson':
202
+ default:
203
+ for (const record of records) {
204
+ const json = this.config.prettify
205
+ ? JSON.stringify(record, null, 2)
206
+ : JSON.stringify(record);
207
+ this.fileStream.write(json + '\n');
208
+ }
209
+ break;
210
+ }
211
+ }
212
+ async rotateFile() {
213
+ try {
214
+ // Close current file properly
215
+ await this.closeCurrentFile();
216
+ // Create rotated filename
217
+ const ext = path.extname(this.filePath);
218
+ const base = this.filePath.slice(0, -ext.length);
219
+ const rotatedPath = `${base}.${this.rotationIndex}${ext}`;
220
+ // Rename current file
221
+ if (fs.existsSync(this.filePath)) {
222
+ fs.renameSync(this.filePath, rotatedPath);
223
+ }
224
+ // Clean up old files
225
+ this.cleanupOldFiles(base, ext);
226
+ // Reset state
227
+ this.rotationIndex++;
228
+ this.currentFileSize = 0;
229
+ this.isFirstWrite = true;
230
+ // Reinitialize for new file
231
+ await this.initialize();
232
+ logger_1.logger.info(`🔄 Rotated JSON file: ${this.filePath}`);
233
+ }
234
+ catch (error) {
235
+ logger_1.logger.error('❌ Failed to rotate JSON file:', error);
236
+ }
237
+ }
238
+ async closeCurrentFile() {
239
+ if (!this.fileStream)
240
+ return;
241
+ // Write file footer based on format
242
+ switch (this.config.format) {
243
+ case 'json-array':
244
+ this.fileStream.write('\n ]');
245
+ if (this.config.includeMetadata) {
246
+ const endMetadata = {
247
+ test_end: new Date().toISOString(),
248
+ total_results: this.totalResults,
249
+ duration_ms: Date.now() - this.testStartTime
250
+ };
251
+ this.fileStream.write(',\n "_summary": ' + JSON.stringify(endMetadata, null, 2).replace(/\n/g, '\n '));
252
+ }
253
+ this.fileStream.write('\n}\n');
254
+ break;
255
+ case 'json-stream':
256
+ if (this.config.includeMetadata) {
257
+ const endMetadata = {
258
+ type: 'summary',
259
+ test_end: new Date().toISOString(),
260
+ total_results: this.totalResults,
261
+ duration_ms: Date.now() - this.testStartTime
262
+ };
263
+ this.fileStream.write(JSON.stringify(endMetadata) + '\n');
264
+ }
265
+ break;
266
+ case 'ndjson':
267
+ default:
268
+ if (this.config.includeMetadata) {
269
+ const endMetadata = {
270
+ _type: 'summary',
271
+ test_end: new Date().toISOString(),
272
+ total_results: this.totalResults,
273
+ duration_ms: Date.now() - this.testStartTime
274
+ };
275
+ this.fileStream.write(JSON.stringify(endMetadata) + '\n');
276
+ }
277
+ break;
278
+ }
279
+ return new Promise((resolve) => {
280
+ this.fileStream.end(() => {
281
+ this.fileStream = undefined;
282
+ resolve();
283
+ });
284
+ });
285
+ }
286
+ cleanupOldFiles(basePath, extension) {
287
+ try {
288
+ const dir = path.dirname(basePath);
289
+ const basename = path.basename(basePath);
290
+ const files = fs.readdirSync(dir)
291
+ .filter(file => file.startsWith(basename) && file.endsWith(extension))
292
+ .map(file => ({
293
+ name: file,
294
+ path: path.join(dir, file),
295
+ index: this.extractRotationIndex(file)
296
+ }))
297
+ .filter(file => file.index >= 0)
298
+ .sort((a, b) => b.index - a.index);
299
+ // Remove excess files
300
+ const filesToRemove = files.slice(this.config.maxFiles);
301
+ for (const file of filesToRemove) {
302
+ fs.unlinkSync(file.path);
303
+ logger_1.logger.debug(`🗑️ Removed old JSON file: ${file.name}`);
304
+ }
305
+ }
306
+ catch (error) {
307
+ logger_1.logger.warn('⚠️ Failed to cleanup old JSON files:', error);
308
+ }
309
+ }
310
+ extractRotationIndex(filename) {
311
+ const match = filename.match(/\.(\d+)\.json$/);
312
+ return match ? parseInt(match[1], 10) : -1;
313
+ }
314
+ async writeSummary(summary) {
315
+ // Write summary to separate file
316
+ const summaryPath = this.filePath.replace('.json', '_summary.json');
317
+ const summaryData = {
318
+ test_summary: {
319
+ timestamp: Date.now(),
320
+ test_end: new Date().toISOString(),
321
+ total_duration_ms: Date.now() - this.testStartTime,
322
+ ...summary
323
+ }
324
+ };
325
+ const json = this.config.prettify
326
+ ? JSON.stringify(summaryData, null, 2)
327
+ : JSON.stringify(summaryData);
328
+ fs.writeFileSync(summaryPath, json);
329
+ logger_1.logger.info(`📊 Written JSON summary: ${summaryPath}`);
330
+ }
331
+ async finalize() {
332
+ // Flush any remaining results
333
+ await this.flushPending();
334
+ // Clear flush timer
335
+ if (this.flushTimer) {
336
+ clearInterval(this.flushTimer);
337
+ this.flushTimer = undefined;
338
+ }
339
+ // Close file properly
340
+ await this.closeCurrentFile();
341
+ logger_1.logger.info(`📊 Streaming JSON finalized. Total results: ${this.totalResults}`);
342
+ }
343
+ getStats() {
344
+ return {
345
+ totalResults: this.totalResults,
346
+ pendingResults: this.pendingResults.length,
347
+ currentFileSize: this.currentFileSize,
348
+ rotationIndex: this.rotationIndex,
349
+ format: this.config.format || 'ndjson'
350
+ };
351
+ }
352
+ }
353
+ exports.StreamingJSONOutput = StreamingJSONOutput;
@@ -0,0 +1,16 @@
1
+ import { OutputHandler } from './base';
2
+ import { TestResult, MetricsSummary } from '../metrics/types';
3
+ export declare class WebhookOutput implements OutputHandler {
4
+ private url;
5
+ private headers;
6
+ private format;
7
+ private template?;
8
+ private templateProcessor;
9
+ private results;
10
+ constructor(url: string, headers?: Record<string, string>, format?: string, template?: string);
11
+ initialize(): Promise<void>;
12
+ writeResult(result: TestResult): Promise<void>;
13
+ writeSummary(summary: MetricsSummary): Promise<void>;
14
+ private sendNotification;
15
+ finalize(): Promise<void>;
16
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WebhookOutput = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const template_1 = require("../utils/template");
9
+ const logger_1 = require("../utils/logger");
10
+ class WebhookOutput {
11
+ constructor(url, headers = {}, format = 'json', template) {
12
+ this.templateProcessor = new template_1.TemplateProcessor();
13
+ this.results = [];
14
+ this.url = url;
15
+ this.headers = {
16
+ 'Content-Type': 'application/json',
17
+ 'User-Agent': 'Perfornium/1.0.0',
18
+ ...headers
19
+ };
20
+ this.format = format;
21
+ this.template = template;
22
+ }
23
+ async initialize() {
24
+ // Test webhook connectivity
25
+ try {
26
+ await axios_1.default.head(this.url, {
27
+ headers: this.headers,
28
+ timeout: 5000
29
+ });
30
+ logger_1.logger.debug('🔗 Webhook endpoint is reachable');
31
+ }
32
+ catch (error) {
33
+ logger_1.logger.warn('⚠️ Webhook endpoint may not be reachable:', error);
34
+ }
35
+ }
36
+ async writeResult(result) {
37
+ this.results.push(result);
38
+ // Optionally send real-time results for critical failures
39
+ if (!result.success && result.error && result.error.includes('timeout')) {
40
+ await this.sendNotification({
41
+ type: 'alert',
42
+ message: `Critical error in VU ${result.vu_id}: ${result.error}`,
43
+ timestamp: new Date().toISOString()
44
+ });
45
+ }
46
+ }
47
+ async writeSummary(summary) {
48
+ try {
49
+ let payload;
50
+ if (this.template) {
51
+ // Use template to format payload
52
+ const context = {
53
+ summary,
54
+ test_name: 'Performance Test',
55
+ success_rate: summary.success_rate.toFixed(2),
56
+ avg_response_time: summary.avg_response_time.toFixed(2),
57
+ total_requests: summary.total_requests,
58
+ total_duration: summary.total_duration
59
+ };
60
+ const processedTemplate = this.templateProcessor.process(this.template, context);
61
+ payload = JSON.parse(processedTemplate);
62
+ }
63
+ else {
64
+ // Default payload format
65
+ payload = {
66
+ type: 'test_completed',
67
+ timestamp: new Date().toISOString(),
68
+ summary: {
69
+ total_requests: summary.total_requests,
70
+ success_rate: `${summary.success_rate.toFixed(2)}%`,
71
+ avg_response_time: `${summary.avg_response_time.toFixed(2)}ms`,
72
+ requests_per_second: summary.requests_per_second.toFixed(2),
73
+ duration: `${(summary.total_duration / 1000).toFixed(1)}s`,
74
+ status: summary.success_rate >= 95 ? 'success' : summary.success_rate >= 90 ? 'warning' : 'error'
75
+ },
76
+ errors: Object.keys(summary.error_distribution).length > 0 ? summary.error_distribution : null
77
+ };
78
+ }
79
+ await this.sendNotification(payload);
80
+ logger_1.logger.debug('🔗 Webhook notification sent successfully');
81
+ }
82
+ catch (error) {
83
+ logger_1.logger.warn('⚠️ Failed to send webhook notification:', error);
84
+ }
85
+ }
86
+ async sendNotification(payload) {
87
+ await axios_1.default.post(this.url, payload, {
88
+ headers: this.headers,
89
+ timeout: 10000
90
+ });
91
+ }
92
+ async finalize() {
93
+ // Nothing to finalize for webhook
94
+ }
95
+ }
96
+ exports.WebhookOutput = WebhookOutput;
@@ -0,0 +1,33 @@
1
+ import { VUContext } from '../config/types';
2
+ export interface ProtocolResult {
3
+ success: boolean;
4
+ status?: number;
5
+ status_text?: string;
6
+ error?: string;
7
+ error_code?: string;
8
+ data?: any;
9
+ response_size?: number;
10
+ response_time?: number;
11
+ duration?: number;
12
+ shouldRecord?: boolean;
13
+ request_url?: string;
14
+ request_method?: string;
15
+ request_headers?: Record<string, string>;
16
+ request_body?: string;
17
+ response_headers?: Record<string, string>;
18
+ response_body?: string;
19
+ sample_start?: number;
20
+ connect_time?: number;
21
+ latency?: number;
22
+ sent_bytes?: number;
23
+ headers_size_sent?: number;
24
+ body_size_sent?: number;
25
+ headers_size_received?: number;
26
+ body_size_received?: number;
27
+ data_type?: 'text' | 'bin' | '';
28
+ custom_metrics?: Record<string, any>;
29
+ }
30
+ export interface ProtocolHandler {
31
+ execute(action: any, context: VUContext): Promise<ProtocolResult>;
32
+ cleanup?(): Promise<void>;
33
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,67 @@
1
+ import { ProtocolHandler, ProtocolResult } from '../base';
2
+ import { VUContext, RESTStep, DebugConfig } from '../../config/types';
3
+ export declare class RESTHandler implements ProtocolHandler {
4
+ private axiosInstance;
5
+ private debugConfig?;
6
+ private connectionTimings;
7
+ constructor(baseURL?: string, defaultHeaders?: Record<string, string>, timeout?: number, debugConfig?: DebugConfig);
8
+ execute(request: RESTStep, context: VUContext): Promise<ProtocolResult>;
9
+ private prepareRequestConfig;
10
+ /**
11
+ * Handle authentication configuration
12
+ */
13
+ private handleAuthentication;
14
+ /**
15
+ * Check if Content-Type header is already set (case-insensitive)
16
+ */
17
+ private hasContentTypeHeader;
18
+ /**
19
+ * Process body payload and detect content type
20
+ */
21
+ private processBodyPayload;
22
+ /**
23
+ * Detect if string is valid JSON
24
+ */
25
+ private isJsonString;
26
+ /**
27
+ * Detect if string is XML
28
+ */
29
+ private isXmlString;
30
+ /**
31
+ * Check if body contains template expressions
32
+ */
33
+ private isTemplateString;
34
+ /**
35
+ * Detect content type from template file extension or content
36
+ */
37
+ private detectTemplateContentType;
38
+ private createSuccessResult;
39
+ private handleError;
40
+ private addDetailedInfo;
41
+ private logRequestDetails;
42
+ private logResponseDetails;
43
+ private runChecksOptimized;
44
+ private buildURL;
45
+ private getResponseText;
46
+ private getJsonPathOptimized;
47
+ private flattenHeaders;
48
+ private truncateIfNeeded;
49
+ private shouldCaptureDetails;
50
+ /**
51
+ * Calculate request header and body sizes
52
+ */
53
+ private calculateRequestSizes;
54
+ /**
55
+ * Calculate response header and body sizes
56
+ */
57
+ private calculateResponseSizes;
58
+ /**
59
+ * Detect data type from response
60
+ */
61
+ private detectDataType;
62
+ /**
63
+ * Generate JMeter-style thread name
64
+ * Format: "iteration. step_name vu_id-iteration"
65
+ */
66
+ private generateThreadName;
67
+ }