@appliqation/automation-sdk 2.3.2 → 2.5.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/src/AppliqationClient.js
CHANGED
|
@@ -4,6 +4,7 @@ const RunMatrixService = require('./services/RunMatrixService');
|
|
|
4
4
|
const ResultService = require('./services/ResultService');
|
|
5
5
|
const TaggingService = require('./services/TaggingService');
|
|
6
6
|
const OrphanTestService = require('./services/OrphanTestService');
|
|
7
|
+
const ProjectInfoService = require('./services/ProjectInfoService');
|
|
7
8
|
const UuidValidator = require('./utils/UuidValidator');
|
|
8
9
|
const PayloadBuilder = require('./utils/PayloadBuilder');
|
|
9
10
|
const logger = require('./utils/logger');
|
|
@@ -13,102 +14,82 @@ const { DEFAULT_APPLIQATION_BASE_URL } = require('./constants');
|
|
|
13
14
|
* Main Appliqation Automation SDK Client
|
|
14
15
|
*
|
|
15
16
|
* @example
|
|
16
|
-
* //
|
|
17
|
+
* // Minimal config — project auto-discovered from API key
|
|
17
18
|
* const client = new AppliqationClient({
|
|
18
|
-
*
|
|
19
|
-
* apiKey: 'appq_live_xxxxxxxxxxxxx',
|
|
20
|
-
* projectKey: 'your-project-key'
|
|
21
|
-
* });
|
|
22
|
-
*
|
|
23
|
-
* // Create run matrix
|
|
24
|
-
* const run = await client.createRun({
|
|
25
|
-
* scenarioId: 123,
|
|
26
|
-
* environment: 'Production',
|
|
27
|
-
* browsers: ['Chrome', 'Firefox'],
|
|
28
|
-
* device: 'Desktop',
|
|
29
|
-
* os: 'Windows 11'
|
|
30
|
-
* });
|
|
31
|
-
*
|
|
32
|
-
* // Submit test results
|
|
33
|
-
* await client.submitResult(run.runId, {
|
|
34
|
-
* uuid: '124-d1f9559c-b978-43cc-9c76-fd539c717cb4',
|
|
35
|
-
* status: 'passed',
|
|
36
|
-
* browser: 'Chrome'
|
|
19
|
+
* apiKey: process.env.APPLIQATION_API_KEY
|
|
37
20
|
* });
|
|
21
|
+
* await client.initialize(); // auto-discovers project
|
|
38
22
|
*/
|
|
39
23
|
class AppliqationClient {
|
|
24
|
+
|
|
40
25
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* @param {
|
|
45
|
-
* @
|
|
46
|
-
* @param {string} [config.username] - Username for CSRF auth (legacy)
|
|
47
|
-
* @param {string} [config.password] - Password for CSRF auth (legacy)
|
|
48
|
-
* @param {string} [config.title] - Custom run title (or use APPLIQATION_RUN_TITLE env var)
|
|
49
|
-
* @param {Object} [config.options] - Additional options
|
|
50
|
-
* @param {number} [config.options.timeout=30000] - Request timeout in ms
|
|
51
|
-
* @param {number} [config.options.retries=3] - Number of retry attempts
|
|
52
|
-
* @param {boolean} [config.options.logOrphans=true] - Log orphan tests to backend
|
|
53
|
-
* @param {string} [config.options.logLevel='info'] - Logging level
|
|
26
|
+
* Resolve configuration from multiple sources.
|
|
27
|
+
* Priority: explicit config > env vars > defaults
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} config - User-provided configuration
|
|
30
|
+
* @returns {Object} Resolved configuration
|
|
54
31
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
32
|
+
static resolveConfig(config = {}) {
|
|
33
|
+
const apiKey = config.apiKey || process.env.APPLIQATION_API_KEY || null;
|
|
34
|
+
const baseUrl = (config.baseUrl || DEFAULT_APPLIQATION_BASE_URL || '').replace(/\/$/, '');
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
...config,
|
|
38
|
+
apiKey,
|
|
39
|
+
baseUrl,
|
|
40
|
+
projectKey: config.projectKey || process.env.APPLIQATION_PROJECT_KEY || null,
|
|
41
|
+
runTitle: config.runTitle || config.title || process.env.APPLIQATION_RUN_TITLE || null,
|
|
61
42
|
};
|
|
43
|
+
}
|
|
62
44
|
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Create Appliqation client
|
|
47
|
+
* @param {Object} config - Client configuration (can be empty — uses resolveConfig)
|
|
48
|
+
*/
|
|
49
|
+
constructor(config = {}) {
|
|
50
|
+
const resolved = AppliqationClient.resolveConfig(config);
|
|
51
|
+
|
|
52
|
+
// Validate — warn instead of throw
|
|
53
|
+
const validation = this.validateConfig(resolved);
|
|
54
|
+
if (!validation.valid) {
|
|
55
|
+
// Log warnings but don't throw
|
|
56
|
+
for (const warning of validation.warnings) {
|
|
57
|
+
logger.warn(warning);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
65
60
|
|
|
66
61
|
// Determine SSL enforcement based on environment
|
|
67
62
|
const isProduction = this._isProductionEnvironment();
|
|
68
|
-
let rejectUnauthorized = true;
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// WARNING: User is trying to disable SSL in production
|
|
74
|
-
logger.warn('SSL certificate verification is disabled in production environment!', {
|
|
75
|
-
environment: process.env.NODE_ENV,
|
|
76
|
-
ci: process.env.CI,
|
|
77
|
-
warning: 'This is a security risk. SSL verification should be enabled in production.'
|
|
78
|
-
});
|
|
63
|
+
let rejectUnauthorized = true;
|
|
64
|
+
|
|
65
|
+
if (resolved.rejectUnauthorized !== undefined) {
|
|
66
|
+
if (isProduction && resolved.rejectUnauthorized === false) {
|
|
67
|
+
logger.warn('SSL certificate verification is disabled in production environment!');
|
|
79
68
|
}
|
|
80
|
-
rejectUnauthorized =
|
|
81
|
-
} else if (isProduction) {
|
|
82
|
-
// Production environment - force SSL verification
|
|
83
|
-
rejectUnauthorized = true;
|
|
84
|
-
logger.info('SSL certificate verification enforced for production environment');
|
|
85
|
-
} else {
|
|
86
|
-
// Development environment - default to secure but can be overridden
|
|
87
|
-
rejectUnauthorized = true;
|
|
69
|
+
rejectUnauthorized = resolved.rejectUnauthorized;
|
|
88
70
|
}
|
|
89
71
|
|
|
90
72
|
// Store configuration
|
|
91
73
|
this.config = {
|
|
92
|
-
baseUrl:
|
|
93
|
-
apiKey:
|
|
94
|
-
projectKey:
|
|
95
|
-
username:
|
|
96
|
-
password:
|
|
97
|
-
runTitle:
|
|
98
|
-
rejectUnauthorized
|
|
74
|
+
baseUrl: resolved.baseUrl,
|
|
75
|
+
apiKey: resolved.apiKey,
|
|
76
|
+
projectKey: resolved.projectKey,
|
|
77
|
+
username: resolved.username,
|
|
78
|
+
password: resolved.password,
|
|
79
|
+
runTitle: resolved.runTitle,
|
|
80
|
+
rejectUnauthorized,
|
|
99
81
|
options: {
|
|
100
|
-
timeout:
|
|
101
|
-
retries:
|
|
102
|
-
logOrphans:
|
|
103
|
-
logLevel:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
normalizedConfig.autoTagName ||
|
|
82
|
+
timeout: resolved.options?.timeout || 30000,
|
|
83
|
+
retries: resolved.options?.retries || 3,
|
|
84
|
+
logOrphans: resolved.options?.logOrphans !== false,
|
|
85
|
+
logLevel: resolved.options?.logLevel || 'info',
|
|
86
|
+
autoTag: resolved.options?.autoTag !== false,
|
|
87
|
+
autoTagName: resolved.options?.autoTagName ||
|
|
88
|
+
resolved.autoTagName ||
|
|
108
89
|
process.env.APPLIQATION_AUTO_TAG_NAME ||
|
|
109
90
|
'Appq_automated',
|
|
110
|
-
autoTagBatchSize:
|
|
111
|
-
autoTagRetries:
|
|
91
|
+
autoTagBatchSize: resolved.options?.autoTagBatchSize || 50,
|
|
92
|
+
autoTagRetries: resolved.options?.autoTagRetries || 2
|
|
112
93
|
}
|
|
113
94
|
};
|
|
114
95
|
|
|
@@ -124,17 +105,15 @@ class AppliqationClient {
|
|
|
124
105
|
this.tagging = new TaggingService(this.http, this.config);
|
|
125
106
|
this.results = new ResultService(this.http, this.tagging, this.config);
|
|
126
107
|
this.orphans = new OrphanTestService(this.http);
|
|
108
|
+
this.projectInfo = new ProjectInfoService();
|
|
127
109
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
tagName: this.tagging.tagName
|
|
132
|
-
});
|
|
110
|
+
// Auto-discovery state
|
|
111
|
+
this.availableEnvironments = null;
|
|
112
|
+
this._initialized = false;
|
|
133
113
|
|
|
134
114
|
// Track current run context
|
|
135
115
|
this.currentRun = null;
|
|
136
116
|
|
|
137
|
-
// Show baseUrl only in DEBUG mode
|
|
138
117
|
const meta = {
|
|
139
118
|
authMode: this.config.apiKey ? 'api_key' : 'csrf'
|
|
140
119
|
};
|
|
@@ -142,29 +121,48 @@ class AppliqationClient {
|
|
|
142
121
|
logger.debug('Client configuration', { baseUrl: this.config.baseUrl });
|
|
143
122
|
}
|
|
144
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Auto-discover project info from API key.
|
|
126
|
+
* Fills in missing projectKey and availableEnvironments.
|
|
127
|
+
* Never throws — logs warnings on failure.
|
|
128
|
+
*
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
async initialize() {
|
|
132
|
+
if (this._initialized) return;
|
|
133
|
+
this._initialized = true;
|
|
134
|
+
|
|
135
|
+
if (!this.config.apiKey) return;
|
|
136
|
+
|
|
137
|
+
const info = await this.projectInfo.getProjectInfo(this.http);
|
|
138
|
+
|
|
139
|
+
if (info) {
|
|
140
|
+
// Auto-fill projectKey if not explicitly set
|
|
141
|
+
if (!this.config.projectKey && info.key) {
|
|
142
|
+
this.config.projectKey = info.key;
|
|
143
|
+
logger.info('Project auto-discovered', { projectKey: info.key, name: info.name });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Store available environments for validation
|
|
147
|
+
if (info.environments && info.environments.length > 0) {
|
|
148
|
+
this.availableEnvironments = info.environments;
|
|
149
|
+
logger.debug('Available environments', { environments: info.environments });
|
|
150
|
+
}
|
|
151
|
+
} else if (!this.config.projectKey) {
|
|
152
|
+
console.warn('[Appliqation] Project auto-discovery failed. Tests will continue without reporting.');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
145
156
|
/**
|
|
146
157
|
* Create a new test run matrix
|
|
147
|
-
* @param {Object} options - Run configuration
|
|
148
|
-
* @param {number} options.scenarioId - Scenario node ID
|
|
149
|
-
* @param {number} options.testSetId - Test Set node ID (alternative to scenarioId)
|
|
150
|
-
* @param {string} options.type - 'scenario' or 'testset'
|
|
151
|
-
* @param {string} [options.environment='Local'] - Environment name
|
|
152
|
-
* @param {string[]} [options.browsers=['Chrome']] - Array of browser names
|
|
153
|
-
* @param {string} [options.device] - Device type (Desktop, Mobile, Tablet)
|
|
154
|
-
* @param {string} [options.os] - Operating system
|
|
155
|
-
* @param {string} [options.title] - Custom run title
|
|
156
|
-
* @returns {Promise<Object>} { runId, token, timestamp, metadata }
|
|
157
158
|
*/
|
|
158
159
|
async createRun(options) {
|
|
159
160
|
try {
|
|
160
161
|
logger.info('Creating run matrix...', options);
|
|
161
|
-
|
|
162
162
|
const run = await this.runMatrix.create(options);
|
|
163
163
|
|
|
164
|
-
// Store current run context
|
|
165
164
|
this.currentRun = run;
|
|
166
165
|
|
|
167
|
-
// Store run configuration in AuthManager for JWT refresh capability
|
|
168
166
|
if (this.auth && this.config.apiKey) {
|
|
169
167
|
this.auth.setRunConfig({
|
|
170
168
|
projectKey: this.config.projectKey,
|
|
@@ -176,7 +174,6 @@ class AppliqationClient {
|
|
|
176
174
|
os: options.os,
|
|
177
175
|
title: options.title || this.config.runTitle
|
|
178
176
|
});
|
|
179
|
-
logger.debug('Run configuration stored in AuthManager for JWT refresh');
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
logger.info('Run matrix created successfully', {
|
|
@@ -196,90 +193,56 @@ class AppliqationClient {
|
|
|
196
193
|
|
|
197
194
|
/**
|
|
198
195
|
* Create multiple runs for device/OS matrix testing
|
|
199
|
-
* @param {Array} configs - Array of run configurations
|
|
200
|
-
* @returns {Promise<Object>} { runs, errors, summary }
|
|
201
196
|
*/
|
|
202
197
|
async createMultipleRuns(configs) {
|
|
203
198
|
try {
|
|
204
199
|
logger.info(`Creating ${configs.length} run matrices...`);
|
|
205
|
-
|
|
206
200
|
const result = await this.runMatrix.createMultiple(configs);
|
|
207
|
-
|
|
208
201
|
logger.info('Multiple runs created', {
|
|
209
202
|
total: result.summary.total,
|
|
210
203
|
success: result.summary.success,
|
|
211
204
|
failed: result.summary.failed
|
|
212
205
|
});
|
|
213
|
-
|
|
214
206
|
return result;
|
|
215
207
|
} catch (error) {
|
|
216
|
-
logger.error('Failed to create multiple runs', {
|
|
217
|
-
error: error.message
|
|
218
|
-
});
|
|
208
|
+
logger.error('Failed to create multiple runs', { error: error.message });
|
|
219
209
|
throw error;
|
|
220
210
|
}
|
|
221
211
|
}
|
|
222
212
|
|
|
223
213
|
/**
|
|
224
214
|
* Submit a single test result
|
|
225
|
-
* @param {string} runId - Run ID
|
|
226
|
-
* @param {Object} result - Test result
|
|
227
|
-
* @param {string} result.uuid - Test case UUID
|
|
228
|
-
* @param {string} result.status - Test status (passed, failed, skipped)
|
|
229
|
-
* @param {string} result.browser - Browser name
|
|
230
|
-
* @param {string} [result.parent_uuid] - Parent test case UUID
|
|
231
|
-
* @param {string} [result.comment] - Additional comments
|
|
232
|
-
* @param {string} [result.environment] - Environment name
|
|
233
|
-
* @returns {Promise<Object>} Response from server
|
|
234
215
|
*/
|
|
235
216
|
async submitResult(runId, result) {
|
|
236
217
|
try {
|
|
237
|
-
// Validate UUID
|
|
238
218
|
const validation = UuidValidator.validateAndExtract(result.uuid);
|
|
239
219
|
if (!validation.valid) {
|
|
240
220
|
throw new Error(`Invalid UUID: ${validation.error}`);
|
|
241
221
|
}
|
|
242
|
-
|
|
243
|
-
// Validate result fields
|
|
244
222
|
const resultValidation = PayloadBuilder.validateResult(result);
|
|
245
223
|
if (!resultValidation.valid) {
|
|
246
224
|
throw new Error(`Invalid result: ${resultValidation.errors.join(', ')}`);
|
|
247
225
|
}
|
|
248
226
|
|
|
249
227
|
logger.debug('Submitting test result', {
|
|
250
|
-
runId,
|
|
251
|
-
uuid: result.uuid,
|
|
252
|
-
status: result.status,
|
|
253
|
-
browser: result.browser
|
|
228
|
+
runId, uuid: result.uuid, status: result.status, browser: result.browser
|
|
254
229
|
});
|
|
255
230
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return response;
|
|
231
|
+
return await this.results.submitSingle(runId, result);
|
|
259
232
|
} catch (error) {
|
|
260
233
|
logger.error('Failed to submit result', {
|
|
261
|
-
error: error.message,
|
|
262
|
-
runId,
|
|
263
|
-
uuid: result.uuid
|
|
234
|
+
error: error.message, runId, uuid: result.uuid
|
|
264
235
|
});
|
|
265
236
|
throw error;
|
|
266
237
|
}
|
|
267
238
|
}
|
|
268
239
|
|
|
269
240
|
/**
|
|
270
|
-
* Submit batch of test results
|
|
271
|
-
* @param {Array} results - Array of test results
|
|
272
|
-
* @param {Object} [options] - Batch options
|
|
273
|
-
* @param {number} [options.batchSize=50] - Results per batch
|
|
274
|
-
* @param {Function} [options.onProgress] - Progress callback
|
|
275
|
-
* @param {boolean} [options.retryFailures=true] - Retry failed submissions
|
|
276
|
-
* @returns {Promise<Object>} { success, failed, total }
|
|
241
|
+
* Submit batch of test results
|
|
277
242
|
*/
|
|
278
243
|
async submitBatch(results, options = {}) {
|
|
279
244
|
try {
|
|
280
245
|
logger.info(`Submitting batch of ${results.length} results...`);
|
|
281
|
-
|
|
282
|
-
// Validate all results first
|
|
283
246
|
const validResults = [];
|
|
284
247
|
const invalidResults = [];
|
|
285
248
|
|
|
@@ -289,10 +252,7 @@ class AppliqationClient {
|
|
|
289
252
|
validResults.push(result);
|
|
290
253
|
} else {
|
|
291
254
|
invalidResults.push({ result, error: validation.error });
|
|
292
|
-
logger.warn('Invalid UUID in batch', {
|
|
293
|
-
uuid: result.uuid,
|
|
294
|
-
error: validation.error
|
|
295
|
-
});
|
|
255
|
+
logger.warn('Invalid UUID in batch', { uuid: result.uuid, error: validation.error });
|
|
296
256
|
}
|
|
297
257
|
}
|
|
298
258
|
|
|
@@ -303,312 +263,171 @@ class AppliqationClient {
|
|
|
303
263
|
const summary = await this.results.submitBatch(validResults, options);
|
|
304
264
|
|
|
305
265
|
logger.info('Batch submission completed', {
|
|
306
|
-
success: summary.success,
|
|
307
|
-
|
|
308
|
-
invalid: invalidResults.length,
|
|
309
|
-
total: results.length
|
|
266
|
+
success: summary.success, failed: summary.failed,
|
|
267
|
+
invalid: invalidResults.length, total: results.length
|
|
310
268
|
});
|
|
311
269
|
|
|
312
|
-
return {
|
|
313
|
-
...summary,
|
|
314
|
-
invalid: invalidResults.length,
|
|
315
|
-
invalidResults
|
|
316
|
-
};
|
|
270
|
+
return { ...summary, invalid: invalidResults.length, invalidResults };
|
|
317
271
|
} catch (error) {
|
|
318
|
-
logger.error('Failed to submit batch', {
|
|
319
|
-
error: error.message,
|
|
320
|
-
count: results.length
|
|
321
|
-
});
|
|
272
|
+
logger.error('Failed to submit batch', { error: error.message, count: results.length });
|
|
322
273
|
throw error;
|
|
323
274
|
}
|
|
324
275
|
}
|
|
325
276
|
|
|
326
277
|
/**
|
|
327
|
-
* Log orphan tests
|
|
328
|
-
* @param {string} runId - Run ID
|
|
329
|
-
* @param {Array} orphanTests - Array of orphan test objects
|
|
330
|
-
* @returns {Promise<Object>} Response from server
|
|
278
|
+
* Log orphan tests
|
|
331
279
|
*/
|
|
332
280
|
async logOrphanTests(runId, orphanTests) {
|
|
333
281
|
try {
|
|
334
282
|
if (!this.config.options.logOrphans) {
|
|
335
|
-
logger.debug('Orphan test logging is disabled');
|
|
336
283
|
return { success: true, logged: 0 };
|
|
337
284
|
}
|
|
338
|
-
|
|
339
285
|
if (!orphanTests || orphanTests.length === 0) {
|
|
340
286
|
return { success: true, logged: 0 };
|
|
341
287
|
}
|
|
342
288
|
|
|
343
289
|
logger.info(`Logging ${orphanTests.length} orphan tests...`);
|
|
344
|
-
|
|
345
290
|
const response = await this.orphans.log(runId, orphanTests);
|
|
346
|
-
|
|
347
|
-
logger.info(`Orphan tests logged successfully`, {
|
|
348
|
-
runId,
|
|
349
|
-
count: orphanTests.length
|
|
350
|
-
});
|
|
351
|
-
|
|
291
|
+
logger.info(`Orphan tests logged successfully`, { runId, count: orphanTests.length });
|
|
352
292
|
return response;
|
|
353
293
|
} catch (error) {
|
|
354
294
|
logger.error('Failed to log orphan tests', {
|
|
355
|
-
error: error.message,
|
|
356
|
-
runId,
|
|
357
|
-
count: orphanTests.length
|
|
295
|
+
error: error.message, runId, count: orphanTests.length
|
|
358
296
|
});
|
|
359
|
-
// Don't throw - orphan logging should not break the test run
|
|
360
297
|
return { success: false, error: error.message };
|
|
361
298
|
}
|
|
362
299
|
}
|
|
363
300
|
|
|
364
301
|
/**
|
|
365
|
-
* Add browser to existing run matrix
|
|
366
|
-
* @param {string} runId - Run ID
|
|
367
|
-
* @param {string} browser - Browser name
|
|
368
|
-
* @param {number} nid - Optional scenario/testset node ID (defaults to 0)
|
|
369
|
-
* @returns {Promise<Object>} Response from server
|
|
302
|
+
* Add browser to existing run matrix
|
|
370
303
|
*/
|
|
371
304
|
async addBrowser(runId, browser, nid = 0) {
|
|
372
305
|
try {
|
|
373
306
|
logger.info('Adding browser to run matrix', { runId, browser, nid });
|
|
374
|
-
|
|
375
307
|
const response = await this.runMatrix.addBrowser(runId, browser, nid);
|
|
376
|
-
|
|
377
308
|
logger.info('Browser added successfully', { runId, browser });
|
|
378
|
-
|
|
379
309
|
return response;
|
|
380
310
|
} catch (error) {
|
|
381
|
-
logger.error('Failed to add browser', {
|
|
382
|
-
error: error.message,
|
|
383
|
-
runId,
|
|
384
|
-
browser
|
|
385
|
-
});
|
|
311
|
+
logger.error('Failed to add browser', { error: error.message, runId, browser });
|
|
386
312
|
throw error;
|
|
387
313
|
}
|
|
388
314
|
}
|
|
389
315
|
|
|
390
316
|
/**
|
|
391
317
|
* Get run matrix data
|
|
392
|
-
* @param {string} runId - Run ID
|
|
393
|
-
* @returns {Promise<Object>} Run matrix data
|
|
394
318
|
*/
|
|
395
319
|
async getRun(runId) {
|
|
396
320
|
try {
|
|
397
|
-
|
|
398
|
-
return response;
|
|
321
|
+
return await this.runMatrix.get(runId);
|
|
399
322
|
} catch (error) {
|
|
400
|
-
logger.error('Failed to get run matrix', {
|
|
401
|
-
error: error.message,
|
|
402
|
-
runId
|
|
403
|
-
});
|
|
323
|
+
logger.error('Failed to get run matrix', { error: error.message, runId });
|
|
404
324
|
throw error;
|
|
405
325
|
}
|
|
406
326
|
}
|
|
407
327
|
|
|
408
|
-
/**
|
|
409
|
-
* Get current run context (if set)
|
|
410
|
-
* @returns {Object|null} Current run data or null
|
|
411
|
-
*/
|
|
412
328
|
getCurrentRun() {
|
|
413
329
|
return this.currentRun;
|
|
414
330
|
}
|
|
415
331
|
|
|
416
|
-
/**
|
|
417
|
-
* Set current run context manually
|
|
418
|
-
* @param {Object} run - Run data with runId
|
|
419
|
-
*/
|
|
420
332
|
setCurrentRun(run) {
|
|
421
333
|
this.currentRun = run;
|
|
422
334
|
}
|
|
423
335
|
|
|
424
336
|
/**
|
|
425
337
|
* Delete a test run
|
|
426
|
-
* @param {string} runId - Run ID to delete
|
|
427
|
-
* @param {string} reason - Deletion reason (for audit logging)
|
|
428
|
-
* @returns {Promise<Object>} Deletion result
|
|
429
338
|
*/
|
|
430
339
|
async deleteRun(runId, reason = 'orphan_cleanup') {
|
|
431
340
|
try {
|
|
432
341
|
logger.info('Deleting run...', { runId, reason });
|
|
433
|
-
|
|
434
342
|
const result = await this.runMatrix.delete(runId, reason);
|
|
435
|
-
|
|
436
|
-
// Clear from current run if it matches
|
|
437
343
|
if (this.currentRun && this.currentRun.runId === runId) {
|
|
438
344
|
this.currentRun = null;
|
|
439
345
|
}
|
|
440
|
-
|
|
441
346
|
logger.info('Run deleted successfully', { runId });
|
|
442
|
-
|
|
443
347
|
return result;
|
|
444
348
|
} catch (error) {
|
|
445
|
-
logger.error('Failed to delete run', {
|
|
446
|
-
error: error.message,
|
|
447
|
-
runId
|
|
448
|
-
});
|
|
349
|
+
logger.error('Failed to delete run', { error: error.message, runId });
|
|
449
350
|
throw error;
|
|
450
351
|
}
|
|
451
352
|
}
|
|
452
353
|
|
|
453
|
-
/**
|
|
454
|
-
* Validate UUID format
|
|
455
|
-
* @param {string} uuid - UUID to validate
|
|
456
|
-
* @returns {boolean} True if valid
|
|
457
|
-
*/
|
|
458
354
|
validateUuid(uuid) {
|
|
459
355
|
return UuidValidator.validate(uuid);
|
|
460
356
|
}
|
|
461
357
|
|
|
462
|
-
/**
|
|
463
|
-
* Extract NID from UUID
|
|
464
|
-
* @param {string} uuid - UUID string
|
|
465
|
-
* @returns {number|null} NID or null if invalid
|
|
466
|
-
*/
|
|
467
358
|
extractNid(uuid) {
|
|
468
359
|
return UuidValidator.extractNid(uuid);
|
|
469
360
|
}
|
|
470
361
|
|
|
471
|
-
/**
|
|
472
|
-
* Validate and extract UUID components
|
|
473
|
-
* @param {string} uuid - UUID string
|
|
474
|
-
* @returns {Object} { valid, nid, uuid, error }
|
|
475
|
-
*/
|
|
476
362
|
parseUuid(uuid) {
|
|
477
363
|
return UuidValidator.validateAndExtract(uuid);
|
|
478
364
|
}
|
|
479
365
|
|
|
480
366
|
/**
|
|
481
|
-
* Validate client configuration
|
|
367
|
+
* Validate client configuration — returns warnings instead of throwing.
|
|
482
368
|
* @private
|
|
369
|
+
* @returns {{ valid: boolean, warnings: string[] }}
|
|
483
370
|
*/
|
|
484
371
|
validateConfig(config) {
|
|
372
|
+
const warnings = [];
|
|
373
|
+
|
|
485
374
|
if (!config) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
'Please provide configuration object:\n' +
|
|
489
|
-
' const client = new AppliqationClient({\n' +
|
|
490
|
-
' baseUrl: process.env.APPLIQATION_BASE_URL,\n' +
|
|
491
|
-
' apiKey: process.env.APPLIQATION_API_KEY,\n' +
|
|
492
|
-
' projectKey: process.env.APPLIQATION_PROJECT_KEY\n' +
|
|
493
|
-
' });'
|
|
494
|
-
);
|
|
375
|
+
warnings.push('[Appliqation] No configuration provided');
|
|
376
|
+
return { valid: false, warnings };
|
|
495
377
|
}
|
|
496
378
|
|
|
497
|
-
if (!config.
|
|
498
|
-
|
|
499
|
-
'baseUrl is required.\n' +
|
|
500
|
-
'Please set the APPLIQATION_BASE_URL environment variable or provide baseUrl in configuration:\n' +
|
|
501
|
-
' APPLIQATION_BASE_URL=https://your-instance.appliqation.com\n' +
|
|
502
|
-
'Or in code:\n' +
|
|
503
|
-
' { baseUrl: "https://your-instance.appliqation.com" }'
|
|
504
|
-
);
|
|
379
|
+
if (!config.apiKey) {
|
|
380
|
+
warnings.push('[Appliqation] API key required. Set APPLIQATION_API_KEY in .env');
|
|
505
381
|
}
|
|
506
382
|
|
|
507
383
|
if (!config.projectKey) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
'Please set the APPLIQATION_PROJECT_KEY environment variable or provide projectKey in configuration:\n' +
|
|
511
|
-
' APPLIQATION_PROJECT_KEY=your-project-key\n' +
|
|
512
|
-
'Or in code:\n' +
|
|
513
|
-
' { projectKey: "your-project-key" }\n' +
|
|
514
|
-
'\n' +
|
|
515
|
-
'You can find your project key in the Appliqation dashboard under Project Settings.'
|
|
516
|
-
);
|
|
384
|
+
// Not an error — will be auto-discovered
|
|
385
|
+
logger.debug('projectKey not set — will auto-discover from API key');
|
|
517
386
|
}
|
|
518
387
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
throw new Error(
|
|
526
|
-
'Authentication credentials are required.\n' +
|
|
527
|
-
'Please set the APPLIQATION_API_KEY environment variable (recommended):\n' +
|
|
528
|
-
' APPLIQATION_API_KEY=appq_live_xxxxxxxxxxxx\n' +
|
|
529
|
-
'Or in code:\n' +
|
|
530
|
-
' { apiKey: "appq_live_xxxxxxxxxxxx" }\n' +
|
|
531
|
-
'\n' +
|
|
532
|
-
'You can generate an API key in the Appliqation dashboard under Settings > API Keys.\n' +
|
|
533
|
-
'\n' +
|
|
534
|
-
'Legacy CSRF authentication (username/password) is also supported but not recommended.'
|
|
535
|
-
);
|
|
388
|
+
if (config.baseUrl) {
|
|
389
|
+
try {
|
|
390
|
+
new URL(config.baseUrl);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
warnings.push(`[Appliqation] Invalid baseUrl format: "${config.baseUrl}"`);
|
|
393
|
+
}
|
|
536
394
|
}
|
|
537
395
|
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
new URL(config.baseUrl);
|
|
541
|
-
} catch (error) {
|
|
542
|
-
throw new Error(
|
|
543
|
-
`Invalid baseUrl format: "${config.baseUrl}"\n` +
|
|
544
|
-
'baseUrl must be a valid URL starting with http:// or https://\n' +
|
|
545
|
-
'Example: https://your-instance.appliqation.com'
|
|
546
|
-
);
|
|
547
|
-
}
|
|
396
|
+
return { valid: warnings.length === 0, warnings };
|
|
548
397
|
}
|
|
549
398
|
|
|
550
399
|
/**
|
|
551
|
-
* Test connectivity
|
|
552
|
-
* @returns {Promise<boolean>} True if connection successful
|
|
400
|
+
* Test connectivity
|
|
553
401
|
*/
|
|
554
402
|
async testConnection() {
|
|
555
403
|
try {
|
|
556
404
|
logger.info('Testing connection to Appliqation...');
|
|
557
|
-
|
|
558
|
-
// Try to authenticate
|
|
559
405
|
await this.auth.authenticate();
|
|
560
|
-
|
|
561
406
|
logger.info('Connection test successful');
|
|
562
407
|
return true;
|
|
563
408
|
} catch (error) {
|
|
564
|
-
logger.error('Connection test failed', {
|
|
565
|
-
error: error.message
|
|
566
|
-
});
|
|
409
|
+
logger.error('Connection test failed', { error: error.message });
|
|
567
410
|
throw new Error(`Connection test failed: ${error.message}`);
|
|
568
411
|
}
|
|
569
412
|
}
|
|
570
413
|
|
|
571
414
|
/**
|
|
572
|
-
* Determine if the current environment is production
|
|
573
415
|
* @private
|
|
574
|
-
* @returns {boolean} True if production environment
|
|
575
416
|
*/
|
|
576
417
|
_isProductionEnvironment() {
|
|
577
|
-
|
|
578
|
-
if (process.env.CI === 'true' || process.env.CI === '1') {
|
|
579
|
-
return true;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Check NODE_ENV
|
|
418
|
+
if (process.env.CI === 'true' || process.env.CI === '1') return true;
|
|
583
419
|
const nodeEnv = (process.env.NODE_ENV || '').toLowerCase();
|
|
584
|
-
if (nodeEnv === 'production' || nodeEnv === 'prod' || nodeEnv === 'staging')
|
|
585
|
-
return true;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Check for common CI environment variables
|
|
420
|
+
if (nodeEnv === 'production' || nodeEnv === 'prod' || nodeEnv === 'staging') return true;
|
|
589
421
|
const ciEnvVars = [
|
|
590
|
-
'GITHUB_ACTIONS',
|
|
591
|
-
'
|
|
592
|
-
'CIRCLECI',
|
|
593
|
-
'TRAVIS',
|
|
594
|
-
'JENKINS_URL',
|
|
595
|
-
'TEAMCITY_VERSION',
|
|
596
|
-
'BUILDKITE'
|
|
422
|
+
'GITHUB_ACTIONS', 'GITLAB_CI', 'CIRCLECI', 'TRAVIS',
|
|
423
|
+
'JENKINS_URL', 'TEAMCITY_VERSION', 'BUILDKITE'
|
|
597
424
|
];
|
|
598
|
-
|
|
599
425
|
for (const envVar of ciEnvVars) {
|
|
600
|
-
if (process.env[envVar])
|
|
601
|
-
return true;
|
|
602
|
-
}
|
|
426
|
+
if (process.env[envVar]) return true;
|
|
603
427
|
}
|
|
604
|
-
|
|
605
428
|
return false;
|
|
606
429
|
}
|
|
607
430
|
|
|
608
|
-
/**
|
|
609
|
-
* Get SDK version
|
|
610
|
-
* @returns {string} Version string
|
|
611
|
-
*/
|
|
612
431
|
getVersion() {
|
|
613
432
|
return require('../package.json').version;
|
|
614
433
|
}
|