@appliqation/automation-sdk 2.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.
- package/LICENSE +21 -0
- package/README.md +441 -0
- package/package.json +107 -0
- package/src/AppliqationClient.js +562 -0
- package/src/constants.js +245 -0
- package/src/core/AuthManager.js +353 -0
- package/src/core/HttpClient.js +475 -0
- package/src/index.d.ts +333 -0
- package/src/index.js +26 -0
- package/src/playwright/JwtBrowserAuth.js +240 -0
- package/src/playwright/fixture.js +92 -0
- package/src/playwright/global-setup.js +243 -0
- package/src/playwright/helpers/jwt-browser-auth.js +227 -0
- package/src/playwright/index.js +16 -0
- package/src/reporters/cypress/CypressReporter.js +387 -0
- package/src/reporters/cypress/UuidExtractor.js +139 -0
- package/src/reporters/cypress/index.js +30 -0
- package/src/reporters/jest/JestReporter.js +361 -0
- package/src/reporters/jest/UuidExtractor.js +174 -0
- package/src/reporters/jest/index.js +28 -0
- package/src/reporters/playwright/AppliqationReporter.js +654 -0
- package/src/reporters/playwright/helpers/DeviceOsDetector.js +435 -0
- package/src/reporters/playwright/helpers/UuidExtractor.js +290 -0
- package/src/reporters/playwright/index.d.ts +96 -0
- package/src/reporters/playwright/index.js +14 -0
- package/src/services/OrphanTestService.js +74 -0
- package/src/services/ResultService.js +252 -0
- package/src/services/RunMatrixService.js +309 -0
- package/src/utils/PayloadBuilder.js +280 -0
- package/src/utils/RunDataNormalizer.js +335 -0
- package/src/utils/UuidValidator.js +102 -0
- package/src/utils/errors.js +217 -0
- package/src/utils/index.js +17 -0
- package/src/utils/logger.js +124 -0
- package/src/utils/mapAppqUuid.js +83 -0
- package/src/utils/validator.js +157 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Error Handling
|
|
3
|
+
* Standardized error classes and error handling utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base SDK Error class
|
|
8
|
+
*/
|
|
9
|
+
class AppliqationError extends Error {
|
|
10
|
+
constructor(message, code, details = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = this.constructor.name;
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
Error.captureStackTrace(this, this.constructor);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toJSON() {
|
|
19
|
+
return {
|
|
20
|
+
name: this.name,
|
|
21
|
+
message: this.message,
|
|
22
|
+
code: this.code,
|
|
23
|
+
details: this.details
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Configuration Error
|
|
30
|
+
* Thrown when SDK configuration is invalid
|
|
31
|
+
*/
|
|
32
|
+
class ConfigurationError extends AppliqationError {
|
|
33
|
+
constructor(message, details = {}) {
|
|
34
|
+
super(message, 'CONFIGURATION_ERROR', details);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Authentication Error
|
|
40
|
+
* Thrown when authentication fails
|
|
41
|
+
*/
|
|
42
|
+
class AuthenticationError extends AppliqationError {
|
|
43
|
+
constructor(message, details = {}) {
|
|
44
|
+
super(message, 'AUTHENTICATION_ERROR', details);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Network Error
|
|
50
|
+
* Thrown when network requests fail
|
|
51
|
+
*/
|
|
52
|
+
class NetworkError extends AppliqationError {
|
|
53
|
+
constructor(message, details = {}) {
|
|
54
|
+
super(message, 'NETWORK_ERROR', details);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validation Error
|
|
60
|
+
* Thrown when data validation fails
|
|
61
|
+
*/
|
|
62
|
+
class ValidationError extends AppliqationError {
|
|
63
|
+
constructor(message, details = {}) {
|
|
64
|
+
super(message, 'VALIDATION_ERROR', details);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* API Error
|
|
70
|
+
* Thrown when API returns an error response
|
|
71
|
+
*/
|
|
72
|
+
class ApiError extends AppliqationError {
|
|
73
|
+
constructor(message, statusCode, details = {}) {
|
|
74
|
+
super(message, 'API_ERROR', { ...details, statusCode });
|
|
75
|
+
this.statusCode = statusCode;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Timeout Error
|
|
81
|
+
* Thrown when requests timeout
|
|
82
|
+
*/
|
|
83
|
+
class TimeoutError extends AppliqationError {
|
|
84
|
+
constructor(message, timeout, details = {}) {
|
|
85
|
+
super(message, 'TIMEOUT_ERROR', { ...details, timeout });
|
|
86
|
+
this.timeout = timeout;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Error Handler Utility
|
|
92
|
+
*/
|
|
93
|
+
class ErrorHandler {
|
|
94
|
+
/**
|
|
95
|
+
* Handle HTTP errors and convert to SDK errors
|
|
96
|
+
* @param {Error} error - Original error
|
|
97
|
+
* @param {string} context - Context where error occurred
|
|
98
|
+
* @returns {AppliqationError} Standardized error
|
|
99
|
+
*/
|
|
100
|
+
static handleHttpError(error, context = 'HTTP request') {
|
|
101
|
+
// Timeout errors
|
|
102
|
+
if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
|
|
103
|
+
return new TimeoutError(
|
|
104
|
+
`${context} timed out`,
|
|
105
|
+
error.config?.timeout,
|
|
106
|
+
{ originalError: error.message }
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Network errors (no response)
|
|
111
|
+
if (!error.response) {
|
|
112
|
+
return new NetworkError(
|
|
113
|
+
`${context} failed: ${error.message}`,
|
|
114
|
+
{
|
|
115
|
+
code: error.code,
|
|
116
|
+
syscall: error.syscall,
|
|
117
|
+
originalError: error.message
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// API errors (with response)
|
|
123
|
+
const { status, statusText, data } = error.response;
|
|
124
|
+
|
|
125
|
+
// Authentication errors
|
|
126
|
+
if (status === 401 || status === 403) {
|
|
127
|
+
return new AuthenticationError(
|
|
128
|
+
`Authentication failed: ${statusText}`,
|
|
129
|
+
{
|
|
130
|
+
status,
|
|
131
|
+
statusText,
|
|
132
|
+
response: data
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Validation errors
|
|
138
|
+
if (status === 400 || status === 422) {
|
|
139
|
+
return new ValidationError(
|
|
140
|
+
`Validation failed: ${data?.message || statusText}`,
|
|
141
|
+
{
|
|
142
|
+
status,
|
|
143
|
+
statusText,
|
|
144
|
+
response: data
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Generic API errors
|
|
150
|
+
return new ApiError(
|
|
151
|
+
`${context} failed: ${statusText}`,
|
|
152
|
+
status,
|
|
153
|
+
{
|
|
154
|
+
statusText,
|
|
155
|
+
response: data
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if error is retryable
|
|
162
|
+
* @param {Error} error - Error to check
|
|
163
|
+
* @returns {boolean} True if error is retryable
|
|
164
|
+
*/
|
|
165
|
+
static isRetryable(error) {
|
|
166
|
+
// Network errors are retryable
|
|
167
|
+
if (error instanceof NetworkError) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Timeout errors are retryable
|
|
172
|
+
if (error instanceof TimeoutError) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Certain API errors are retryable (5xx, 429, 408)
|
|
177
|
+
if (error instanceof ApiError) {
|
|
178
|
+
const { statusCode } = error;
|
|
179
|
+
return statusCode >= 500 || statusCode === 429 || statusCode === 408;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Format error for logging
|
|
187
|
+
* @param {Error} error - Error to format
|
|
188
|
+
* @returns {Object} Formatted error object
|
|
189
|
+
*/
|
|
190
|
+
static formatForLogging(error) {
|
|
191
|
+
if (error instanceof AppliqationError) {
|
|
192
|
+
return {
|
|
193
|
+
name: error.name,
|
|
194
|
+
code: error.code,
|
|
195
|
+
message: error.message,
|
|
196
|
+
details: error.details
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
name: error.name,
|
|
202
|
+
message: error.message,
|
|
203
|
+
stack: error.stack
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
AppliqationError,
|
|
210
|
+
ConfigurationError,
|
|
211
|
+
AuthenticationError,
|
|
212
|
+
NetworkError,
|
|
213
|
+
ValidationError,
|
|
214
|
+
ApiError,
|
|
215
|
+
TimeoutError,
|
|
216
|
+
ErrorHandler
|
|
217
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities export module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const UuidValidator = require('./UuidValidator');
|
|
6
|
+
const PayloadBuilder = require('./PayloadBuilder');
|
|
7
|
+
const RunDataNormalizer = require('./RunDataNormalizer');
|
|
8
|
+
const { mapAppqUuid } = require('./mapAppqUuid');
|
|
9
|
+
const logger = require('./logger');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
UuidValidator,
|
|
13
|
+
PayloadBuilder,
|
|
14
|
+
RunDataNormalizer,
|
|
15
|
+
mapAppqUuid,
|
|
16
|
+
logger
|
|
17
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const LOG_LEVELS = {
|
|
2
|
+
ERROR: 0,
|
|
3
|
+
WARN: 1,
|
|
4
|
+
INFO: 2,
|
|
5
|
+
DEBUG: 3
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Sensitive data patterns to sanitize
|
|
9
|
+
const SENSITIVE_PATTERNS = [
|
|
10
|
+
// API Keys
|
|
11
|
+
{ pattern: /(appq_\w+_[a-f0-9]{32})/gi, replacement: 'appq_***_REDACTED' },
|
|
12
|
+
{ pattern: /("apiKey"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' },
|
|
13
|
+
{ pattern: /("X-API-Key"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' },
|
|
14
|
+
|
|
15
|
+
// JWT Tokens
|
|
16
|
+
{ pattern: /(eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/=]+)/g, replacement: 'eyJ***REDACTED***' },
|
|
17
|
+
{ pattern: /("token"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' },
|
|
18
|
+
{ pattern: /("jwt"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' },
|
|
19
|
+
|
|
20
|
+
// Authorization Headers
|
|
21
|
+
{ pattern: /("Authorization"\s*:\s*")(Bearer\s+)?([^"]+)(")/gi, replacement: '$1$2***REDACTED***$4' },
|
|
22
|
+
{ pattern: /("authorization"\s*:\s*")(Bearer\s+)?([^"]+)(")/gi, replacement: '$1$2***REDACTED***$4' },
|
|
23
|
+
|
|
24
|
+
// Passwords
|
|
25
|
+
{ pattern: /("password"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' },
|
|
26
|
+
{ pattern: /("pass"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' },
|
|
27
|
+
|
|
28
|
+
// Session IDs
|
|
29
|
+
{ pattern: /("sessionId"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' },
|
|
30
|
+
{ pattern: /("session"\s*:\s*")([^"]+)(")/gi, replacement: '$1***REDACTED***$3' }
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
class Logger {
|
|
34
|
+
constructor(level = 'INFO') {
|
|
35
|
+
this.level = LOG_LEVELS[level.toUpperCase()] || LOG_LEVELS.INFO;
|
|
36
|
+
this.enableConsole = process.env.NODE_ENV !== 'test';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sanitize sensitive data from logs
|
|
41
|
+
* @param {any} data - Data to sanitize (string, object, or array)
|
|
42
|
+
* @returns {any} Sanitized data
|
|
43
|
+
*/
|
|
44
|
+
sanitize(data) {
|
|
45
|
+
if (data === null || data === undefined) {
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle strings
|
|
50
|
+
if (typeof data === 'string') {
|
|
51
|
+
let sanitized = data;
|
|
52
|
+
SENSITIVE_PATTERNS.forEach(({ pattern, replacement }) => {
|
|
53
|
+
sanitized = sanitized.replace(pattern, replacement);
|
|
54
|
+
});
|
|
55
|
+
return sanitized;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle objects and arrays by converting to JSON and back
|
|
59
|
+
if (typeof data === 'object') {
|
|
60
|
+
try {
|
|
61
|
+
let jsonString = JSON.stringify(data);
|
|
62
|
+
SENSITIVE_PATTERNS.forEach(({ pattern, replacement }) => {
|
|
63
|
+
jsonString = jsonString.replace(pattern, replacement);
|
|
64
|
+
});
|
|
65
|
+
return JSON.parse(jsonString);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// If JSON parsing fails, return original data
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
formatMessage(level, message, meta = {}) {
|
|
76
|
+
const timestamp = new Date().toISOString();
|
|
77
|
+
// Sanitize both message and meta
|
|
78
|
+
const sanitizedMessage = this.sanitize(message);
|
|
79
|
+
const sanitizedMeta = this.sanitize(meta);
|
|
80
|
+
const metaString = Object.keys(sanitizedMeta).length > 0 ? JSON.stringify(sanitizedMeta) : '';
|
|
81
|
+
return `[${timestamp}] [${level}] ${sanitizedMessage} ${metaString}`.trim();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
error(message, meta = {}) {
|
|
85
|
+
if (this.level >= LOG_LEVELS.ERROR) {
|
|
86
|
+
const formatted = this.formatMessage('ERROR', message, meta);
|
|
87
|
+
if (this.enableConsole) console.error(formatted);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
warn(message, meta = {}) {
|
|
92
|
+
if (this.level >= LOG_LEVELS.WARN) {
|
|
93
|
+
const formatted = this.formatMessage('WARN', message, meta);
|
|
94
|
+
if (this.enableConsole) console.warn(formatted);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
info(message, meta = {}) {
|
|
99
|
+
if (this.level >= LOG_LEVELS.INFO) {
|
|
100
|
+
const formatted = this.formatMessage('INFO', message, meta);
|
|
101
|
+
if (this.enableConsole) console.log(formatted);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
debug(message, meta = {}) {
|
|
106
|
+
if (this.level >= LOG_LEVELS.DEBUG) {
|
|
107
|
+
const formatted = this.formatMessage('DEBUG', message, meta);
|
|
108
|
+
if (this.enableConsole) console.debug(formatted);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setLevel(level) {
|
|
113
|
+
this.level = LOG_LEVELS[level.toUpperCase()] || LOG_LEVELS.INFO;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setConsoleEnabled(enabled) {
|
|
117
|
+
this.enableConsole = enabled;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Export singleton instance
|
|
122
|
+
const logger = new Logger(process.env.LOG_LEVEL || 'INFO');
|
|
123
|
+
|
|
124
|
+
module.exports = logger;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UUID Mapping Utility for Appliqation SDK
|
|
3
|
+
*
|
|
4
|
+
* This utility provides a simple, framework-agnostic way to map test UUIDs
|
|
5
|
+
* to framework-specific test metadata (currently supports Playwright).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Adds Appliqation UUID annotation to a Playwright test.
|
|
10
|
+
*
|
|
11
|
+
* This helper function keeps UUID mapping centralized and makes tests more
|
|
12
|
+
* framework-agnostic by abstracting the annotation mechanism.
|
|
13
|
+
*
|
|
14
|
+
* @param {import('@playwright/test').TestInfo} testInfo - Playwright test info object
|
|
15
|
+
* @param {string} uuid - Appliqation test UUID in format: nid-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
16
|
+
*
|
|
17
|
+
* @throws {Error} If testInfo is not provided or invalid
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // In your Playwright test file
|
|
21
|
+
* const { test } = require('@playwright/test');
|
|
22
|
+
* const { mapAppqUuid } = require('@appliqation/automation-sdk/utils');
|
|
23
|
+
*
|
|
24
|
+
* test('login functionality', async ({ page }, testInfo) => {
|
|
25
|
+
* // Map this test to Appliqation test case
|
|
26
|
+
* mapAppqUuid(testInfo, '1141-6d2827f3-1bc2-405c-95b4-6a09e8b64148');
|
|
27
|
+
*
|
|
28
|
+
* await page.goto('/login');
|
|
29
|
+
* // ... test implementation
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // UUID format examples
|
|
34
|
+
* mapAppqUuid(testInfo, '1141-6d2827f3-1bc2-405c-95b4-6a09e8b64148'); // Standard format
|
|
35
|
+
* mapAppqUuid(testInfo, '1234-abc12345-6789-def0-1234-567890abcdef'); // Another valid UUID
|
|
36
|
+
*/
|
|
37
|
+
function mapAppqUuid(testInfo, uuid) {
|
|
38
|
+
// Validate testInfo parameter
|
|
39
|
+
if (!testInfo) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'[Appliqation SDK] mapAppqUuid requires a valid testInfo object. ' +
|
|
42
|
+
'Make sure to pass the testInfo parameter from your test function.'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Validate testInfo has annotations array
|
|
47
|
+
if (!testInfo.annotations || !Array.isArray(testInfo.annotations)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
'[Appliqation SDK] testInfo.annotations is not available. ' +
|
|
50
|
+
'Ensure you are using a compatible version of Playwright (@playwright/test >= 1.30.0).'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Validate UUID parameter
|
|
55
|
+
if (!uuid || typeof uuid !== 'string') {
|
|
56
|
+
console.warn(
|
|
57
|
+
'[Appliqation SDK] mapAppqUuid called without a valid UUID. ' +
|
|
58
|
+
'Test results may not be properly linked to Appliqation test cases.'
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Trim whitespace from UUID
|
|
64
|
+
const trimmedUuid = uuid.trim();
|
|
65
|
+
|
|
66
|
+
// Basic UUID format validation (nid-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
|
|
67
|
+
const uuidPattern = /^\d+-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
68
|
+
if (!uuidPattern.test(trimmedUuid)) {
|
|
69
|
+
console.warn(
|
|
70
|
+
`[Appliqation SDK] UUID format appears invalid: "${trimmedUuid}". ` +
|
|
71
|
+
'Expected format: nid-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (e.g., 1141-6d2827f3-1bc2-405c-95b4-6a09e8b64148)'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Add UUID as Playwright annotation
|
|
76
|
+
// The SDK's UuidExtractor will read this annotation when reporting results
|
|
77
|
+
testInfo.annotations.push({
|
|
78
|
+
type: 'appliqation-uuid',
|
|
79
|
+
description: trimmedUuid
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { mapAppqUuid };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates the payload for test result insertion
|
|
3
|
+
* @param {Object} payload - The test result payload
|
|
4
|
+
* @returns {Object} - Validation result with isValid flag and errors array
|
|
5
|
+
*/
|
|
6
|
+
function validatePayload(payload) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
|
|
9
|
+
if (!payload) {
|
|
10
|
+
errors.push('Payload is required');
|
|
11
|
+
return { isValid: false, errors };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Required fields validation
|
|
15
|
+
if (!payload.run_id) {
|
|
16
|
+
errors.push('run_id is required');
|
|
17
|
+
} else if (typeof payload.run_id !== 'string') {
|
|
18
|
+
errors.push('run_id must be a string');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!payload.uuid) {
|
|
22
|
+
errors.push('uuid is required');
|
|
23
|
+
} else if (typeof payload.uuid !== 'string') {
|
|
24
|
+
errors.push('uuid must be a string');
|
|
25
|
+
} else if (!validateUuidFormat(payload.uuid)) {
|
|
26
|
+
errors.push('uuid must be in format: nid-uuid (e.g., "124-d1f9559c-b978-43cc-9c76-fd539c717cb4")');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!payload.status) {
|
|
30
|
+
errors.push('status is required');
|
|
31
|
+
} else if (typeof payload.status !== 'string') {
|
|
32
|
+
errors.push('status must be a string');
|
|
33
|
+
} else if (!validateStatus(payload.status)) {
|
|
34
|
+
errors.push('status must be one of: Pass, Fail, Skip, Pending');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Optional fields validation
|
|
38
|
+
if (payload.browser && typeof payload.browser !== 'string') {
|
|
39
|
+
errors.push('browser must be a string');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (payload.comment && typeof payload.comment !== 'string') {
|
|
43
|
+
errors.push('comment must be a string');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Additional validation for run_id format (UUID)
|
|
47
|
+
if (payload.run_id && !validateRunIdFormat(payload.run_id)) {
|
|
48
|
+
errors.push('run_id must be a valid UUID format');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isValid: errors.length === 0,
|
|
53
|
+
errors
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validates UUID format for run_id
|
|
59
|
+
* @param {string} runId - The run ID to validate
|
|
60
|
+
* @returns {boolean} - True if valid UUID format
|
|
61
|
+
*/
|
|
62
|
+
function validateRunIdFormat(runId) {
|
|
63
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
64
|
+
return uuidRegex.test(runId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validates UUID format for test case UUID
|
|
69
|
+
* @param {string} uuid - The UUID to validate (format: nid-uuid)
|
|
70
|
+
* @returns {boolean} - True if valid format
|
|
71
|
+
*/
|
|
72
|
+
function validateUuidFormat(uuid) {
|
|
73
|
+
// Format should be: nid-uuid (e.g., "124-d1f9559c-b978-43cc-9c76-fd539c717cb4")
|
|
74
|
+
const parts = uuid.split('-');
|
|
75
|
+
if (parts.length < 6) return false; // nid + 5 UUID parts
|
|
76
|
+
|
|
77
|
+
const nid = parts[0];
|
|
78
|
+
const uuidPart = parts.slice(1).join('-');
|
|
79
|
+
|
|
80
|
+
// Validate nid is numeric
|
|
81
|
+
if (!/^\d+$/.test(nid)) return false;
|
|
82
|
+
|
|
83
|
+
// Validate UUID part
|
|
84
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
85
|
+
return uuidRegex.test(uuidPart);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validates test status
|
|
90
|
+
* @param {string} status - The test status
|
|
91
|
+
* @returns {boolean} - True if valid status
|
|
92
|
+
*/
|
|
93
|
+
function validateStatus(status) {
|
|
94
|
+
const validStatuses = ['Pass', 'Fail', 'Skip', 'Pending', 'pass', 'fail', 'skip', 'pending'];
|
|
95
|
+
return validStatuses.includes(status);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sanitizes and normalizes payload data
|
|
100
|
+
* @param {Object} payload - The payload to sanitize
|
|
101
|
+
* @returns {Object} - Sanitized payload
|
|
102
|
+
*/
|
|
103
|
+
function sanitizePayload(payload) {
|
|
104
|
+
const sanitized = { ...payload };
|
|
105
|
+
|
|
106
|
+
// Normalize status to proper case
|
|
107
|
+
if (sanitized.status) {
|
|
108
|
+
sanitized.status = sanitized.status.toLowerCase();
|
|
109
|
+
sanitized.status = sanitized.status.charAt(0).toUpperCase() + sanitized.status.slice(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Trim string fields
|
|
113
|
+
if (sanitized.browser) {
|
|
114
|
+
sanitized.browser = sanitized.browser.trim();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (sanitized.comment) {
|
|
118
|
+
sanitized.comment = sanitized.comment.trim();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (sanitized.run_id) {
|
|
122
|
+
sanitized.run_id = sanitized.run_id.trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (sanitized.uuid) {
|
|
126
|
+
sanitized.uuid = sanitized.uuid.trim();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return sanitized;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Creates a payload from test framework data
|
|
134
|
+
* @param {Object} testData - Test data from framework
|
|
135
|
+
* @param {Object} config - Configuration options
|
|
136
|
+
* @returns {Object} - Formatted payload
|
|
137
|
+
*/
|
|
138
|
+
function createPayload(testData, config) {
|
|
139
|
+
const payload = {
|
|
140
|
+
run_id: testData.run_id || config.run_id,
|
|
141
|
+
uuid: testData.uuid || config.uuid,
|
|
142
|
+
browser: testData.browser || config.browser || 'Unknown',
|
|
143
|
+
status: testData.status || 'Fail',
|
|
144
|
+
comment: testData.comment || testData.error || ''
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return sanitizePayload(payload);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
validatePayload,
|
|
152
|
+
validateRunIdFormat,
|
|
153
|
+
validateUuidFormat,
|
|
154
|
+
validateStatus,
|
|
155
|
+
sanitizePayload,
|
|
156
|
+
createPayload
|
|
157
|
+
};
|