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