@aifabrix/builder 2.8.0 ā 2.9.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/integration/hubspot/README.md +136 -0
- package/integration/hubspot/env.template +9 -0
- package/integration/hubspot/hubspot-deploy-company.json +200 -0
- package/integration/hubspot/hubspot-deploy-contact.json +228 -0
- package/integration/hubspot/hubspot-deploy-deal.json +248 -0
- package/integration/hubspot/hubspot-deploy.json +91 -0
- package/integration/hubspot/variables.yaml +17 -0
- package/lib/app-config.js +4 -3
- package/lib/app-deploy.js +8 -20
- package/lib/app-dockerfile.js +7 -9
- package/lib/app-prompts.js +6 -5
- package/lib/app-push.js +9 -9
- package/lib/app-register.js +23 -5
- package/lib/app-rotate-secret.js +10 -0
- package/lib/app-run.js +5 -11
- package/lib/app.js +42 -14
- package/lib/build.js +20 -16
- package/lib/cli.js +61 -2
- package/lib/datasource-deploy.js +14 -20
- package/lib/external-system-deploy.js +123 -40
- package/lib/external-system-download.js +431 -0
- package/lib/external-system-generator.js +13 -10
- package/lib/external-system-test.js +446 -0
- package/lib/generator-builders.js +323 -0
- package/lib/generator.js +200 -292
- package/lib/schema/application-schema.json +853 -852
- package/lib/schema/external-datasource.schema.json +823 -49
- package/lib/schema/external-system.schema.json +96 -78
- package/lib/templates.js +1 -1
- package/lib/utils/cli-utils.js +4 -4
- package/lib/utils/external-system-display.js +159 -0
- package/lib/utils/external-system-validators.js +245 -0
- package/lib/utils/paths.js +151 -1
- package/lib/utils/schema-resolver.js +7 -2
- package/lib/validator.js +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External System Testing Module
|
|
3
|
+
*
|
|
4
|
+
* Provides unit testing (local validation) and integration testing (via dataplane)
|
|
5
|
+
* for external systems and datasources.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview External system testing functionality for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs').promises;
|
|
13
|
+
const fsSync = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const yaml = require('js-yaml');
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const { authenticatedApiCall } = require('./utils/api');
|
|
18
|
+
const { getDeploymentAuth } = require('./utils/token-manager');
|
|
19
|
+
const { getDataplaneUrl } = require('./datasource-deploy');
|
|
20
|
+
const { getConfig } = require('./config');
|
|
21
|
+
const { detectAppType } = require('./utils/paths');
|
|
22
|
+
const externalSystemSchema = require('./schema/external-system.schema.json');
|
|
23
|
+
const externalDataSourceSchema = require('./schema/external-datasource.schema.json');
|
|
24
|
+
const logger = require('./utils/logger');
|
|
25
|
+
const {
|
|
26
|
+
validateFieldMappings,
|
|
27
|
+
validateMetadataSchema,
|
|
28
|
+
validateAgainstSchema
|
|
29
|
+
} = require('./utils/external-system-validators');
|
|
30
|
+
const {
|
|
31
|
+
displayTestResults,
|
|
32
|
+
displayIntegrationTestResults
|
|
33
|
+
} = require('./utils/external-system-display');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Loads and validates external system files
|
|
37
|
+
* @async
|
|
38
|
+
* @param {string} appName - Application name
|
|
39
|
+
* @returns {Promise<Object>} Loaded files and validation results
|
|
40
|
+
*/
|
|
41
|
+
async function loadExternalSystemFiles(appName) {
|
|
42
|
+
const { appPath } = await detectAppType(appName);
|
|
43
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
44
|
+
|
|
45
|
+
// Load variables.yaml
|
|
46
|
+
if (!fsSync.existsSync(variablesPath)) {
|
|
47
|
+
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
51
|
+
let variables;
|
|
52
|
+
try {
|
|
53
|
+
variables = yaml.load(variablesContent);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!variables.externalIntegration) {
|
|
59
|
+
throw new Error('externalIntegration block not found in variables.yaml');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Load system file(s)
|
|
63
|
+
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
64
|
+
const systemFiles = variables.externalIntegration.systems || [];
|
|
65
|
+
const systemJsonFiles = [];
|
|
66
|
+
|
|
67
|
+
for (const systemFile of systemFiles) {
|
|
68
|
+
const systemPath = path.isAbsolute(schemaBasePath)
|
|
69
|
+
? path.join(schemaBasePath, systemFile)
|
|
70
|
+
: path.join(appPath, schemaBasePath, systemFile);
|
|
71
|
+
|
|
72
|
+
if (!fsSync.existsSync(systemPath)) {
|
|
73
|
+
throw new Error(`System file not found: ${systemPath}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const systemContent = await fs.readFile(systemPath, 'utf8');
|
|
77
|
+
let systemJson;
|
|
78
|
+
try {
|
|
79
|
+
systemJson = JSON.parse(systemContent);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw new Error(`Invalid JSON syntax in ${systemFile}: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
systemJsonFiles.push({ path: systemPath, data: systemJson });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Load datasource files
|
|
88
|
+
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
89
|
+
const datasourceJsonFiles = [];
|
|
90
|
+
|
|
91
|
+
for (const datasourceFile of datasourceFiles) {
|
|
92
|
+
const datasourcePath = path.isAbsolute(schemaBasePath)
|
|
93
|
+
? path.join(schemaBasePath, datasourceFile)
|
|
94
|
+
: path.join(appPath, schemaBasePath, datasourceFile);
|
|
95
|
+
|
|
96
|
+
if (!fsSync.existsSync(datasourcePath)) {
|
|
97
|
+
throw new Error(`Datasource file not found: ${datasourcePath}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const datasourceContent = await fs.readFile(datasourcePath, 'utf8');
|
|
101
|
+
let datasourceJson;
|
|
102
|
+
try {
|
|
103
|
+
datasourceJson = JSON.parse(datasourceContent);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error(`Invalid JSON syntax in ${datasourceFile}: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
datasourceJsonFiles.push({ path: datasourcePath, data: datasourceJson });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
variables,
|
|
113
|
+
systemFiles: systemJsonFiles,
|
|
114
|
+
datasourceFiles: datasourceJsonFiles
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Runs unit tests for external system (local validation, no API calls)
|
|
120
|
+
* @async
|
|
121
|
+
* @function testExternalSystem
|
|
122
|
+
* @param {string} appName - Application name
|
|
123
|
+
* @param {Object} options - Test options
|
|
124
|
+
* @param {string} [options.datasource] - Test specific datasource only
|
|
125
|
+
* @param {boolean} [options.verbose] - Show detailed validation output
|
|
126
|
+
* @returns {Promise<Object>} Test results
|
|
127
|
+
* @throws {Error} If testing fails
|
|
128
|
+
*/
|
|
129
|
+
async function testExternalSystem(appName, options = {}) {
|
|
130
|
+
if (!appName || typeof appName !== 'string') {
|
|
131
|
+
throw new Error('App name is required and must be a string');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
logger.log(chalk.blue(`\nš§Ŗ Running unit tests for: ${appName}`));
|
|
136
|
+
|
|
137
|
+
// Load files
|
|
138
|
+
const { variables: _variables, systemFiles, datasourceFiles } = await loadExternalSystemFiles(appName);
|
|
139
|
+
|
|
140
|
+
const results = {
|
|
141
|
+
valid: true,
|
|
142
|
+
errors: [],
|
|
143
|
+
warnings: [],
|
|
144
|
+
systemResults: [],
|
|
145
|
+
datasourceResults: []
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Validate system files
|
|
149
|
+
logger.log(chalk.blue('š Validating system files...'));
|
|
150
|
+
for (const systemFile of systemFiles) {
|
|
151
|
+
const validation = validateAgainstSchema(systemFile.data, externalSystemSchema);
|
|
152
|
+
if (!validation.valid) {
|
|
153
|
+
results.valid = false;
|
|
154
|
+
results.errors.push(`System file ${path.basename(systemFile.path)}: ${validation.errors.join(', ')}`);
|
|
155
|
+
} else {
|
|
156
|
+
results.systemResults.push({
|
|
157
|
+
file: path.basename(systemFile.path),
|
|
158
|
+
valid: true
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validate datasource files
|
|
164
|
+
logger.log(chalk.blue('š Validating datasource files...'));
|
|
165
|
+
const datasourcesToTest = options.datasource
|
|
166
|
+
? datasourceFiles.filter(ds => ds.data.key === options.datasource || path.basename(ds.path).includes(options.datasource))
|
|
167
|
+
: datasourceFiles;
|
|
168
|
+
|
|
169
|
+
for (const datasourceFile of datasourcesToTest) {
|
|
170
|
+
const datasource = datasourceFile.data;
|
|
171
|
+
const datasourceResult = {
|
|
172
|
+
key: datasource.key,
|
|
173
|
+
file: path.basename(datasourceFile.path),
|
|
174
|
+
valid: true,
|
|
175
|
+
errors: [],
|
|
176
|
+
warnings: [],
|
|
177
|
+
fieldMappingResults: null,
|
|
178
|
+
metadataSchemaResults: null
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Validate against schema
|
|
182
|
+
const schemaValidation = validateAgainstSchema(datasource, externalDataSourceSchema);
|
|
183
|
+
if (!schemaValidation.valid) {
|
|
184
|
+
datasourceResult.valid = false;
|
|
185
|
+
datasourceResult.errors.push(...schemaValidation.errors);
|
|
186
|
+
results.valid = false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Validate relationships
|
|
190
|
+
if (systemFiles.length > 0) {
|
|
191
|
+
const systemKey = systemFiles[0].data.key;
|
|
192
|
+
if (datasource.systemKey !== systemKey) {
|
|
193
|
+
datasourceResult.valid = false;
|
|
194
|
+
datasourceResult.errors.push(`systemKey mismatch: expected '${systemKey}', got '${datasource.systemKey}'`);
|
|
195
|
+
results.valid = false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Test with testPayload if available
|
|
200
|
+
if (datasource.testPayload && datasource.testPayload.payloadTemplate) {
|
|
201
|
+
logger.log(chalk.blue(` Testing datasource: ${datasource.key}`));
|
|
202
|
+
|
|
203
|
+
// Validate field mappings
|
|
204
|
+
const fieldMappingResults = validateFieldMappings(datasource, datasource.testPayload);
|
|
205
|
+
datasourceResult.fieldMappingResults = fieldMappingResults;
|
|
206
|
+
if (!fieldMappingResults.valid) {
|
|
207
|
+
datasourceResult.valid = false;
|
|
208
|
+
datasourceResult.errors.push(...fieldMappingResults.errors);
|
|
209
|
+
results.valid = false;
|
|
210
|
+
}
|
|
211
|
+
if (fieldMappingResults.warnings.length > 0) {
|
|
212
|
+
datasourceResult.warnings.push(...fieldMappingResults.warnings);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Validate metadata schema
|
|
216
|
+
const metadataSchemaResults = validateMetadataSchema(datasource, datasource.testPayload);
|
|
217
|
+
datasourceResult.metadataSchemaResults = metadataSchemaResults;
|
|
218
|
+
if (!metadataSchemaResults.valid) {
|
|
219
|
+
datasourceResult.valid = false;
|
|
220
|
+
datasourceResult.errors.push(...metadataSchemaResults.errors);
|
|
221
|
+
results.valid = false;
|
|
222
|
+
}
|
|
223
|
+
if (metadataSchemaResults.warnings.length > 0) {
|
|
224
|
+
datasourceResult.warnings.push(...metadataSchemaResults.warnings);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Compare with expectedResult if provided
|
|
228
|
+
if (datasource.testPayload.expectedResult && fieldMappingResults.mappedFields) {
|
|
229
|
+
// This would require actual transformation execution, which is complex
|
|
230
|
+
// For now, we just note that expectedResult is present
|
|
231
|
+
if (options.verbose) {
|
|
232
|
+
datasourceResult.warnings.push('expectedResult validation not yet implemented (requires transformation engine)');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
datasourceResult.warnings.push('No testPayload.payloadTemplate found - skipping field mapping and metadata schema tests');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
results.datasourceResults.push(datasourceResult);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return results;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
throw new Error(`Failed to run unit tests: ${error.message}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Retries API call with exponential backoff
|
|
250
|
+
* @async
|
|
251
|
+
* @param {Function} fn - Function to retry
|
|
252
|
+
* @param {number} maxRetries - Maximum number of retries
|
|
253
|
+
* @param {number} backoffMs - Initial backoff in milliseconds
|
|
254
|
+
* @returns {Promise<*>} Function result
|
|
255
|
+
*/
|
|
256
|
+
async function retryApiCall(fn, maxRetries = 3, backoffMs = 1000) {
|
|
257
|
+
let lastError;
|
|
258
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
259
|
+
try {
|
|
260
|
+
return await fn();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
lastError = error;
|
|
263
|
+
if (attempt < maxRetries) {
|
|
264
|
+
const delay = backoffMs * Math.pow(2, attempt);
|
|
265
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
throw lastError;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Calls pipeline test endpoint
|
|
274
|
+
* @async
|
|
275
|
+
* @param {string} systemKey - System key
|
|
276
|
+
* @param {string} datasourceKey - Datasource key
|
|
277
|
+
* @param {Object} payloadTemplate - Test payload template
|
|
278
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
279
|
+
* @param {Object} authConfig - Authentication configuration
|
|
280
|
+
* @param {number} timeout - Request timeout in milliseconds
|
|
281
|
+
* @returns {Promise<Object>} Test response
|
|
282
|
+
*/
|
|
283
|
+
async function callPipelineTestEndpoint(systemKey, datasourceKey, payloadTemplate, dataplaneUrl, authConfig, timeout = 30000) {
|
|
284
|
+
const endpoint = `${dataplaneUrl}/api/v1/pipeline/${systemKey}/${datasourceKey}/test`;
|
|
285
|
+
|
|
286
|
+
const response = await retryApiCall(async() => {
|
|
287
|
+
return await authenticatedApiCall(
|
|
288
|
+
endpoint,
|
|
289
|
+
{
|
|
290
|
+
method: 'POST',
|
|
291
|
+
body: JSON.stringify({ payloadTemplate }),
|
|
292
|
+
timeout
|
|
293
|
+
},
|
|
294
|
+
authConfig.token
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (!response.success || !response.data) {
|
|
299
|
+
throw new Error(`Test endpoint failed: ${response.error || response.formattedError || 'Unknown error'}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return response.data.data || response.data;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Runs integration tests via dataplane pipeline API
|
|
307
|
+
* @async
|
|
308
|
+
* @function testExternalSystemIntegration
|
|
309
|
+
* @param {string} appName - Application name
|
|
310
|
+
* @param {Object} options - Test options
|
|
311
|
+
* @param {string} [options.datasource] - Test specific datasource only
|
|
312
|
+
* @param {string} [options.payload] - Path to custom test payload file
|
|
313
|
+
* @param {string} [options.environment] - Environment (dev, tst, pro)
|
|
314
|
+
* @param {string} [options.controller] - Controller URL
|
|
315
|
+
* @param {boolean} [options.verbose] - Show detailed test output
|
|
316
|
+
* @param {number} [options.timeout] - Request timeout in milliseconds
|
|
317
|
+
* @returns {Promise<Object>} Integration test results
|
|
318
|
+
* @throws {Error} If testing fails
|
|
319
|
+
*/
|
|
320
|
+
async function testExternalSystemIntegration(appName, options = {}) {
|
|
321
|
+
if (!appName || typeof appName !== 'string') {
|
|
322
|
+
throw new Error('App name is required and must be a string');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
logger.log(chalk.blue(`\nš Running integration tests for: ${appName}`));
|
|
327
|
+
|
|
328
|
+
// Load files
|
|
329
|
+
const { variables: _variables, systemFiles, datasourceFiles } = await loadExternalSystemFiles(appName);
|
|
330
|
+
|
|
331
|
+
if (systemFiles.length === 0) {
|
|
332
|
+
throw new Error('No system files found');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const systemKey = systemFiles[0].data.key;
|
|
336
|
+
|
|
337
|
+
// Get authentication
|
|
338
|
+
const config = await getConfig();
|
|
339
|
+
const environment = options.environment || 'dev';
|
|
340
|
+
const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
|
|
341
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
342
|
+
|
|
343
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
344
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get dataplane URL
|
|
348
|
+
logger.log(chalk.blue('š Getting dataplane URL from controller...'));
|
|
349
|
+
const dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
|
|
350
|
+
logger.log(chalk.green(`ā Dataplane URL: ${dataplaneUrl}`));
|
|
351
|
+
|
|
352
|
+
// Determine datasources to test
|
|
353
|
+
const datasourcesToTest = options.datasource
|
|
354
|
+
? datasourceFiles.filter(ds => ds.data.key === options.datasource || path.basename(ds.path).includes(options.datasource))
|
|
355
|
+
: datasourceFiles;
|
|
356
|
+
|
|
357
|
+
if (datasourcesToTest.length === 0) {
|
|
358
|
+
throw new Error('No datasources found to test');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const results = {
|
|
362
|
+
success: true,
|
|
363
|
+
systemKey,
|
|
364
|
+
datasourceResults: []
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Load custom payload if provided
|
|
368
|
+
let customPayload = null;
|
|
369
|
+
if (options.payload) {
|
|
370
|
+
const payloadPath = path.isAbsolute(options.payload) ? options.payload : path.join(process.cwd(), options.payload);
|
|
371
|
+
const payloadContent = await fs.readFile(payloadPath, 'utf8');
|
|
372
|
+
customPayload = JSON.parse(payloadContent);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Test each datasource
|
|
376
|
+
for (const datasourceFile of datasourcesToTest) {
|
|
377
|
+
const datasource = datasourceFile.data;
|
|
378
|
+
const datasourceKey = datasource.key;
|
|
379
|
+
|
|
380
|
+
logger.log(chalk.blue(`\nš” Testing datasource: ${datasourceKey}`));
|
|
381
|
+
|
|
382
|
+
// Determine payload to use
|
|
383
|
+
let payloadTemplate;
|
|
384
|
+
if (customPayload) {
|
|
385
|
+
payloadTemplate = customPayload;
|
|
386
|
+
} else if (datasource.testPayload && datasource.testPayload.payloadTemplate) {
|
|
387
|
+
payloadTemplate = datasource.testPayload.payloadTemplate;
|
|
388
|
+
} else {
|
|
389
|
+
logger.log(chalk.yellow(` ā No test payload found for ${datasourceKey}, skipping...`));
|
|
390
|
+
results.datasourceResults.push({
|
|
391
|
+
key: datasourceKey,
|
|
392
|
+
skipped: true,
|
|
393
|
+
reason: 'No test payload available'
|
|
394
|
+
});
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const testResponse = await callPipelineTestEndpoint(
|
|
400
|
+
systemKey,
|
|
401
|
+
datasourceKey,
|
|
402
|
+
payloadTemplate,
|
|
403
|
+
dataplaneUrl,
|
|
404
|
+
authConfig,
|
|
405
|
+
parseInt(options.timeout, 10) || 30000
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const datasourceResult = {
|
|
409
|
+
key: datasourceKey,
|
|
410
|
+
skipped: false,
|
|
411
|
+
success: testResponse.success !== false,
|
|
412
|
+
validationResults: testResponse.validationResults || {},
|
|
413
|
+
fieldMappingResults: testResponse.fieldMappingResults || {},
|
|
414
|
+
endpointTestResults: testResponse.endpointTestResults || {}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
if (!datasourceResult.success) {
|
|
418
|
+
results.success = false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
results.datasourceResults.push(datasourceResult);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
results.success = false;
|
|
424
|
+
results.datasourceResults.push({
|
|
425
|
+
key: datasourceKey,
|
|
426
|
+
skipped: false,
|
|
427
|
+
success: false,
|
|
428
|
+
error: error.message
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return results;
|
|
434
|
+
} catch (error) {
|
|
435
|
+
throw new Error(`Failed to run integration tests: ${error.message}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = {
|
|
440
|
+
testExternalSystem,
|
|
441
|
+
testExternalSystemIntegration,
|
|
442
|
+
displayTestResults,
|
|
443
|
+
displayIntegrationTestResults,
|
|
444
|
+
callPipelineTestEndpoint,
|
|
445
|
+
retryApiCall
|
|
446
|
+
};
|