UNPKG

4.91 kBJavaScriptView Raw
1'use strict';
2
3var async = require('async');
4var extend = require('xtend');
5var uuid = require('node-uuid').v4;
6var distributeProbabilities = require('./lib/distribute-flow-probabilities');
7var debug = require('debug')('flowbench:flow');
8var template = require('./lib/template');
9
10var defaultOptions = {};
11
12module.exports = function Flow(parent, options, experiment) {
13 var parentOptions = extend({}, parent.options);
14
15 options = extend({
16 request: parentOptions.request
17 }, defaultOptions, options);
18
19 var tasks = [];
20 var flows = [];
21 var req = parent.options.req || {};
22 var res = parent.options.res || {};
23 var templateData = {
24 req: req,
25 res: res
26 };
27 var lastRequest;
28 var flowing = false;
29
30 var flow = function(cb) {
31 debug('executing flow');
32 async.series(tasks, cb);
33 };
34
35 flow.options = options;
36 flow.req = req;
37 flow.res = res;
38
39 flow.prepare = function() {
40 distributeProbabilities(flows);
41 flows.forEach(function(flow) {
42 flow.prepare();
43 });
44 };
45
46 flow.request = function(method, url, options) {
47 checkNotFlowing();
48
49 url = template.prepare(url);
50 options = template.prepare(options);
51 var data = extend({}, templateData, {
52 fixtures: options && fixturize(options.fixtures)
53 });
54 var dataAsArray = [data.req, data.res, data.fixtures];
55
56 tasks.push(function(cb) {
57 options = extend({}, options, {
58 uri: template.render(url, data, dataAsArray),
59 method: method.toUpperCase()
60 });
61 options = template.render(options, data, dataAsArray);
62
63 debug('options.body: %j', options.body);
64 debug('options.json: %j', options.json);
65
66 if (typeof options.json == 'undefined' &&
67 typeof options.body == 'object')
68 {
69 options.json = true;
70 }
71
72
73 if (! options.id) {
74 options.id = uuid();
75 }
76
77 lastRequest = options.id;
78
79 debug('request options:', options);
80
81 var request = parentOptions.request(options, function(err, resp, body) {
82 if (resp) {
83 experiment.emit('response', resp);
84 resp.body = body;
85 res[options.id] = resp;
86 }
87 if (err) {
88 experiment.emit('request-error', request, err);
89 }
90 cb();
91 });
92 experiment.emit('request', request);
93
94 req[options.id] = extend({}, request, {
95 body: options.body ? options.body : request.body,
96 qs: options.qs ? options.qs : request.qs
97 });
98 });
99
100 return flow;
101 };
102
103 ['get', 'post', 'delete', 'head', 'put'].forEach(function(method) {
104 flow[method] = function(url, options) {
105 return flow.request(method.toUpperCase(), url, options);
106 };
107 });
108
109 flow.verify = function() {
110 checkNotFlowing();
111 var verifiers = Array.prototype.slice.call(arguments);
112 verifiers.forEach(function(verifier) {
113 tasks.push(function(cb) {
114 var valid = false;
115 var err;
116 try {
117 valid = verifier.call(null, req[lastRequest], res[lastRequest]);
118 } catch(_err) {
119 err = _err;
120 }
121
122 if (! err && valid instanceof Error) {
123 err = valid;
124 }
125 if (! err && (typeof valid == 'boolean') && ! valid) {
126 err = new Error('unknown verification error');
127 }
128
129 if (err) {
130 experiment.emit('verify-error', err, req[lastRequest], res[lastRequest]);
131 }
132 cb();
133 });
134 });
135
136 return flow;
137 };
138
139 flow.wait = function(ms) {
140 checkNotFlowing();
141 tasks.push(function(cb) {
142 setTimeout(cb, ms);
143 });
144 return flow;
145 };
146
147 flow.flow = function(options) {
148 if (! flowing) {
149 flowing = true;
150 tasks.push(doFlows);
151 }
152 var flow = new Flow(this, options, experiment);
153 flows.push(flow);
154 return flow;
155 };
156
157 flow.end = function({
158 return parent;
159 };
160
161 flow.type = 'flow';
162
163 function doFlows(cb) {
164 var random = Math.random();
165 var sum = 0;
166 var flow;
167 var idx = 0;
168 while(sum < random && idx < flows.length) {
169 flow = flows[idx];
170 if (flow) {
171 sum += flow.options.probability;
172 }
173 idx ++;
174 }
175 if (! flow) {
176 throw new Error('No flow to select');
177 }
178 flow(cb);
179 }
180
181 function checkNotFlowing() {
182 if (flowing) {
183 throw new Error('Adding more tasks after flows is not allowed');
184 }
185 }
186
187 return flow;
188}
189
190function isFlow(task) {
191 return task.type == 'flow';
192}
193
194function fixturize(fixtures) {
195 if (Array.isArray(fixtures)) {
196 Object.defineProperty(fixtures, 'random', {
197 enumerable: false,
198 value: pickRandom
199 });
200 } else if(typeof fixtures == 'object') {
201 Object.keys(fixtures).forEach(function(key) {
202 if (fixtures.hasOwnProperty(key)) {
203 fixtures[key] = fixturize(fixtures[key]);
204 }
205 });
206 }
207
208 return fixtures;
209}
210
211function pickRandom() {
212 var i = Math.floor(Math.random() * this.length);
213 return this[i];
214}
\No newline at end of file