Issue-ID: SDC-2483

Adding https support for cucumber tests and slight refactoring

Signed-off-by: ilanap <ilanap@amdocs.com>
Change-Id: Ib772d18cd4278238571daf54bcb6372c553d6e4b
diff --git a/cucumber-js-test-apis-ci/cucumber-common/package.json b/cucumber-js-test-apis-ci/cucumber-common/package.json
new file mode 100644
index 0000000..1efc8f1
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/package.json
@@ -0,0 +1,37 @@
+{
+  "name": "cucumber-common",
+  "version": "1.0.14",
+  "description": "Cucumber common methods and utilities",
+  "repository": "",
+  "main": "index.js",
+  "directories": {
+    "doc": "docs"
+  },
+  "scripts": {
+    "test": "cucumber-js",
+    "test-and-report": "npm-run-all -c -s test cucumber-html-report",
+    "cucumber-html-report": "node plugins/reporter.js",
+    "cucumber-docs": "jsdoc ./stepDefinitions  -c plugins/jsdoc_config.json --readme plugins/README.md"
+  },
+  "author": "",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "assert": "^1.4.1",
+    "btoa": "^1.2.1",
+    "cucumber": "^5.1.0",
+    "cucumber-html-reporter": "^4.0.4",
+    "docdash": "^1.0.2",
+    "find-up": "^4.1.0",
+    "jsdoc": "^3.5.5",
+    "jsdoc-one-page": "0.0.5",
+    "lodash": "^4.17.11",
+    "md5": "^2.2.1",
+    "needle": "^2.4.0",
+    "node-zip": "^1.1.1",
+    "normalize-newline": "^3.0.0",
+    "npm-run-all": "^4.1.2",
+    "request": "^2.83.0",
+    "yamljs": "^0.3.0"
+  },
+  "devDependencies": {}
+}
diff --git a/cucumber-js-test-apis-ci/cucumber-common/plugins/README.md b/cucumber-js-test-apis-ci/cucumber-common/plugins/README.md
new file mode 100644
index 0000000..9eaeb8f
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/plugins/README.md
@@ -0,0 +1,30 @@
+<br>
+<h1>Welcome!</h1>
+This is the documentation for using the BDD testing framework for SDC.<br>
+The Modules on the left contains all steps for particalar aress and/or explanations of what they do.<br>
+<br><br>
+<h3>How to set the server configuration</h3>
+<li> Copy the config.json to devConfig.json
+<li> Replace the server and user values with the correct values
+<h3>How to run with Maven</h3>
+<li>"mvn clean install" will install npm if needed, download all modules
+<li> run "mvn install -DskipTests=true -P dev" to create the documentation under the "docs" folder
+<li>"mvn test -P dev" will run all tests in the features folder and create an HTML report under the "reports" folder
+<h3>How to develop tests</h3>
+You can open the project in IntelliJ and Webstorm to run and develop scenarios.<br>
+<li><b>You will need to install the Cucumber.Js plugin</b> In order to install, go to "Settings/Plugins". If cucumber.js in not on the list, go to "Browse repositories.." and install .
+<li>First time only: Right click on feature file and try to run. Now go to "Run/edit configurations" and set the "executable path" to the "node_modules\.bin\cucumber-js.cmd" under your current project.
+<li>Now you can run the feature files by right clicking on the file and selecting "Run" from IDEA.<br>
+<li>Add to existing scenarios or create new files under the "features" directory for additional tests
+<br>
+<li>You can also run a specific test from the command line by running "npm run test -- [features/path to file]
+<h3>More Information</h3>
+<li> More on <a href="https://cucumber.io/docs/reference">Cucumber</a>
+<li> More on <a herf="https://github.com/cucumber/cucumber/wiki/Gherkin">Gherkin</a>
+<li> More on <a href="https://github.com/cucumber/cucumber-js">Cucumber-js</a>
+<br>
+<h3>How to run the docker</h3>
+<li>"mvn clean install -P docker" will create the docker images
+<li>the "docker_run.sh" script will start all ONAP images and run the cucumber docker against them
+<li> environment variables that can be set to change the server/version: IMAGES_TAG (default 1.4-STAGING-latest), TEST_CI_BE_HOST (deafult - machine IP), TEST_CI_CATALOG_PORT (default 8080)
+
diff --git a/cucumber-js-test-apis-ci/cucumber-common/plugins/jsdoc_config.json b/cucumber-js-test-apis-ci/cucumber-common/plugins/jsdoc_config.json
new file mode 100644
index 0000000..2757000
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/plugins/jsdoc_config.json
@@ -0,0 +1,16 @@
+{
+  "tags": {
+    "allowUnknownTags": true
+  },
+  "templates": {
+    "default": {
+      "outputSourceFiles": false
+    }
+  },
+
+  "plugins": ["./steps"],
+  "opts": {
+    "template": "node_modules/jsdoc-one-page",
+    "destination": "docs/"
+  }
+}
\ No newline at end of file
diff --git a/cucumber-js-test-apis-ci/cucumber-common/plugins/reporter.js b/cucumber-js-test-apis-ci/cucumber-common/plugins/reporter.js
new file mode 100644
index 0000000..8913789
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/plugins/reporter.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var reporter = require('cucumber-html-reporter');
+
+var options = {
+	theme: 'bootstrap',
+	jsonFile: 'report/report.json',
+	output: 'report/report.html',
+	reportSuiteAsScenarios: true,
+	launchReport: false,
+	metadata: {
+		"ONAP" : "Some build",
+		"Executed": "Local"
+	}
+};
+
+reporter.generate(options);
+
diff --git a/cucumber-js-test-apis-ci/cucumber-common/plugins/steps.js b/cucumber-js-test-apis-ci/cucumber-common/plugins/steps.js
new file mode 100644
index 0000000..2faa7ef
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/plugins/steps.js
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * @module plugins/steptag
+ */
+'use strict';
+
+
+exports.handlers = {
+	/**
+	 * Support @step tag.
+	 *
+	 * @step description
+	 */
+	newDoclet: function(e) {
+		var tags = e.doclet.tags;
+		var tag;
+		var value;
+
+		// any user-defined tags in this doclet?
+		if (typeof tags !== 'undefined') {
+
+			tags = tags.filter(function($) {
+				return $.title === 'step' || $.title === 'examplefile';
+			});
+
+			if (tags.length) {
+				// take the first one
+				tag = tags[0];
+				let step = null;
+				let exampleFile = null;
+				for (tag in tags) {
+					if (tags[tag].title === "step") {
+						step = "<b>" + tags[tag].value + "</b><br>";
+					}
+					if (tags[tag].title === "examplefile") {
+						exampleFile = "<i> Example Features File: " + tags[tag].value + "</i><br>";
+					}
+				}
+				if (exampleFile !== null) {
+					step += exampleFile;
+				}
+				e.doclet.meta = e.doclet.meta || {};
+				if (e.doclet.description !== undefined) {
+					e.doclet.description =  step +  e.doclet.description;
+				} else {
+					e.doclet.description =  step;
+				}
+			}
+		}
+	}
+};
\ No newline at end of file
diff --git a/cucumber-js-test-apis-ci/cucumber-common/stepDefinitions/world.js b/cucumber-js-test-apis-ci/cucumber-common/stepDefinitions/world.js
new file mode 100644
index 0000000..7811aba
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/stepDefinitions/world.js
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const { setWorldConstructor } = require('cucumber');
+const _ = require('lodash');
+const findUp = require('find-up');
+
+
+
+let configPath = findUp.sync('config.json');
+configPath = configPath.replace(/\\/g, '/');
+
+let config = require(configPath);
+let localConfig = {};
+try {
+	let devConfigPath = findUp.sync('devConfig.json');
+	devConfigPath = devConfigPath.replace(/\\/g, '/');
+	localConfig = require(devConfigPath);
+} catch (e) {
+	try {
+		let envdir = findUp.sync('environments', {type: 'directory'});
+		envdir = envdir.replace(/\\/g, '/');
+		localConfig = require(envdir + '/dockerConfig.json');
+	} catch (e) {
+		console.error("no env configuration was found!");
+	}
+}
+
+config = _.merge(config, localConfig);
+var {setDefaultTimeout} = require('cucumber');
+
+
+/**
+ * @module Context
+ * @description Context that is used per feature file and can be accessed as 'this.context' in all steps.<Br>
+ *     This class can be extended in order to add additional configurations.
+ *<Br>
+ * Contains the following items:<br>
+ * <li>this.context.server <ul>REST server and onboarding prefix including version. set either in configuration file or from the command line or SERVER environment variable</ul>
+ * <li>this.context.item <ul>When a VLM or VSP has been created, this has the an id and versionId set to the correct IDs.</ul>
+ * <li>this.context <ul>Object with properties that were saved in the steps.</ul>
+ * <li>this.context.inputdata <ul><b>Automatically updated with the last responseData from the Rest call</b><br>Object with properties that were prepares in the steps.</ul>
+ * <li>this.context.responseData <ul>Response from the last REST call.</ul>
+ **/
+class CustomWorld {
+	constructor(options) {
+		this.context = {};
+		this.context.headers = {};
+		let typeName;
+
+		this.context.defaultServerType = 'main';
+		for (typeName in config) {
+			this.context.headers[typeName] = {};
+			if (config[typeName].user) {
+				this.context.headers[typeName]['USER_ID'] = config[typeName].user;
+			}
+			// adding additional headers
+			if (config[typeName].additionalHeaders) {
+				_.assign(this.context.headers[typeName] , config[typeName].additionalHeaders);
+			}
+			if (config[typeName].isDefault !== undefined && config[typeName].isDefault) {
+				this.context.defaultServerType = typeName;
+			}
+		}
+		this.context.item = {id: null, versionId: null, componentId: null};
+		// adding the default items that should also be initialized
+		if (config.initData) {
+			_.assign(this.context, config.initData);
+		}
+
+		this.context.shouldFail = false;
+		this.context.errorCode = null;
+		this.context.inputData = null;
+		this.context.responseData = null;
+
+
+		this.config = config;
+
+		let context = this.context;
+		this.context.getUrlForType = (function(type) {
+			var _server = context.server;
+			var _config = config;
+			return function(type) {
+				let typeData = _config[type];
+				let _url = typeData.protocol + '://' +
+					typeData.server + ':' +
+					typeData.port + '/' +
+					typeData.prefix;
+				return _url;
+			}
+		})();
+		setDefaultTimeout(60 * 1000);
+	}
+}
+setWorldConstructor(CustomWorld);
diff --git a/cucumber-js-test-apis-ci/cucumber-common/utils/UpdateTestConfig.js b/cucumber-js-test-apis-ci/cucumber-common/utils/UpdateTestConfig.js
new file mode 100644
index 0000000..a2c1dae
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/utils/UpdateTestConfig.js
@@ -0,0 +1,81 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict'
+
+const fs = require('fs');
+
+var pathToRoot = process.env.TESTS_BASE;
+if (!pathToRoot.endsWith("/")) {
+	pathToRoot += "/";
+}
+var envConfig = require(pathToRoot + 'config.json');
+var protocol = (process.env.PROTOCOL !== undefined) ? process.env.PROTOCOL : 'https';
+
+try {
+	envConfig = require(pathToRoot + 'environments/dockerConfig.json');
+} catch (e) {
+}
+
+function run() {
+	var inputArgs = process.argv.slice(2);
+	let changeConfig = false;
+	if (process.env.K8S_CONF_PATH !== undefined) {
+		console.log('updating with kubernetes services');
+		let k8sConfig = require(pathToRoot + process.env.K8S_CONF_PATH);
+		mapK8sPod2Docker(k8sConfig, inputArgs[0], inputArgs[1]);
+		changeConfig = true;
+	} else {
+		console.log('not updating at all');
+	}
+	if (changeConfig) {
+		let data = JSON.stringify(envConfig, null, 2);
+		console.log('writing config file: ' + pathToRoot+'environments/dockerConfig.json');
+		console.log(data);
+		fs.writeFileSync(pathToRoot+'environments/dockerConfig.json', data);
+	}
+}
+
+function mapK8sPod2Docker(k8sConfig, id, k8sid) {
+	let item = k8sConfig.items.find(item => {
+		if (item.spec !== undefined && item.spec.ports !== undefined) {
+			let spec = item.spec.ports.find(port => {
+				if (port.name === k8sid) {
+					return true;
+				}
+			});
+			return (spec !== undefined);
+		} else {
+			return false;
+		}
+	});
+
+	item.spec.ports.forEach(port => {
+		if (port.name === k8sid) {
+			envConfig[id].port = port.nodePort;
+			let rancherData = JSON.parse(item.metadata.annotations["field.cattle.io/publicEndpoints"]);
+			let address = rancherData.find(address => {
+				return address.port === port.nodePort;
+			});
+			envConfig[id].port = address.port;
+			envConfig[id].server = address.addresses[0];
+			envConfig[id].protocol = protocol;
+			envConfig[id].user = process.env.SDC_USER_ID;
+		}
+	});
+
+}
+
+run();
diff --git a/cucumber-js-test-apis-ci/cucumber-common/utils/Utils.js b/cucumber-js-test-apis-ci/cucumber-common/utils/Utils.js
new file mode 100644
index 0000000..22ee775
--- /dev/null
+++ b/cucumber-js-test-apis-ci/cucumber-common/utils/Utils.js
@@ -0,0 +1,223 @@
+/*
+ * Copyright © 2016-2017 European Support Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const needle = require('needle');
+const fs = require('fs');
+require('node-zip');
+var btoa = require('btoa');
+const md5 = require('md5');
+const _ = require('lodash');
+
+function getOptionsForRequest(context, method, path, type) {
+	if (type == undefined || type == null) {
+		type = context.defaultServerType
+	}
+	let server = context.getUrlForType(type);
+	let options = {
+		method: method,
+		url: server + path,
+		headers: _.clone(context.headers[type])
+	};
+//	options.headers["Content-Type"] = "application/json";
+//	options.headers["accept"] = "application/json";
+	return options;
+}
+
+function _requestBinaryFormData(context, method, path, fileName, formInputName, type) {
+	let options = getOptionsForRequest(context, method, path, type);
+	let formData = {};
+	if (method === 'POST' || method === 'PUT') {
+		//formData[formInputName] = fs.createReadStream(fileName);
+		//options.formData = formData;
+		let fileData =  {
+			file: fileName
+		};
+		fileData['content_type'] = 'multipart/form-data';
+		options.formData = {};
+		options.formData[formInputName] = fileData;
+	}
+	return _request(context, method, path, options);
+}
+function _requestBinaryBody(context, method, path, fileName, type) {
+	let options = getOptionsForRequest(context, method, path, type);
+	if (method === 'POST' || method === 'PUT') {
+		options.body =  fs.createReadStream(fileName);
+		options.headers['Content-Type'] = 'application/octet-stream';
+
+	}
+	return _request(context, method, path, options);
+}
+
+
+function _requestPayload(context, method, path, filePath, type) {
+	let options = getOptionsForRequest(context, method, path, type);
+	options.json = _createPayload(filePath);
+	options.headers['Content-MD5'] = addCheckSum(options.json);
+	return _request(context, method, path, options);
+}
+
+function _requestRest(context, method, path, data, type) {
+	let options = getOptionsForRequest(context, method, path, type);
+	if (method === 'POST' || method === 'PUT') {
+		options.json = data;
+	}
+	return _request(context, method, path, options);
+}
+
+function _request(context, method, path, options) {
+	console.log('--> Calling REST ' + options.method +' url: ' + options.url);
+	let inputData = options.json;
+	let needleOptions = {headers: options.headers, rejectUnauthorized: false};
+	if (inputData == undefined) {
+		if (options.formData != undefined) {
+			inputData = options.formData;
+			needleOptions.multipart = true;
+		}
+		if (inputData && inputData.body != undefined) {
+			inputData = options.body;
+		}
+	} else {
+		needleOptions.json = true;
+	}
+	return needle(method, options.url, inputData, needleOptions)
+		.then(function(result) {
+			context.inputData = null;
+			let isExpected = (context.shouldFail) ? (result.statusCode != 200 && result.statusCode != 201) : (result.statusCode == 200 || result.statusCode == 201);
+			data = result.body;
+			if (!isExpected) {
+				console.log('Did not get expected response code');
+				throw  'Status Code was ' + result.statusCode ;
+			}
+			if (context.shouldFail && context.errorCode) {
+				if (typeof data === 'string' && data) {
+					data = JSON.parse(data);
+				}
+				let errorCode = data.errorCode;
+				let contextErrorCode = context.errorCode;
+				context.errorCode = null;
+				if (errorCode !== contextErrorCode) {
+					throw 'Error Code was ' + errorCode + ' instead of ' + contextErrorCode;
+				}
+			}
+			if (context.shouldFail && context.errorMessage) {
+				if (typeof data === 'string' && data) {
+					data = JSON.parse(data);
+				}
+				let errorMessage = data.message;
+				let contextErrorMessage = context.errorMessage;
+				context.errorMessage = null;
+				if (errorMessage !== contextErrorMessage) {
+					throw 'Error Message was ' + errorMessage + ' instead of ' + contextErrorMessage;
+				}
+			}
+			if (context.shouldFail) {
+				context.shouldFail = false;
+				return({statusCode: result.statusCode, data: {}});
+			}
+
+			if (typeof data === 'string' && data) {
+				if (data.startsWith('[') || data.startsWith('{')) {
+					data = JSON.parse(data);
+				}
+			}
+			context.responseData = data;
+			context.inputData = data;
+			return({statusCode: result.statusCode, data: data});
+
+		})
+		.catch(function(err) {
+			console.error('Request URL: ' + options.url);
+			console.error('Request Method: ' + options.method);
+			console.log(err);
+			throw err;
+		})
+}
+
+function download(context, path, filePath,  type) {
+	if (type == undefined || type == null) {
+		type = context.defaultServerType
+	}
+	let server = context.getUrlForType(type);
+	let options = {
+		method: 'GET',
+		url: server + path,
+		headers: context.headers[type]
+	};
+
+	console.log('--> Calling REST download url: ' + options.url);
+	return needle('GET', options.url, {}, {
+		headers: options.headers,
+		rejectUnauthorized: false,
+		output: filePath
+	})
+		.then(function (result) {
+			let zipFile = fs.readFileSync(filePath, 'binary');
+			let zip = new JSZip(zipFile, {base64: false, checkCRC32: true});
+			if (zip.files['MANIFEST.json']) {
+				let manifestData = zip.files['MANIFEST.json']._data;
+				manifestData = manifestData.replace(/\\n/g, '');
+				context.responseData = JSON.parse(manifestData);
+			}
+			return zip;
+		})
+		.catch(function (err) {
+			console.error('Request URL: ' + options.url);
+			console.error('Request Method: ' + options.method);
+			throw err;
+		})
+}
+
+function _random() {
+	let d = new Date();
+	return d.getTime().toString().split('').reverse().join('');
+}
+
+function _getJSONFromFile(file) {
+	return JSON.parse(fs.readFileSync(file, 'utf8'));
+}
+
+function _createPayload(fileName) {
+	var body = fs.readFileSync(fileName);
+	let payload = {
+		payloadData: body.toString('base64'),
+		payloadName: fileName.substring(fileName.lastIndexOf("/") + 1 )
+	};
+	return payload;
+}
+
+function addCheckSum(payloadData) {
+	let _md5 = md5(JSON.stringify(payloadData));
+	return btoa(_md5.toLowerCase());
+}
+
+function _getFile(file, format) {
+	if(format === '' ){
+		return fs.readFileSync(file)
+	}
+	return fs.readFileSync(file, format);
+}
+
+
+module.exports = {
+	getFile: _getFile,
+	request: _requestRest,
+	requestPayload: _requestPayload,
+	requestBinaryFormData: _requestBinaryFormData,
+	requestBinaryBody: _requestBinaryBody,
+	random : _random,
+	getJSONFromFile: _getJSONFromFile,
+	download: download,
+	payload: _createPayload
+};