UNPKG

dave-dredd

Version:
280 lines (279 loc) 11.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const clone_1 = __importDefault(require("clone")); const uuid_1 = require("uuid"); const os_1 = __importDefault(require("os")); const request_1 = __importDefault(require("request")); const logger_1 = __importDefault(require("../logger")); const reporterOutputLogger_1 = __importDefault(require("./reporterOutputLogger")); const package_json_1 = __importDefault(require("../../package.json")); const CONNECTION_ERRORS = [ 'ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE', ]; function ApiaryReporter(emitter, stats, config, runner) { this.type = 'apiary'; this.stats = stats; this.uuid = null; this.startedAt = null; this.endedAt = null; this.remoteId = null; this.config = config; this.runner = runner; this.reportUrl = null; this.errors = []; this.serverError = false; this.configuration = { apiUrl: this._get('apiaryApiUrl', 'APIARY_API_URL', 'https://api.apiary.io').replace(/\/$/, ''), apiToken: this._get('apiaryApiKey', 'APIARY_API_KEY', null), apiSuite: this._get('apiaryApiName', 'APIARY_API_NAME', null), }; this.configureEmitter(emitter); logger_1.default.debug(`Using '${this.type}' reporter.`); if (!this.configuration.apiToken && !this.configuration.apiSuite) { logger_1.default.warn(` Apiary API Key or API Project Name were not provided. Configure Dredd to be able to save test reports alongside your Apiary API project: https://dredd.org/en/latest/how-to-guides/#using-apiary-reporter-and-apiary-tests `); } if (!this.configuration.apiSuite) { this.configuration.apiSuite = 'public'; } } // THIS IS HIIIIGHWAY TO HELL, HIIIIIGHWAY TO HELL. Everything should have one single interface ApiaryReporter.prototype._get = function _get(customProperty, envProperty, defaultVal) { let returnVal = defaultVal; // This will be deprecated if (this.config.custom && this.config.custom[customProperty]) { returnVal = this.config.custom[customProperty]; // This will be the ONLY supported way how to configure this reporter } else if (this.config.custom && this.config.custom[customProperty]) { returnVal = this.config.custom[customProperty]; // This will be deprecated } else if (this.config.custom && this.config.custom.apiaryReporterEnv && this.config.custom.apiaryReporterEnv[customProperty]) { returnVal = this.config.custom.apiaryReporterEnv[customProperty]; // This will be deprecated } else if (this.config.custom && this.config.custom.apiaryReporterEnv && this.config.custom.apiaryReporterEnv[envProperty]) { returnVal = this.config.custom.apiaryReporterEnv[envProperty]; // This will be supported for backward compatibility, but can be removed in future. } else if (process.env[envProperty]) { returnVal = process.env[envProperty]; } return returnVal; }; ApiaryReporter.prototype._getKeys = function _getKeys() { let returnKeys = []; returnKeys = returnKeys.concat(Object.keys((this.config.custom && this.config.custom.apiaryReporterEnv) || {})); return returnKeys.concat(Object.keys(process.env)); }; ApiaryReporter.prototype.configureEmitter = function configureEmitter(emitter) { emitter.on('start', (apiDescriptions, callback) => { if (this.serverError === true) { return callback(); } this.uuid = uuid_1.v4(); this.startedAt = Math.round(new Date().getTime() / 1000); // Cycle through all keys from // - config.custom.apiaryReporterEnv // - process.env keys const ciVars = /^(TRAVIS|CIRCLE|CI|DRONE|BUILD_ID)/; const envVarNames = this._getKeys(); const ciEnvVars = {}; for (const envVarName of envVarNames) { if (envVarName.match(ciVars)) { ciEnvVars[envVarName] = this._get(envVarName, envVarName); } } // Transform blueprints data to array const data = { blueprints: apiDescriptions.map((apiDescription) => ({ filename: apiDescription.location, raw: apiDescription.content, annotations: apiDescription.annotations, })), endpoint: this.config.server, agent: this._get('dreddAgent', 'DREDD_AGENT') || this._get('user', 'USER'), agentRunUuid: this.uuid, hostname: this._get('dreddHostname', 'DREDD_HOSTNAME') || os_1.default.hostname(), startedAt: this.startedAt, public: true, status: 'running', agentEnvironment: ciEnvVars, }; if (this.configuration.apiToken && this.configuration.apiSuite) { data.public = false; } const path = `/apis/${this.configuration.apiSuite}/tests/runs`; this._performRequestAsync(path, 'POST', data, (error, response, parsedBody) => { if (error) { callback(error); } else { this.remoteId = parsedBody._id; if (parsedBody.reportUrl) { this.reportUrl = parsedBody.reportUrl; } callback(); } }); }); emitter.on('test pass', this._createStep.bind(this)); emitter.on('test fail', this._createStep.bind(this)); emitter.on('test skip', this._createStep.bind(this)); emitter.on('test error', (error, test, callback) => { if (this.serverError === true) { return callback(); } const data = this._transformTestToReporter(test); if (Array.from(CONNECTION_ERRORS).includes(error.code)) { data.results.errors.push({ severity: 'error', message: 'Error connecting to server under test!', }); } else { data.results.errors.push({ severity: 'error', message: 'Unhandled error occured when executing the transaction.', }); } const path = `/apis/${this.configuration.apiSuite}/tests/steps?testRunId=${this.remoteId}`; this._performRequestAsync(path, 'POST', data, (err) => { if (err) { return callback(err); } callback(); }); }); emitter.on('end', (callback) => { if (this.serverError === true) { return callback(); } const data = { endedAt: Math.round(new Date().getTime() / 1000), result: this.stats, status: this.stats.failures > 0 || this.stats.errors > 0 ? 'failed' : 'passed', logs: this.runner && this.runner.logs && this.runner.logs.length ? this.runner.logs : undefined, }; const path = `/apis/${this.configuration.apiSuite}/tests/run/${this.remoteId}`; this._performRequestAsync(path, 'PATCH', data, (error) => { if (error) { return callback(error); } const reportUrl = this.reportUrl || `https://app.apiary.io/${this.configuration.apiSuite}/tests/run/${this.remoteId}`; reporterOutputLogger_1.default.complete(`See results in Apiary at: ${reportUrl}`); callback(); }); }); }; ApiaryReporter.prototype._createStep = function _createStep(test, callback) { if (this.serverError === true) { return callback(); } const data = this._transformTestToReporter(test); const path = `/apis/${this.configuration.apiSuite}/tests/steps?testRunId=${this.remoteId}`; this._performRequestAsync(path, 'POST', data, (error) => { if (error) { return callback(error); } callback(); }); }; ApiaryReporter.prototype._performRequestAsync = function _performRequestAsync(path, method, reqBody, callback) { const handleRequest = (err, res, resBody) => { let parsedBody; if (err) { this.serverError = true; logger_1.default.debug('Requesting Apiary API errored:', `${err}` || err.code); if (Array.from(CONNECTION_ERRORS).includes(err.code)) { return callback(new Error('Apiary reporter could not connect to Apiary API')); } return callback(err); } logger_1.default.debug('Handling HTTP response from Apiary API'); try { parsedBody = JSON.parse(resBody); } catch (error) { this.serverError = true; err = new Error(` Apiary reporter failed to parse Apiary API response body: ${error.message}\n${resBody} `); return callback(err); } const info = { headers: res.headers, statusCode: res.statusCode, body: parsedBody, }; logger_1.default.debug('Apiary reporter response:', JSON.stringify(info, null, 2)); callback(null, res, parsedBody); }; const body = reqBody ? JSON.stringify(reqBody) : ''; const system = `${os_1.default.type()} ${os_1.default.release()}; ${os_1.default.arch()}`; const headers = { 'User-Agent': `Dredd Apiary Reporter/${package_json_1.default.version} (${system})`, 'Content-Type': 'application/json', }; const options = clone_1.default(this.config.http || {}); options.uri = this.configuration.apiUrl + path; options.method = method; options.headers = headers; options.body = body; if (this.configuration.apiToken) { options.headers.Authentication = `Token ${this.configuration.apiToken}`; } try { const protocol = options.uri.split(':')[0].toUpperCase(); logger_1.default.debug(` About to perform an ${protocol} request from Apiary reporter to Apiary API: ${options.method} ${options.uri} \ (${body ? 'with' : 'without'} body) `); logger_1.default.debug('Request details:', JSON.stringify({ options, body }, null, 2)); return request_1.default(options, handleRequest); } catch (error) { this.serverError = true; logger_1.default.debug('Requesting Apiary API errored:', error); return callback(error); } }; ApiaryReporter.prototype._transformTestToReporter = function _transformTestToReporter(test) { return { testRunId: this.remoteId, origin: test.origin, duration: test.duration, result: test.status, startedAt: test.startedAt, results: { request: test.request, realResponse: test.actual, expectedResponse: test.expected, validationResult: test.results || {}, errors: test.errors || [], }, }; }; exports.default = ApiaryReporter;