oatts-cypress
Version:
An Open API Cypress Test Template Generator
625 lines (540 loc) • 23 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>lib/process.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="compilation.html">compilation</a><ul class='methods'><li data-type='method'><a href="compilation.html#compile">compile</a></li><li data-type='method'><a href="compilation.html#compileOperationLevel">compileOperationLevel</a></li><li data-type='method'><a href="compilation.html#compilePathLevel">compilePathLevel</a></li><li data-type='method'><a href="compilation.html#compileTransactionLevel">compileTransactionLevel</a></li><li data-type='method'><a href="compilation.html#prepareTemplate">prepareTemplate</a></li></ul></li><li><a href="processing.html">processing</a><ul class='methods'><li data-type='method'><a href="processing.html#determineQueryType">determineQueryType</a></li><li data-type='method'><a href="processing.html#isNumberType">isNumberType</a></li><li data-type='method'><a href="processing.html#lookupCustomQueryValue">lookupCustomQueryValue</a></li><li data-type='method'><a href="processing.html#lookupCustomValue">lookupCustomValue</a></li><li data-type='method'><a href="processing.html#pathify">pathify</a></li><li data-type='method'><a href="processing.html#process">process</a></li><li data-type='method'><a href="processing.html#processHeaders">processHeaders</a></li><li data-type='method'><a href="processing.html#processOperations">processOperations</a></li><li data-type='method'><a href="processing.html#processParams">processParams</a></li><li data-type='method'><a href="processing.html#processPaths">processPaths</a></li><li data-type='method'><a href="processing.html#processResponse">processResponse</a></li><li data-type='method'><a href="processing.html#processTransactions">processTransactions</a></li><li data-type='method'><a href="processing.html#replaceNewlines">replaceNewlines</a></li></ul></li><li><a href="templateHelpers.html">templateHelpers</a><ul class='methods'><li data-type='method'><a href="templateHelpers.html#isNotDefaultStatusCode">isNotDefaultStatusCode</a></li><li data-type='method'><a href="templateHelpers.html#json">json</a></li><li data-type='method'><a href="templateHelpers.html#notEmptyObject">notEmptyObject</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html">generate</a></li><li><a href="global.html">merge2</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">lib/process.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>// Copyright 2018 Google Inc. All Rights Reserved.
//
// 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';
var util = require('util')
var helpers = require('./handlebar-helpers')
var merge2 = require('./util').merge2;
/**
* @namespace processing
*/
module.exports = process
/**
* ProcessedSpec is the fully traversed spec processed for unit test compilation
* @typedef {object} ProcessedSpec
* @memberof processing
* @property {string} host Hostname to be used in the test requests; derived from the options or spec
* @property {string} scheme HTTP schema to be used in the test requests; derived from the spec
* @property {string} basePath Base path as defined by the spec
* @property {string[]} consumes Global content type 'consumes' collection
* @property {ProcessedPath[]} tests Collection of paths processed for test compilation
*/
/**
* Processes the given API spec, creating test data artifacts
* @function process
* @memberof processing
* @instance
* @param {object} api API spec object to be processed
* @param {object} options options to be used during processing
* @return {processing.ProcessedSpec}
*/
function process(api, options) {
var processedSpec = {
'host': (options.host !== undefined ? options.host : (api.host !== undefined
? api.host : 'localhost:5000')),
'scheme': (options.scheme !== undefined ? options.scheme : (api.schemes
!== undefined ? api.schemes[0] : 'http')),
'basePath': (api.basePath !== undefined ? api.basePath : ''),
'consumes': (api.consumes !== undefined ? api.consumes : []),
'produces': (api.produces !== undefined ? api.produces : [])
}
var processedPaths = processPaths(api, processedSpec, options)
if (processedPaths.length == 0) {
console.log('no paths to process in spec')
return null
}
processedSpec.tests = processedPaths
return processedSpec
}
/**
* ProcessedPath is the fully traversed path resource ready for unit test compilation
* @typedef {object} ProcessedPath
* @memberof processing
* @property {string} name name to be used in generation of a file name; based on the path
* @property {string} pathLevelDescription the brief description to use at the top level 'describe' block
* @property {processing.ProcessedOp[]} operations Collection of operations processed for test compilation
*/
/**
* Processes the paths defined by the api spec, or indicated by the options
* @function processPaths
* @memberof processing
* @instance
* @param {object} api parsed OpenAPI spec object
* @param {object} topLevel global spec properties
* @param {object} options options to use during processing
* @return {processing.ProcessedPath[]}
*/
function processPaths(api, topLevel, options) {
var data = []
var targetPaths = [];
if (options.paths !== undefined) {
options.paths.forEach(function (path, ndx, arr) {
targetPaths.push(api.getPath(path))
})
} else {
targetPaths = api.getPaths();
}
if (options.statusCodes !== undefined) {
var tempPaths = [];
options.statusCodes.forEach(function (code, ndx, arr) {
for (var ndx = 0; ndx < targetPaths.length; ndx++) {
var opList = targetPaths[ndx].getOperations();
for (var opNdx = 0; opNdx < opList.length; opNdx++) {
if (opList[opNdx].getResponse(code)) {
tempPaths.push(targetPaths[ndx])
break;
}
}
}
});
targetPaths = tempPaths;
}
targetPaths.forEach(function (pathObj, ndx, arr) {
var ops = processOperations(api, pathObj, topLevel, options)
if (ops.length !== 0) {
data.push({
'name': pathObj.path.replace(/\//g, '-').substring(1),
'pathLevelDescription': 'tests for ' + pathObj.path,
'operations': ops
})
}
})
return data
}
/**
* ProcessedOp is the fully traversed path operation ready for unit test compilation
* @typedef {object} ProcessedOp
* @memberof processing
* @instance
* @property {string} operationLevelDescription the brief description to use at the inner 'describe' block
* @property {processing.ProcessedTransaction[]} transactions Collection of transactions processed for test compilation
*/
/**
* Processes the operations of the given Path object
* @function processOperations
* @memberof processing
* @instance
* @param {object} api parsed OpenAPI spec object
* @param {object} parentPath sway Path object being processed
* @param {object} topLevel global spec properties
* @param {object} options options to use during processing
* @return {processing.ProcessedOp[]}
*/
function processOperations(api, parentPath, topLevel, options) {
var ops = []
parentPath.getOperations().forEach(function (op, ndx, arr) {
var transactions = processTransactions(api, op, parentPath, topLevel,
options)
if (transactions.length !== 0) {
ops.push({
'operationLevelDescription': 'tests for ' + op.method,
'transactions': transactions
})
}
})
return ops
}
/**
* ProcessedTransaction is an HTTP transaction (request/response pair) processed for test compilation
* @typedef {object} ProcessedTransaction
* @memberof processing
* @property {string} testLevelDescription the brief description to use in the test 'it' block
* @property {string} scheme copied from the globally defined HTTP schema
* @property {string} host copied from the globally defined hostname or as specified in the options
* @property {string} path fully qualified path built with the base path; includes path param substitutions
* @property {op} op the REST operation to use
* @property {object} body the request body params
* @property {object} query the request query params
* @property {object} formData the request form data params
* @property {ExpectedResponse} expected the expected response to use for test validation
* @property {boolean} hasSamples flag indicating if the expected response includes sample values
* @property {string[]} consumes based on globally defined conumes or operation defined types
*/
/**
* Processes the transactions for a given Path + Operation
* @function processTransactions
* @memberof processing
* @instance
* @param {object} api parsed OpenAPI spec object
* @param {object} parentOp parent sway Operation object being processed
* @param {object} parentPath parent sway Path object being processed
* @param {object} topLevel global spec properties
* @param {object} options options to use during processing
* @return {processing.ProcessedTransaction[]}
*/
function processTransactions(api, parentOp, parentPath, topLevel, options) {
var transactions = []
parentOp.getResponses().forEach(function (res, ndx, arr) {
if (options.statusCodes && options.statusCodes.indexOf(res.statusCode)
=== -1) {
// skip unwanted status codes
return;
}
var params = processParams(res.statusCode, parentOp, parentPath, options)
var expected = processResponse(res, parentOp.method, parentPath.path,
options)
var hasValue = options.samples
if (expected.custom) {
delete expected.custom
hasValue = true
}
transactions.push({
'testLevelDescription': util.format('should respond %s for "%s"',
res.statusCode, replaceNewlines(res.description)),
'scheme': topLevel.scheme,
'host': topLevel.host,
'path': (topLevel.basePath + params.path),
'op': parentOp.method,
'body': params.body,
'query': params.query,
'formData': params.formData,
'expected': expected,
'hasValue': hasValue,
'headers': merge2(params.headers,
processHeaders(res.statusCode, parentOp, parentPath, topLevel,
options))
})
})
return transactions
}
/**
* ProcessedParams is an object representing the processed request parameters for unit test compilation
* @typedef {object} ProcessedParams
* @memberof processing
* @property {string} path processed path with path parameter sample substitutions
* @property {object} body the request body params
* @property {object} query the request query params
* @property {object} formData the request form data params
*/
/**
* Processes the parameters of a Path + Operation for use in a test
* @function processParams
* @memberof processing
* @instance
* @param {number} code response status code being processed
* @param {object} op sway Operation object that's being processed
* @param {object} path sway Path object that's being process
* @param {object} options options to use during processing
* @return {processing.ProcessedParams}
*/
function processParams(code, op, path, options) {
var opCustomQuery = lookupCustomQueryValue(code, op.method, path.path, options);
var params = {
'body': {},
'query': opCustomQuery || {},
'formData': {},
'path': path.path,
'headers': {}
}
op.getParameters().forEach(function (param, ndx, arr) {
var customValue = lookupCustomValue(param.name, param.in, code, op.method,
path.path, options)
// Skip optional parameter with specifically nulled custom value or in-line example
if ((customValue === null || param.example === null) && (!param.required)) {
return
}
// If multiple examples are present, use the first one
if (param.examples) {
param.example = param.examples[Object.keys(param.examples)[0]].value;
}
if (param.in === 'body') {
params.body = customValue !== undefined ? customValue : param.example !== undefined ? param.example : param.getSample()
} else if (param.in === 'query' && !opCustomQuery) {
params.query[param.name] = param.example !== undefined ? param.example : param.getSample();
} else if (param.in === 'formData') {
try {
params.formData[param.name] = customValue !== undefined ? customValue
: param.example !== undefined ? param.example : param.getSample()
} catch (e) { // this will be because of formData property of type 'file'
params.formData[param.name] = '{fileUpload}'
}
} else if (param.in === 'path') {
params.path = pathify(params.path, param, customValue)
} else if (param.in === 'header') {
params.headers[param.name] = customValue !== undefined ? customValue
: param.example !== undefined ? param.example : param.getSample()
}
})
path.getParameters().forEach(function (param, ndx, arr) {
var customValue = lookupCustomValue(param.name, param.in, code, op.method,
path.path, options)
// Skip optional parameter with specifically nulled custom value or in-line example
if ((customValue === null || param.example === null) && (!param.required)) {
return
}
params.path = pathify(params.path, param, customValue)
})
return params
}
/**
* resolves any custom values available for the given parameter
* @function lookupCustomValue
* @memberof processing
* @instance
* @param {string} name parameter name being looked up
* @param {string} location where the parameter is in the request/response
* @param {number} code response status code being evaluated
* @param {string} op the operation method being processed
* @param {string} path API path being processed
* @param {object} options for use in looking up the custom value
* @return {any}
*/
function lookupCustomValue(name, location, code, op, path, options) {
var levels = [path, op, code]
var val = undefined;
if (options.customValues) {
var curr = options.customValues
var ndx = 0
do {
if (curr[location]) {
if (curr[location][name] !== undefined) {
val = curr[location][name]
}
}
curr = curr[levels[ndx]]
} while (ndx++ < levels.length && curr)
}
return val
}
/**
* resolves any custom query values available for the given operation and path
* @function lookupCustomQueryValue
* @memberof processing
* @instance
* @param {number} code response status code being evaluated
* @param {string} op the operation method being processed
* @param {string} path API path being processed
* @param {object} options for use in looking up the custom value
* @return {any}
*/
function lookupCustomQueryValue(code, op, path, options) {
var levels = [path, op, code];
var val = undefined;
if (options.customValues) {
var curr = options.customValues;
var ndx = 0
do {
if (curr.query !== undefined) {
val = curr.query
}
curr = curr[levels[ndx]];
} while (ndx++ < levels.length && curr)
}
return val
}
/**
* Determines the request headers for a transaction
* @function processHeaders
* @memberof processing
* @instance
* @param {string} responseCode response code being processed
* @param {object} op parent operation object
* @param {object} path parent path object
* @param {object} top global properties
* @param {object} options for use in processing headers
* @return {object}
*/
function processHeaders(responseCode, op, path, top, options) {
var headers = {};
var levels = [path.path, op.method, responseCode]
// handle consumes
if (options.consumes) { // a specific content-type was targeted
if (op.definitionFullyResolved.consumes) {
// this operation overrides global consumes, and does contain the requested content-type
if (op.definitionFullyResolved.consumes.indexOf(options.consumes) != -1) {
headers['Content-Type'] = options.consumes;
} else { // we will just use the first one because this doesn't match
headers['Content-Type'] = op.definitionFullyResolved.consumes[0];
}
} else if (top.consumes.length > 0 && top.consumes.indexOf(options.consumes)
!= -1) {
headers['Content-Type'] = options.consumes;
} else if (top.consumes.length === 1) { // unless there is a singular different global consumes, just use that one
headers['Content-Type'] = top.consumes[0];
}
} else if (op.definitionFullyResolved.consumes) { // we will just use the first one if none are specified
headers['Content-Type'] = op.definitionFullyResolved.consumes[0];
} else if (top.consumes.length > 0) {
headers['Content-Type'] = top.consumes[0];
}
// handle produces
if (options.produces) {
if (op.definitionFullyResolved.produces) {
if (op.definitionFullyResolved.produces.indexOf(options.produces) != -1) {
headers['Accept'] = options.produces;
} else {
headers['Accept'] = op.definitionFullyResolved.produces[0];
}
} else if (top.produces.length > 0 && top.produces.indexOf(options.produces)
!= -1) {
headers['Accept'] = options.produces;
} else if (top.produces.length === 1) {
headers['Accept'] = top.produces[0];
}
} else if (op.definitionFullyResolved.produces) {
headers['Accept'] = op.definitionFullyResolved.produces[0];
} else if (top.produces.length > 0) {
headers['Accept'] = top.produces[0];
}
// check custom request values for any desired headers; cascading merge
if (options.customValues) {
var curr = options.customValues
var ndx = 0
do {
if (curr['header'] !== undefined) {
headers = merge2(curr['header'], headers)
}
curr = curr[levels[ndx]]
} while (ndx++ < levels.length && curr !== undefined)
}
return headers
}
/**
* ExpectedResponse is the expected response generated based on the API spec
* @typedef {object} ExpectedResponse
* @memberof processing
* @property {number} statusCode expected response status code
* @property {boolean} custom indicates if the value was a custom injection
* @property {object} res expected response body, if applicable; can be a generated sample
*/
/**
* Processes a sway Response for use in a test
* @function processResponse
* @memberof processing
* @instance
* @param {object} res sway Response object being processed
* @param {string} op the operation method being processed
* @param {string} path API path being processed
* @param {object} options options to use during processing
* @return {processing.ExpectedResponse}
*/
function processResponse(res, op, path, options) {
var expected = {
'statusCode': res.statusCode,
'custom': false
}
if (options.customValues
&& options.customValues[path]
&& options.customValues[path][op]
&& options.customValues[path][op][res.statusCode]
&& options.customValues[path][op][res.statusCode].response) {
expected['res'] = options.customValues[path][op][res.statusCode].response
expected.custom = true;
return expected
}
var res = options.samples ? res.getSample()
: res.definitionFullyResolved.schema
if (res !== undefined) {
expected['res'] = res
}
return expected
}
/**
* replaces the path paremeter in the given URL path with a sample value
* @function pathify
* @memberof processing
* @instance
* @param {string} path URL path to be pathified
* @param {object} param sway Parameter object to use in pathify
* @param {(string|number)} value a custom value to use in the pathify
* @return {string}
*/
function pathify(path, param, value) {
var regex = new RegExp('(\/.*){' + param.name + '}(\/.*)*', 'g')
var sample = value !== undefined ? value : param.example !== undefined ? param.example : param.getSample()
if (isNumberType(param.definition.type)) {
// prevent negative sample values for numbers
while (sample < 0) {
sample = param.getSample()
}
}
return path.replace(regex, '$1' + sample + '$2').replace(/ /g, '');
}
/**
* evaluates if the given type is an OpenAPI number type or not
* @function isNumberType
* @memberof processing
* @instance
* @param {string} type type to be evaluated
* @return {boolean}
*/
function isNumberType(type) {
return (type === 'integer' || type === 'float' || type === 'long' || type
=== 'double')
}
/**
* Determines the proper type for the Query parameter
* @function determineQueryType
* @memberof processing
* @instance
* @param {object} param sway Parameter object to investigate
* @return {string}
*/
function determineQueryType(param) {
var val = param.name; // default to the name if all else fails
if (param.definitionFullyResolved.type === 'array') {
val = param.definitionFullyResolved.items.type + '[]'
} else {
val = param.definitionFullyResolved.type
}
return '{' + val + '}'
}
/**
* Used to replace newlines with a space in each response.description
* @function replaceNewlines
* @memberof processing
* @instance
* @param {string} str string to replace newlines with spaces
* @return {string}
*/
function replaceNewlines(str) {
return str.replace(/\r?\n|\r/g, " ")
}
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.3</a> on Sat Aug 24 2019 12:31:35 GMT+0100 (Western European Summer Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/linenumber.js"></script>
</body>
</html>