@zohodesk/unit-testing-framework 0.0.30-experimental → 0.0.32-experimental

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.
@@ -0,0 +1,359 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PipelineSummaryGenerator = exports.ERROR_CATEGORIES = void 0;
7
+ exports.createPipelineSummary = createPipelineSummary;
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _logger = require("./logger.js");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ /**
13
+ * Error Categories
14
+ * - 'code': Issues in test/application code
15
+ * - 'environment': Environment setup, dependencies, configuration issues
16
+ * - 'flaky': Intermittent test failures
17
+ * - 'tool': Tool execution failures (jest, npm, etc.)
18
+ */
19
+ const ERROR_CATEGORIES = exports.ERROR_CATEGORIES = {
20
+ CODE: 'code',
21
+ ENVIRONMENT: 'environment',
22
+ FLAKY: 'flaky',
23
+ TOOL: 'tool'
24
+ };
25
+
26
+ /**
27
+ * Pipeline Summary Generator
28
+ * Creates standardized JSON responses for pipeline stages
29
+ */
30
+ class PipelineSummaryGenerator {
31
+ constructor(options = {}) {
32
+ this.stageName = options.stageName || 'unknown';
33
+ this.outputPath = options.outputPath || null;
34
+ this.webUrl = options.webUrl || null;
35
+ }
36
+
37
+ /**
38
+ * Create a SUCCESS response
39
+ * @param {Object} params - Response parameters
40
+ * @param {string} params.message - Custom message
41
+ * @param {string} params.webUrl - Optional URL override
42
+ * @returns {Object} Success response object
43
+ */
44
+ success(params = {}) {
45
+ const response = {
46
+ status: 'SUCCESS',
47
+ message: params.message || `${this.stageName} completed successfully`,
48
+ web_url: params.webUrl || this.webUrl || ''
49
+ };
50
+ this._logResponse(response);
51
+ return response;
52
+ }
53
+
54
+ /**
55
+ * Create a FAILURE response with error details
56
+ * @param {Object} params - Failure parameters
57
+ * @param {string} params.message - Custom message for the stage
58
+ * @param {string} params.errorCode - Custom error code
59
+ * @param {string} params.errorCategory - One of: 'code', 'environment', 'flaky', 'tool'
60
+ * @param {string} params.errorMessage - Detailed error message
61
+ * @param {Array} params.errors - List of detailed errors
62
+ * @param {Array} params.result - Optional result data
63
+ * @returns {Object} Failure response object
64
+ */
65
+ failure(params = {}) {
66
+ const {
67
+ message = `${this.stageName} failed`,
68
+ errorCode = 'UNKNOWN_ERROR',
69
+ errorCategory = ERROR_CATEGORIES.TOOL,
70
+ errorMessage = 'An unexpected error occurred',
71
+ errors = [],
72
+ result = []
73
+ } = params;
74
+ if (!this._isValidErrorCategory(errorCategory)) {
75
+ _logger.Logger.warn(`Invalid error category: ${errorCategory}. Using 'tool' as default.`);
76
+ }
77
+ const response = {
78
+ status: 'FAILURE',
79
+ message,
80
+ web_url: this.webUrl || '',
81
+ error: {
82
+ code: errorCode,
83
+ category: errorCategory,
84
+ message: errorMessage,
85
+ errors: Array.isArray(errors) ? errors : [errors]
86
+ }
87
+ };
88
+ if (Array.isArray(result) && result.length > 0) {
89
+ response.result = result;
90
+ }
91
+ this._logResponse(response);
92
+ return response;
93
+ }
94
+
95
+ /**
96
+ * Handle test failures
97
+ * @param {Object} params - Test failure parameters
98
+ * @param {Object} params.testResults - Jest test results object
99
+ * @param {string} params.message - Custom message
100
+ * @returns {Object} Failure response object
101
+ */
102
+ testFailure(params = {}) {
103
+ const {
104
+ testResults,
105
+ message
106
+ } = params;
107
+ if (!testResults) {
108
+ return this.failure({
109
+ errorCode: 'TEST_FAILURE',
110
+ errorCategory: ERROR_CATEGORIES.CODE,
111
+ errorMessage: message || 'Tests failed'
112
+ });
113
+ }
114
+ const errors = [];
115
+ const failedTests = testResults.testResults || [];
116
+ for (const testFile of failedTests) {
117
+ if (testFile.numFailingTests > 0) {
118
+ for (const assertion of testFile.assertionResults || []) {
119
+ if (assertion.status === 'failed') {
120
+ errors.push({
121
+ test: assertion.fullName || assertion.title,
122
+ file: testFile.name,
123
+ message: assertion.failureMessages?.[0] || 'Test assertion failed'
124
+ });
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return this.failure({
130
+ message: message || `${testResults.numFailedTests} test(s) failed out of ${testResults.numTotalTests}`,
131
+ errorCode: 'TEST_FAILURE',
132
+ errorCategory: ERROR_CATEGORIES.CODE,
133
+ errorMessage: `${testResults.numFailedTests} test assertion(s) failed`,
134
+ errors: errors.length > 0 ? errors : ['Test failures detected'],
135
+ result: [{
136
+ totalTests: testResults.numTotalTests,
137
+ failedTests: testResults.numFailedTests,
138
+ passedTests: testResults.numPassedTests,
139
+ skippedTests: testResults.numSkippedTests
140
+ }]
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Handle tool execution errors
146
+ * @param {Object} params - Tool error parameters
147
+ * @param {string} params.toolName - Name of the tool (jest, npm, etc.)
148
+ * @param {Error|string} params.error - Error object or message
149
+ * @param {string} params.exitCode - Exit code from tool
150
+ * @returns {Object} Failure response object
151
+ */
152
+ toolError(params = {}) {
153
+ const {
154
+ toolName = 'unknown',
155
+ error,
156
+ exitCode
157
+ } = params;
158
+ const errorMessage = error instanceof Error ? error.message : String(error);
159
+ const errorStack = error instanceof Error ? error.stack : null;
160
+ const errors = [{
161
+ tool: toolName,
162
+ message: errorMessage,
163
+ exitCode: exitCode || 'unknown'
164
+ }];
165
+ if (errorStack) {
166
+ errors[0].stack = errorStack;
167
+ }
168
+ return this.failure({
169
+ message: `${toolName} execution failed`,
170
+ errorCode: `${toolName.toUpperCase()}_EXECUTION_ERROR`,
171
+ errorCategory: ERROR_CATEGORIES.TOOL,
172
+ errorMessage: `Failed to execute ${toolName}: ${errorMessage}`,
173
+ errors
174
+ });
175
+ }
176
+
177
+ /**
178
+ * Handle environment errors
179
+ * @param {Object} params - Environment error parameters
180
+ * @param {string} params.issue - Description of the environment issue
181
+ * @param {string} params.requirement - What was required
182
+ * @param {string} params.suggestion - Suggested fix
183
+ * @returns {Object} Failure response object
184
+ */
185
+ environmentError(params = {}) {
186
+ const {
187
+ issue = 'Environment configuration issue',
188
+ requirement = null,
189
+ suggestion = null
190
+ } = params;
191
+ const errors = [{
192
+ issue,
193
+ ...(requirement && {
194
+ requirement
195
+ }),
196
+ ...(suggestion && {
197
+ suggestion
198
+ })
199
+ }];
200
+ return this.failure({
201
+ message: 'Environment setup failed',
202
+ errorCode: 'ENV_SETUP_ERROR',
203
+ errorCategory: ERROR_CATEGORIES.ENVIRONMENT,
204
+ errorMessage: issue,
205
+ errors
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Handle flaky test failures
211
+ * @param {Object} params - Flaky test parameters
212
+ * @param {Array} params.testNames - Names of flaky tests
213
+ * @param {number} params.attemptNumber - Current attempt number
214
+ * @param {number} params.maxAttempts - Max retry attempts
215
+ * @returns {Object} Failure response object
216
+ */
217
+ flakyTestFailure(params = {}) {
218
+ const {
219
+ testNames = [],
220
+ attemptNumber = 1,
221
+ maxAttempts = 3,
222
+ message
223
+ } = params;
224
+ return this.failure({
225
+ message: message || `Flaky test failure (attempt ${attemptNumber}/${maxAttempts})`,
226
+ errorCode: 'FLAKY_TEST_FAILURE',
227
+ errorCategory: ERROR_CATEGORIES.FLAKY,
228
+ errorMessage: `${testNames.length} test(s) failed intermittently`,
229
+ errors: testNames.map(name => ({
230
+ test: name,
231
+ type: 'flaky',
232
+ attemptNumber
233
+ })),
234
+ result: [{
235
+ attemptNumber,
236
+ maxAttempts,
237
+ flakyTestCount: testNames.length
238
+ }]
239
+ });
240
+ }
241
+
242
+ /**
243
+ * Save response to JSON file
244
+ * @param {Object} response - Response object to save
245
+ * @param {string} outputPath - Optional path override
246
+ * @returns {boolean} Success status
247
+ */
248
+ saveToFile(response, outputPath = null) {
249
+ const filePath = outputPath || this.outputPath;
250
+ if (!filePath) {
251
+ _logger.Logger.warn('No output path specified for pipeline summary');
252
+ return false;
253
+ }
254
+ try {
255
+ const dir = _path.default.dirname(filePath);
256
+
257
+ // ── Create directory if it doesn't exist ──────────────────────
258
+ if (!_fs.default.existsSync(dir)) {
259
+ try {
260
+ _fs.default.mkdirSync(dir, {
261
+ recursive: true
262
+ });
263
+ _logger.Logger.info(`Created directory: ${dir}`);
264
+ } catch (mkdirErr) {
265
+ if (mkdirErr.code === 'EACCES') {
266
+ _logger.Logger.error(`Permission denied creating directory: ${dir}. ` + `Check folder permissions.`);
267
+ } else if (mkdirErr.code === 'EROFS') {
268
+ _logger.Logger.error(`Read-only file system: ${dir}. ` + `Cannot create directory on read-only filesystem.`);
269
+ } else {
270
+ _logger.Logger.error(`Failed to create directory ${dir}: ${mkdirErr.message}`);
271
+ }
272
+ return false;
273
+ }
274
+ }
275
+
276
+ // ── Verify directory is writable ─────────────────────────────
277
+ try {
278
+ _fs.default.accessSync(dir, _fs.default.constants.W_OK);
279
+ } catch (accessErr) {
280
+ _logger.Logger.error(`Directory not writable: ${dir}. ` + `Check folder permissions.`);
281
+ return false;
282
+ }
283
+
284
+ // ── Write the summary file ───────────────────────────────────
285
+ const jsonContent = JSON.stringify(response, null, 2);
286
+ _fs.default.writeFileSync(filePath, jsonContent, 'utf-8');
287
+
288
+ // ── Verify file was written ──────────────────────────────────
289
+ if (!_fs.default.existsSync(filePath)) {
290
+ _logger.Logger.error(`File was not created: ${filePath}. ` + `Unknown file system error.`);
291
+ return false;
292
+ }
293
+ _logger.Logger.info(`✓ Pipeline summary saved to: ${filePath}`);
294
+ return true;
295
+ } catch (err) {
296
+ // ── Handle unexpected errors ─────────────────────────────────
297
+ if (err.code === 'ENOSPC') {
298
+ _logger.Logger.error(`No space left on device. ` + `Cannot save pipeline summary to: ${outputPath || this.outputPath}`);
299
+ } else if (err.code === 'ENOTDIR') {
300
+ _logger.Logger.error(`Invalid path: ${outputPath || this.outputPath}. ` + `A parent component is not a directory.`);
301
+ } else {
302
+ _logger.Logger.error(`Failed to save pipeline summary: ${err.message}. ` + `Path: ${outputPath || this.outputPath}`);
303
+ }
304
+ return false;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Save response and return both
310
+ * @param {Object} response - Response object
311
+ * @param {string} outputPath - Optional path override
312
+ * @returns {Object} { response, saved: boolean }
313
+ */
314
+ saveAndReturn(response, outputPath = null) {
315
+ const saved = this.saveToFile(response, outputPath);
316
+ return {
317
+ response,
318
+ saved
319
+ };
320
+ }
321
+
322
+ /**
323
+ * Validate error category
324
+ * @private
325
+ */
326
+ _isValidErrorCategory(category) {
327
+ return Object.values(ERROR_CATEGORIES).includes(category);
328
+ }
329
+
330
+ /**
331
+ * Log response for debugging
332
+ * @private
333
+ */
334
+ _logResponse(response) {
335
+ if (response.status === 'SUCCESS') {
336
+ _logger.Logger.info(`✓ Stage '${this.stageName}' completed: ${response.message}`);
337
+ } else {
338
+ _logger.Logger.error(`✗ Stage '${this.stageName}' failed: ${response.error.category} - ${response.error.code}`);
339
+ }
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Factory function for quick usage
345
+ * @param {string} stageName - Name of the pipeline stage
346
+ * @param {Object} options - Additional options
347
+ * @returns {PipelineSummaryGenerator}
348
+ */
349
+ exports.PipelineSummaryGenerator = PipelineSummaryGenerator;
350
+ function createPipelineSummary(stageName, options = {}) {
351
+ return new PipelineSummaryGenerator({
352
+ stageName,
353
+ ...options
354
+ });
355
+ }
356
+
357
+ /**
358
+ * Export error categories for external use
359
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zohodesk/unit-testing-framework",
3
- "version": "0.0.30-experimental",
3
+ "version": "0.0.32-experimental",
4
4
  "description": "A modular Jest-based unit testing framework",
5
5
  "main": "./build/index.js",
6
6
  "exports": {