1 | 'use strict';
|
2 |
|
3 | var async = require('async');
|
4 | var extend = require('xtend');
|
5 | var uuid = require('node-uuid').v4;
|
6 | var distributeProbabilities = require('./lib/distribute-flow-probabilities');
|
7 | var debug = require('debug')('flowbench:flow');
|
8 | var template = require('./lib/template');
|
9 |
|
10 | var defaultOptions = {};
|
11 |
|
12 | module.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 |
|
190 | function isFlow(task) {
|
191 | return task.type == 'flow';
|
192 | }
|
193 |
|
194 | function 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 |
|
211 | function pickRandom() {
|
212 | var i = Math.floor(Math.random() * this.length);
|
213 | return this[i];
|
214 | } |
\ | No newline at end of file |