UNPKG

playr-json-scenario

Version:

plays back request scenarios defined in a json file

426 lines (291 loc) 14.2 kB
(function() { 'use strict'; var Class = require('ee-class') , type = require('ee-types') , fs = require('fs') , log = require('ee-log') , playr = require('playr') , ScenarioContext = require('./ScenarioContext') ; module.exports = new Class({ inherits: playr.Scenario // default name , name: 'JSON Scenario' , init: function init(options) { // a path to the json file is required if (!options || (!options.path && !options.data)) throw new Error('The JSON Scenario needs a path to a json file!'); this.path = options.path; this.url = options.url; this.log = options.log; this.data = options.data; // storage for the requests this.requests = new Map(); // storage which defiens which response to cache this.requestCacheInstruction = new Map(); // a list of all responses that must be cached this.responseCacheIds = new Set(); // call super init.super.call(this, options); } /** * the scenario needs to be instantiated for * every thread. this creates a new storage * context for the thread * * @returns {object} context */ , createContext: function() { return new ScenarioContext(this); } /** * checks if a request with the given * index exists * * @param {number} index the request index * * @returns {boolean} */ , hasRequestAt: function(index, context) { return this.requests.has(index); } /** * returns a request that can be executed * * @param {number} index the request index * * @returns {promise} */ , getRequestAt: function(index, request, context) { let requestDefinition = this.requests.get(index); // the requets must know its definition // so it can be validated request.definition = requestDefinition.request; let execPreroll = (index) => { if (requestDefinition.preroll.length > index) { if (requestDefinition.preroll[index].kind === 'pause') { return new Promise((resolve) => { setTimeout(resolve, requestDefinition.preroll[index].duration); }); } else return Promise.reject(new Error(`Cannot execute preroll item of the kind ${requestDefinition.preroll[index].kind}`)); } else return Promise.resolve(); }; return execPreroll(0).then(() => { // nice, the preroll was executed // maybe we have to wait for the // response from a previous request if (requestDefinition.request.parameterized) { // make sure all required responses are available return Promise.all(requestDefinition.request.requiredResponses.map((requestId) => { if (context.responseCache.has(requestId)) return Promise.resolve({requestId: requestId, response: context.responseCache.get(requestId)}); else { // we have to wait for the response return new Promise((resolve, reject) => { let hasTimeout = false; setTimeout(() => { hasTimeout = true; reject(new Error(`Timeout while waiting for the response of the ${requestDefinition.request.id} request!`)); }, 30000); context.once(requestId, (response) => { if (!hasTimeout) resolve({requestId: requestId, response: response}); }); }); } })).then((responseData) => { let responseDataMap = {}; responseData.forEach((instruction) => { responseDataMap[instruction.requestId] = instruction.response; }); return this.popoulateRequest(requestDefinition, request, responseDataMap); }); } else { // k, we're ready return this.popoulateRequest(requestDefinition, request); } }); } /** * configres the http request */ , popoulateRequest: function(requestDefinition, request, responseDataMap) { let definition = requestDefinition.request; // id if (definition.id) request.id = definition.id; // methd && url request.url = definition.request.url; request.method = definition.request.method; // check if we have to oevrride the domain if (this.url) request.url = request.url.replace(/^https?:\/\/[^\/]+\//i, this.url+'/'); // headers if (definition.request.headers) { Object.keys(definition.request.headers).forEach((headerName) => { request.headers[headerName] = definition.request.headers[headerName]; }); } // query if (definition.request.query) { Object.keys(definition.request.query).forEach((parameterName) => { request.query[parameterName] = definition.request.query[parameterName]; }); } // content if (definition.request.content) { Object.keys(definition.request.content).forEach((parameterName) => { request.form[parameterName] = definition.request.content[parameterName]; }); } if (definition.request.body) request.body = definition.request.body; if (definition.id) { let instructions = this.requestCacheInstruction.get(definition.id); if (definition.parameterizedURL) { instructions.url.forEach((instruction) => { request.url = request.url.replace(instruction.match, this.getContentItem(responseDataMap[instruction.requestId].data, instruction.path)); }); } if (definition.parameterizedContent) { Object.keys(instructions.content).forEach((key) => { let instructionSet = instructions.content[key]; instructionSet.forEach((instruction) => { if (request.form[key]) { request.form[key] = request.form[key].replace(instruction.match, this.getContentItem(responseDataMap[instruction.requestId].data, instruction.path)); } }); }); } } // logging? if (this.log) console.log(request.method.toUpperCase().green+' '+request.url.white); return Promise.resolve(); } /** * extracts a value from a json object */ , getContentItem: function(content, path) { if (path && path.length) { if (path[0] === '[') { let index = parseInt(path.substring(1, path.indexOf(']')), 10); if (Array.isArray(content) && content[index]) { return this.getContentItem(content[index], path.substr(path.indexOf(']')+2)); } else return null; } else { if (typeof content === 'object' && content !== null) { // path is a property name if (path.indexOf('[') >= 0) { // there is an array on the proeprty return this.getContentItem(content[path.substring(0, path.indexOf('['))], path.substr(path.indexOf('['))); } else if (path.indexOf('.') >= 0) { // object return this.getContentItem(content[path.substring(0, path.indexOf('.'))], path.substr(path.indexOf('.'))); } else { // got it return content[path]; } } } } } /** * time to prepare the scenario if there is * anything to load it should be done here * * @returns {promise} */ , prepare: function() { return this.loadJSONdata().then(() => { // go through all requests, look if we need to cache responses this.scenario.forEach((item) => { if (item.kind === 'request') { let reg = /(\{([a-z0-9\-]+)([\[\]0-9\.a-z]+)\})/gi; let result; // url while(result = reg.exec(item.request.url)) { if (!this.requestCacheInstruction.has(item.id)) this.requestCacheInstruction.set(item.id, {url: [], content: {}}); let instruction = this.requestCacheInstruction.get(item.id); instruction.url.push({ match: result[1] , requestId: result[2] , path: result[3] }); if (!this.responseCacheIds.has(result[2])) this.responseCacheIds.add(result[2]); item.parameterized = true; item.parameterizedURL = true; if (!item.requiredResponses) item.requiredResponses = []; item.requiredResponses.push(result[2]); } if (item.request.content) { // check the content too Object.keys(item.request.content).forEch((key) => { if (item.request.content[key] === 'string') { reg.lastIndex = 0; while(result = reg.exec(item.request.content[key])) { if (!this.requestCacheInstruction.has(item.id)) this.requestCacheInstruction.set(item.id, {url: [], content: {}}); let instruction = this.requestCacheInstruction.get(item.id); if (!instruction.content[key]) instruction.content[key] = []; instruction.content[key].push({ match: result[1] , requestId: result[2] , path: result[3] }); if (!this.responseCacheIds.has(result[2])) this.responseCacheIds.add(result[2]); item.parameterized = true; item.parameterizedContent = true; if (!item.requiredResponses) item.requiredResponses = []; item.requiredResponses.push(result[2]); } } }); } } }); // count the requests let preroll = []; this.scenario.forEach((item) => { if (item.kind === 'request') { this.requests.set(this.requests.size, { request: item , preroll: preroll }); preroll = []; } else preroll.push(item); }); return Promise.resolve(); }); } /** * load either a file or takes the object passed * to the constructor */ , loadJSONdata: function() { if (this.data) { this.scenario = this.data.scenario || this.data; return Promise.resolve(); } else { return new Promise((resolve, reject) => { fs.readFile(this.path, (err, data) => { if (err) reject(new Error(`Failed to load JSON scenario ${this.path}: ${err.message}`)); else { try { this.data = JSON.parse(data); } catch (e) { return reject(new Error(`Failed to parse JSON scenario ${this.path}: ${err.message}`)); } if (this.data.scenario) { this.scenario = this.data.scenario; resolve(); } else reject(new Error(`Missing the scenario in the JSON scenario ${this.path}!`)); } }); }); } } }); })();