straal
Version:
Wrapper for Xray's REST API
629 lines (511 loc) • 19.7 kB
JavaScript
/**
* Wrapper for Xray's REST API
*
* @author João Galego
* @copyright 2018
* @license MIT
*/
/**
* Module Dependencies
*/
var JiraClient = require('jira-connector');
var oauth = require('oauth');
var request = require('request');
var url = require('url');
var fs = require('fs');
var junit = require('./junit');
/**
* XrayApi
*/
var XrayApi = exports.XrayApi = function (protocol, host, port, username, password, apiVersion, xrayProperties, verbose, strictSSL, oauth, base) {
this.protocol = protocol;
this.host = host;
this.port = port;
this.username = username;
this.password = password;
this.apiVersion = apiVersion;
this.base = base;
// Jira
this.jira = new JiraClient({
protocol: this.protocol,
host: this.host,
port: this.port,
basic_auth: {
username: this.username,
password: this.password
}
});
// SSL
if (strictSSL == null) {
strictSSL = true;
}
this.strictSSL = strictSSL;
// makeUri
this.makeUri = function (pathname, altBase, altApiVersion) {
// Setup base path
var basePath = 'rest/raven/';
if (altBase != null) {
basePath = altBase;
}
if (this.base) {
basePath = this.base + '/' + basePath;
}
// Setup API version
var apiVersion = this.apiVersion;
if (altApiVersion != null) {
apiVersion = altApiVersion;
}
// Format URL
var uri = url.format({
protocol: this.protocol,
hostname: this.host,
port: this.port,
pathname: basePath + apiVersion + pathname
});
return decodeURIComponent(uri);
};
// doRequest
this.doRequest = function (options, callback) {
if (oauth && oauth.consumer_key && oauth.consumer_secret) {
options.oauth = {
consumer_key: oauth.consumer_key,
consumer_secret: oauth.consumer_secret,
token: oauth.access_token,
token_secret: oauth.access_token_secret
};
} else if (this.username && this.password) {
options.auth = {
'user': this.username,
'pass': this.password
};
}
// Make the request
request(options, callback);
};
};
(function () {
/**
* Exports test execution results from JIRA
*
* @param {String} testExecKey the test execution to export the test runs
* @param {Object} callback
*/
this.exportTestExecutionResults = function (testExecKey, callback) {
var options = {
rejectUnauthorized: this.strictSSL,
uri: this.makeUri('/testruns?testExecKey=' + testExecKey),
method: 'GET'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 400) {
callback('Please check Jira log.');
return;
}
if (response.statusCode === 401) {
callback('The Xray license is not valid.');
return;
}
if (response.statusCode === 500) {
callback('An internal error occurred when generating the output file.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during exportTestExecutionResults.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(body));
});
};
/**
* Imports test execution results in JSON format to JIRA
*
* @param {String} filePath path to the JSON file
* @param {Object} callback
*/
this.importResults = function (filePath, callback) {
var options = {
rejectUnauthorized: this.strictSSL,
body: fetch(filePath).then(response => response.json()),
uri: this.makeUri('/import/execution'),
json: true,
contentType: 'application/json',
method: 'POST'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 400) {
callback('No execution results where provided.');
return;
}
if (response.statusCode === 401) {
callback('The Xray license is not valid.');
return;
}
if (response.statusCode === 500) {
callback('An internal error occurred when importing execution results.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during importResults.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(body));
});
};
/**
* Imports test execution results in JUnit's XML format to JIRA
*
* @param {String} file a MultipartFormParam containing a XML file to import
* @param {String} projectKey key of the project where the test execution (if the testExecKey parameter wasn't provided) and the tests (if they aren't created yet) are going to be created
* @param {String} testPlanKey key of the Test Plan; if you specify the Test Plan, the Tests will be added automatically to the Test Plan if they're not part of it
* @param {String} fixVersion the Fix Version associated with the test execution (it supports only one value)
* @param {Object} callback
*/
this.importJunitResults = function (file, projectKey, testPlanKey, callback) {
var formData = {
file: fs.createReadStream(file),
};
var options = {
rejectUnauthorized: this.strictSSL,
uri: this.makeUri('/import/execution/junit?projectKey=' + projectKey + '&testPlanKey=' + testPlanKey),
contentType: 'multipart/form-data',
formData: formData,
method: 'POST'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 400) {
callback('Please check Jira log.');
return;
}
if (response.statusCode === 401) {
callback('The Xray license is not valid.');
return;
}
if (response.statusCode === 500) {
callback('An internal error occurred when importing execution results.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during importJunitXmlResults.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(body));
});
};
/**
* Get project info
*
* @param {String} projectKey The project key.
* @param {Object} callback
*/
this.getProjectInfo = function (projectKey, callback) {
var options = {
rejectUnauthorized: this.strictSSL,
uri: this.makeUri('/project/' + projectKey, 'rest/api/', '2'),
contentType: 'application/json',
method: 'GET'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during getProjectInfo.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(body));
});
};
/**
* Get issue information
*
* @param {String} issueIdOrKey The issue ID or key.
* @param {Object} callback
*/
this.getIssueInfo = function (issueIdOrKey, callback) {
var options = {
rejectUnauthorized: this.strictSSL,
uri: this.makeUri('/issue/' + issueIdOrKey, 'rest/api/', '2'),
contentType: 'application/json',
method: 'GET'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 404) {
callback(response.statusCode + ': Please check if the issue exists.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during getIssueInfo.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(body));
});
};
/**
* Get test run
*
* @param {String} testExecKey The test execution key.
* @param {String} testKey The test key.
* @param {Object} callback
*/
this.getTestRun = function (testExecKey, testKey, callback) {
var options = {
rejectUnauthorized: this.strictSSL,
uri: this.makeUri('/api/testrun?testExecIssueKey=' + testExecKey + '&testIssueKey=' + testKey),
contentType: 'application/json',
method: 'GET'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 401) {
callback(response.statusCode + ': The Xray license is not valid.');
return;
}
if (response.statusCode === 500) {
callback(response.statusCode + ': An internal error occurred getting the test run.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during getTestRun.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(body));
});
};
/**
* Creates a Test Plan issue
*
* @param {Integer} projectId The project ID.
* @param {String} issueSummary The test plan summary
* @param {String} issueDescription The test plan description
* @param {Object} callback
*/
this.createTestPlan = function (projectId, issueSummary, issueDescription, callback) {
var testPlanIssue = {fields: {project: {id: projectId}, summary: issueSummary, description: issueDescription, issuetype: {name: 'Test Plan'}}};
this.jira.issue.createIssue(testPlanIssue, callback);
};
/**
* Creates a Test Set issue
*
* @param {Integer} projectId The project ID.
* @param {String} issueSummary The test set summary
* @param {String} issueDescription The test set description
* @param {Object} callback
*/
this.createTestSet = function (projectId, issueSummary, issueDescription, callback) {
var testSetIssue = {fields: {project: {id: projectId}, summary: issueSummary, description: issueDescription, issuetype: {name: 'Test Set'}}};
this.jira.issue.createIssue(testSetIssue, callback);
};
/**
* Creates a Test Execution issue
*
* @param {Integer} projectId The project ID.
* @param {String} issueSummary The test execution summary
* @param {String} issueDescription The test execution description
* @param {Object} callback
*/
this.createTestExecution = function (projectId, issueSummary, issueDescription, callback) {
var testExecutionIssue = {fields: {project: {id: projectId}, summary: issueSummary, description: issueDescription, issuetype: {name: 'Test Execution'}}};
this.jira.issue.createIssue(testExecutionIssue, callback);
};
/**
* Creates a Test issue
*
* @param {Integer} projectId The project ID.
* @param {String} issueSummary The test summary
* @param {String} issueDescription The test description
* @param {Object} callback
*/
this.createTest = function (projectId, issueSummary, issueDescription, callback) {
// Custom fields
var defaultValueTestType = this.xrayProperties['default_values']['test_testType'];
var customFieldTestType = this.xrayProperties['custom_fields']['test_testType'];
var customFieldTestDefinition = this.xrayProperties['custom_fields']['test_genericTestDefinition'];
var testIssue = {fields: {project: {id: projectId}, summary: issueSummary, description: issueDescription, issuetype: {name: 'Test'}}};
if(defaultValueTestType && customFieldTestType && customFieldTestDefinition) {
testIssue['fields'][customFieldTestType] = {value: defaultValueTestType};
testIssue['fields'][customFieldTestDefinition] = issueDescription;
} else {
console.log("Missing custom fields and/or default values in Xray properties. Creating manual tests instead");
}
this.jira.issue.createIssue(testIssue, callback);
};
/**
* Save Test run
*
* Note: Use <a href="https://js-joda.github.io/js-joda/>js-joda</a> to convert a javascript Date or moment into a LocalDateTime object.
*
* @param {String} testExecutionKey The test execution key
* @param {String} testKey The test key
* @param {LocalDateTime} startDate The start date of the test run
* @param {LocalDateTime} finishDate The finish date of the test run
* @param {String} status The test run status e.g. PASS, FAIL
* @param {String} comment A string comment
* @param {Object} callback
*/
this.saveTestRun = function (testExecutionKey, testKey, startDate, finishDate, status, comment, callback) {
var mTestRunIssue = {testExecutionKey: testExecutionKey, tests: [{testKey: testKey, start: startDate, finish: finishDate, comment: comment, status: status}]};
var options = {
rejectUnauthorized: this.strictSSL,
body: mTestRunIssue,
uri: this.makeUri('/import/execution'),
json: true,
contentType: 'application/json',
method: 'POST'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 400) {
callback('No execution results where provided.');
return;
}
if (response.statusCode === 401) {
callback('The Xray license is not valid.');
return;
}
if (response.statusCode === 500) {
callback('An internal error occurred when importing execution results.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during importResults.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(JSON.stringify(body)));
});
};
/**
* Add evidence to test run.
*
* @param {String} testRunId The test run id.
* @param {String} data Base64 file enconding of the evidence data.
* @param {String} filename A filename for the evidence data e.g. evidence.jpg
* @param {String} contentType The content type of the evidence data e.g. image/jpeg
* @param {Object} callback
*/
this.addEvidenceToTestRun = function (testRunId, data, filename, contentType, callback) {
var attachment = {data: data, filename: filename, contentType: contentType};
var options = {
rejectUnauthorized: this.strictSSL,
body: attachment,
uri: this.makeUri('/api/testrun/' + testRunId + '/attachment'),
json: true,
contentType: 'application/json',
method: 'POST'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 401) {
callback('The Xray license is not valid.');
return;
}
if (response.statusCode === 500) {
callback('An internal error occurred when inserting the evidences.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during addEvidenceToTestRun.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(body));
});
};
/**
* Run JQL query.
*
* @param {String} jqlQuery A JQL query.
* @param {Object} callback
*/
this.runJqlQuery = function (jqlQuery, callback) {
// Encode unsupported characters in the JQL query so it can be used in the URI
// For more information, please check RFC 3986
jqlQuery = jqlQuery.replace(/\s/g, "%20").replace(/=/g, "%3D");
var options = {
rejectUnauthorized: this.strictSSL,
uri: this.makeUri('/search?jql=' + jqlQuery, '/rest/api/', '2'),
json: true,
contentType: 'application/json',
method: 'GET'
};
this.doRequest(options, function (error, response, body) {
if (error) {
callback(error, null);
return;
}
if (response.statusCode === 401) {
callback('The Xray license is not valid.');
return;
}
if (response.statusCode === 500) {
callback('An internal error occurred when running JQL query.');
return;
}
if (response.statusCode !== 200) {
callback(response.statusCode + ': Unable to connect to JIRA during runJqlQuery.');
return;
}
if (body === undefined) {
callback('Response body was undefined.');
return;
}
callback(null, JSON.parse(JSON.stringify(body)));
});
};
}).call(XrayApi.prototype);