1 | _ = require 'lodash'
|
2 | FlowDeployer = require '../src/flow-deployer'
|
3 |
|
4 |
|
5 | describe 'FlowDeployer', ->
|
6 | describe 'when constructed with a flow', ->
|
7 | beforeEach ->
|
8 | @request = get: sinon.stub()
|
9 | @configuration = erik_is_happy: true
|
10 |
|
11 | options =
|
12 | flowUuid: 'the-flow-uuid'
|
13 | flowToken: 'the-flow-token'
|
14 | forwardUrl: 'http://www.zombo.com'
|
15 | instanceId: 'an-instance-id'
|
16 | userUuid: 'some-user-uuid'
|
17 | userToken: 'some-user-token'
|
18 | octobluUrl: 'https://api.octoblu.com'
|
19 | deploymentUuid: 'the-deployment-uuid'
|
20 | flowLoggerUuid: 'flow-logger-uuid'
|
21 |
|
22 | @configurationGenerator =
|
23 | configure: sinon.stub()
|
24 |
|
25 | @configurationSaver =
|
26 | save: sinon.stub()
|
27 | stop: sinon.stub()
|
28 |
|
29 | @meshbluHttp =
|
30 | message: sinon.stub()
|
31 | updateDangerously: sinon.stub()
|
32 | createSubscription: sinon.stub()
|
33 | search: sinon.stub()
|
34 |
|
35 | MeshbluHttp = sinon.spy => @meshbluHttp
|
36 |
|
37 | @sut = new FlowDeployer options,
|
38 | configurationGenerator: @configurationGenerator
|
39 | configurationSaver: @configurationSaver
|
40 | MeshbluHttp: MeshbluHttp
|
41 | request: @request
|
42 |
|
43 | @request.get.withArgs('https://api.octoblu.com/api/flows/the-flow-uuid').yields null, {}, {a: 1, b: 5}
|
44 | @request.get.withArgs('https://api.octoblu.com/api/user').yields null, {}, {api: {}}
|
45 |
|
46 | describe 'when deploy is called', ->
|
47 | beforeEach (done)->
|
48 | flowConfig =
|
49 | 'some': 'thing'
|
50 | 'subscribe-devices':
|
51 | config:
|
52 | 'broadcast.sent': ['subscribe-to-this-uuid']
|
53 |
|
54 | @configurationGenerator.configure.yields null, flowConfig, {stop: 'config'}
|
55 | @configurationSaver.stop.yields null
|
56 | @configurationSaver.save.yields null
|
57 | @sut.setupDevice = sinon.stub().yields null
|
58 | @sut.deploy => done()
|
59 |
|
60 | it 'should message the FLOW_LOGGER_UUID', ->
|
61 | expect(@meshbluHttp.message).to.have.been.called
|
62 | firstArg = @meshbluHttp.message.firstCall.args[0]
|
63 | delete firstArg.payload.date
|
64 |
|
65 | expect(firstArg).to.deep.equal
|
66 | devices: ['flow-logger-uuid']
|
67 | payload:
|
68 | application: 'flow-deploy-service'
|
69 | deploymentUuid: 'the-deployment-uuid'
|
70 | flowUuid: 'the-flow-uuid'
|
71 | userUuid: 'some-user-uuid'
|
72 | workflow: 'flow-start'
|
73 | state: 'begin'
|
74 | message: undefined
|
75 |
|
76 | it 'should call configuration generator with the flow', ->
|
77 | expect(@configurationGenerator.configure).to.have.been.calledWith
|
78 | flowData: { a: 1, b: 5 }
|
79 | userData: {api: {}}
|
80 | deploymentUuid: 'the-deployment-uuid'
|
81 | flowToken: 'the-flow-token'
|
82 |
|
83 | it 'should call configuration saver with the flow', ->
|
84 | expect(@configurationSaver.save).to.have.been.calledWith(
|
85 | flowId: 'the-flow-uuid'
|
86 | instanceId: 'an-instance-id'
|
87 | flowData:
|
88 | 'some': 'thing'
|
89 | 'subscribe-devices':
|
90 | config:
|
91 | 'broadcast.sent': ['subscribe-to-this-uuid']
|
92 | )
|
93 | expect(@configurationSaver.save).to.have.been.calledWith(
|
94 | flowId: 'the-flow-uuid-stop'
|
95 | instanceId: 'an-instance-id'
|
96 | flowData:
|
97 | stop: 'config'
|
98 | )
|
99 |
|
100 | it 'should call request.get', ->
|
101 | options =
|
102 | json: true
|
103 | auth :
|
104 | user: 'some-user-uuid'
|
105 | pass: 'some-user-token'
|
106 |
|
107 | expect(@request.get).to.have.been.calledWith 'https://api.octoblu.com/api/flows/the-flow-uuid', options
|
108 | expect(@request.get).to.have.been.calledWith 'https://api.octoblu.com/api/user', options
|
109 |
|
110 | it 'should message the FLOW_LOGGER_UUID', ->
|
111 | expect(@meshbluHttp.message).to.have.been.called
|
112 | firstArg = @meshbluHttp.message.secondCall.args[0]
|
113 | delete firstArg.payload.date
|
114 |
|
115 | expect(firstArg).to.deep.equal
|
116 | devices: ['flow-logger-uuid']
|
117 | payload:
|
118 | application: 'flow-deploy-service'
|
119 | deploymentUuid: 'the-deployment-uuid'
|
120 | flowUuid: 'the-flow-uuid'
|
121 | userUuid: 'some-user-uuid'
|
122 | workflow: 'flow-start'
|
123 | state: 'end'
|
124 | message: undefined
|
125 |
|
126 | describe 'when deploy is called and user GET errored', ->
|
127 | beforeEach (done) ->
|
128 | userUrl = 'https://api.octoblu.com/api/user'
|
129 | @request.get.withArgs(userUrl).yields new Error 'whoa, thats not a user', null
|
130 | @sut.deploy (@error, @result) => done()
|
131 |
|
132 | it 'should call request.get', ->
|
133 | expect(@request.get).to.have.been.called
|
134 |
|
135 | it 'should yield and error', ->
|
136 | expect(@error).to.exist
|
137 |
|
138 | it 'should not give us a result', ->
|
139 | expect(@result).to.not.exist
|
140 |
|
141 | it 'should message the FLOW_LOGGER_UUID', ->
|
142 | expect(@meshbluHttp.message).to.have.been.calledTwice
|
143 | firstArg = @meshbluHttp.message.secondCall.args[0]
|
144 | delete firstArg.payload.date
|
145 |
|
146 | expect(firstArg).to.deep.equal
|
147 | devices: ['flow-logger-uuid']
|
148 | payload:
|
149 | application: 'flow-deploy-service'
|
150 | deploymentUuid: 'the-deployment-uuid'
|
151 | flowUuid: 'the-flow-uuid'
|
152 | userUuid: 'some-user-uuid'
|
153 | workflow: 'flow-start'
|
154 | state: 'error'
|
155 | message: 'whoa, thats not a user'
|
156 |
|
157 | describe 'when deploy is called and flow get errored', ->
|
158 | beforeEach (done) ->
|
159 | userUrl = 'https://api.octoblu.com/api/user'
|
160 | @request.get.withArgs(userUrl).yields null
|
161 |
|
162 | flowUrl = 'https://api.octoblu.com/api/flows/the-flow-uuid'
|
163 | @request.get.withArgs(flowUrl).yields new Error 'whoa, shoots bad', null
|
164 | @sut.deploy (@error, @result) => done()
|
165 |
|
166 | it 'should call request.get', ->
|
167 | expect(@request.get).to.have.been.called
|
168 |
|
169 | it 'should yield and error', ->
|
170 | expect(@error).to.exist
|
171 |
|
172 | it 'should not give us a result', ->
|
173 | expect(@result).to.not.exist
|
174 |
|
175 | it 'should message the FLOW_LOGGER_UUID', ->
|
176 | expect(@meshbluHttp.message).to.have.been.calledTwice
|
177 | firstArg = @meshbluHttp.message.secondCall.args[0]
|
178 | delete firstArg.payload.date
|
179 |
|
180 | expect(firstArg).to.deep.equal
|
181 | devices: ['flow-logger-uuid']
|
182 | payload:
|
183 | application: 'flow-deploy-service'
|
184 | deploymentUuid: 'the-deployment-uuid'
|
185 | flowUuid: 'the-flow-uuid'
|
186 | userUuid: 'some-user-uuid'
|
187 | workflow: 'flow-start'
|
188 | state: 'error'
|
189 | message: 'whoa, shoots bad'
|
190 |
|
191 | describe 'when deploy is called and the configuration generator returns an error', ->
|
192 | beforeEach (done)->
|
193 | @configurationGenerator.configure.yields new Error 'Oh noes'
|
194 | @sut.deploy (@error, @result)=> done()
|
195 |
|
196 | it 'should return an error with an error', ->
|
197 | expect(@error).to.exist
|
198 |
|
199 | it 'should not give us a result', ->
|
200 | expect(@result).to.not.exist
|
201 |
|
202 | it 'should message the FLOW_LOGGER_UUID', ->
|
203 | expect(@meshbluHttp.message).to.have.been.calledTwice
|
204 | firstArg = @meshbluHttp.message.secondCall.args[0]
|
205 | delete firstArg.payload.date
|
206 |
|
207 | expect(firstArg).to.deep.equal
|
208 | devices: ['flow-logger-uuid']
|
209 | payload:
|
210 | application: 'flow-deploy-service'
|
211 | deploymentUuid: 'the-deployment-uuid'
|
212 | flowUuid: 'the-flow-uuid'
|
213 | userUuid: 'some-user-uuid'
|
214 | workflow: 'flow-start'
|
215 | state: 'error'
|
216 | message: 'Oh noes'
|
217 |
|
218 | describe 'when deploy is called and the configuration stop returns an error', ->
|
219 | beforeEach (done)->
|
220 | @configurationGenerator.configure.yields null, { erik_likes_me: true}
|
221 | @configurationSaver.stop.yields new Error 'Erik can never like me enough'
|
222 | @sut.deploy (@error, @result)=> done()
|
223 |
|
224 | it 'should yield and error', ->
|
225 | expect(@error).to.exist
|
226 |
|
227 | it 'should not give us a result', ->
|
228 | expect(@result).to.not.exist
|
229 |
|
230 | it 'should not call save', ->
|
231 | expect(@configurationSaver.save).to.not.have.been.called
|
232 |
|
233 | it 'should message the FLOW_LOGGER_UUID', ->
|
234 | expect(@meshbluHttp.message).to.have.been.calledTwice
|
235 | firstArg = @meshbluHttp.message.secondCall.args[0]
|
236 | delete firstArg.payload.date
|
237 |
|
238 | expect(firstArg).to.deep.equal
|
239 | devices: ['flow-logger-uuid']
|
240 | payload:
|
241 | application: 'flow-deploy-service'
|
242 | deploymentUuid: 'the-deployment-uuid'
|
243 | flowUuid: 'the-flow-uuid'
|
244 | userUuid: 'some-user-uuid'
|
245 | workflow: 'flow-start'
|
246 | state: 'error'
|
247 | message: 'Erik can never like me enough'
|
248 |
|
249 | describe 'when deploy is called and the configuration save returns an error', ->
|
250 | beforeEach (done)->
|
251 | @configurationGenerator.configure.yields null, { erik_likes_me: true}
|
252 | @configurationSaver.stop.yields null
|
253 | @configurationSaver.save.yields new Error 'Erik can never like me enough'
|
254 | @sut.deploy (@error, @result)=> done()
|
255 |
|
256 | it 'should yield and error', ->
|
257 | expect(@error).to.exist
|
258 |
|
259 | it 'should not give us a result', ->
|
260 | expect(@result).to.not.exist
|
261 |
|
262 | it 'should message the FLOW_LOGGER_UUID', ->
|
263 | expect(@meshbluHttp.message).to.have.been.calledTwice
|
264 | firstArg = @meshbluHttp.message.secondCall.args[0]
|
265 | delete firstArg.payload.date
|
266 |
|
267 | expect(firstArg).to.deep.equal
|
268 | devices: ['flow-logger-uuid']
|
269 | payload:
|
270 | application: 'flow-deploy-service'
|
271 | deploymentUuid: 'the-deployment-uuid'
|
272 | flowUuid: 'the-flow-uuid'
|
273 | userUuid: 'some-user-uuid'
|
274 | workflow: 'flow-start'
|
275 | state: 'error'
|
276 | message: 'Erik can never like me enough'
|
277 |
|
278 | describe 'when deploy is called and the generator and saver actually worked', ->
|
279 | beforeEach (done) ->
|
280 | @configurationGenerator.configure.yields null, { erik_likes_me: 'more than you know'}
|
281 | @configurationSaver.stop.yields null
|
282 | @configurationSaver.save.yields null, {finally_i_am_happy: true}
|
283 | @sut.setupDevice = sinon.stub().yields null
|
284 |
|
285 | @sut.deploy (@error, @result) => done()
|
286 |
|
287 | it 'should call setupDeviceForwarding', ->
|
288 | expect(@sut.setupDevice).to.have.been.called
|
289 |
|
290 |
|
291 | describe 'createSubscriptions', ->
|
292 | beforeEach (done) ->
|
293 | @meshbluHttp.createSubscription.yields null
|
294 | flowConfig =
|
295 | 'subscribe-devices':
|
296 | config:
|
297 | 'broadcast.sent': ['subscribe-to-this-uuid']
|
298 | @sut.createSubscriptions flowConfig, done
|
299 |
|
300 | it "should create the subscription to the device's", ->
|
301 | subscriberUuid = 'the-flow-uuid'
|
302 | emitterUuid = 'subscribe-to-this-uuid'
|
303 | type = 'broadcast.sent'
|
304 | expect(@meshbluHttp.createSubscription).to.have.been.calledWith {subscriberUuid, emitterUuid, type}
|
305 |
|
306 | describe 'setupDeviceForwarding', ->
|
307 | beforeEach (done) ->
|
308 | @updateMessageHooks =
|
309 | $addToSet:
|
310 | 'meshblu.forwarders.broadcast.received':
|
311 | signRequest: true
|
312 | url: 'http://www.zombo.com'
|
313 | method: 'POST'
|
314 | name: 'nanocyte-flow-deploy'
|
315 | type: 'webhook'
|
316 | 'meshblu.forwarders.message.received':
|
317 | signRequest: true
|
318 | url: 'http://www.zombo.com'
|
319 | method: 'POST'
|
320 | name: 'nanocyte-flow-deploy'
|
321 | type: 'webhook'
|
322 |
|
323 | @pullMessageHooks =
|
324 | $pull:
|
325 | 'meshblu.forwarders.received':
|
326 | name: 'nanocyte-flow-deploy'
|
327 | 'meshblu.messageHooks':
|
328 | name: 'nanocyte-flow-deploy'
|
329 | 'meshblu.forwarders.broadcast.received':
|
330 | name: 'nanocyte-flow-deploy'
|
331 | 'meshblu.forwarders.message.received':
|
332 | name: 'nanocyte-flow-deploy'
|
333 |
|
334 | @removeOldMessageHooks =
|
335 | $unset:
|
336 | 'meshblu.forwarders.broadcast': ''
|
337 |
|
338 | @device =
|
339 | uuid: 1
|
340 | flow: {a: 1, b: 5}
|
341 | meshblu:
|
342 | messageHooks: [
|
343 | generateAndForwardMeshbluCredentials: true
|
344 | url: 'http://www.neopets.com'
|
345 | method: 'DELETE'
|
346 | name: 'nanocyte-flow-deploy'
|
347 | ]
|
348 |
|
349 | @meshbluHttp.search.yields null, [meshblu: forwarders: broadcast: []]
|
350 | @meshbluHttp.updateDangerously.yields null, null
|
351 | @sut.setupDeviceForwarding (@error, @result) => done()
|
352 |
|
353 | it "should update a meshblu device with the webhook to wherever it's going", ->
|
354 | expect(@meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid', @removeOldMessageHooks
|
355 | expect(@meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid', @pullMessageHooks
|
356 | expect(@meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid', @updateMessageHooks
|
357 |
|
358 | describe 'setupMessageSchema', ->
|
359 | beforeEach (done) ->
|
360 | @updateDevice = $set:
|
361 | messageSchema:
|
362 | type: 'object'
|
363 | properties:
|
364 | from:
|
365 | type: 'string'
|
366 | title: 'Trigger'
|
367 | required: true
|
368 | enum: [ 'a', 'c' ]
|
369 | payload:
|
370 | title: "payload"
|
371 | description: "Use {{msg}} to send the entire message"
|
372 | replacePayload:
|
373 | type: 'string'
|
374 | default: 'payload'
|
375 |
|
376 | messageFormSchema: [
|
377 | {
|
378 | key: 'from'
|
379 | titleMap:
|
380 | 'a' : 'multiply (a)'
|
381 | 'c' : 'rabbits (c)'
|
382 | }
|
383 | { key: 'payload', 'type': 'input', title: "Payload", description: "Use {{msg}} to send the entire message"}
|
384 | ]
|
385 |
|
386 | nodes = [
|
387 | {
|
388 | class: 'trigger'
|
389 | id: 'a'
|
390 | name: 'multiply'
|
391 | },
|
392 | {
|
393 | class: 'not-a-trigger'
|
394 | id: 'b'
|
395 | name: 'like'
|
396 | },
|
397 | {
|
398 | class: 'trigger'
|
399 | id: 'c'
|
400 | name: 'rabbits'
|
401 | }
|
402 | ]
|
403 |
|
404 | @sut.meshbluHttp.updateDangerously.yields null, null
|
405 | @sut.setupMessageSchema nodes, (@error, @result) => done()
|
406 |
|
407 | it "should update a meshblu device with message schema for triggers", ->
|
408 | expect(@sut.meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid', @updateDevice
|
409 |
|
410 | describe 'startFlow', ->
|
411 | describe 'when called and there is no errors', ->
|
412 | beforeEach (done) ->
|
413 | @meshbluHttp.updateDangerously.yields null
|
414 | @meshbluHttp.message.yields null, null
|
415 | @sut.startFlow (@error, @result) => done()
|
416 |
|
417 | it 'should update meshblu device status', ->
|
418 | expect(@meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
|
419 | $set:
|
420 | online: true
|
421 | deploying: false
|
422 | stopping: false
|
423 |
|
424 | it 'should message meshblu with the a flow start message', ->
|
425 | expect(@meshbluHttp.message).to.have.been.calledWith
|
426 | devices: ['the-flow-uuid']
|
427 | payload:
|
428 | from: "engine-start"
|
429 |
|
430 | it 'should message meshblu with a subscribe:pulse message', ->
|
431 | expect(@meshbluHttp.message).to.have.been.calledWith
|
432 | devices: ['the-flow-uuid']
|
433 | topic: 'subscribe:pulse'
|
434 |
|
435 | describe 'when called and meshblu returns an error', ->
|
436 | beforeEach (done) ->
|
437 | @message =
|
438 | payload:
|
439 | from: "engine-start"
|
440 |
|
441 | @meshbluHttp.updateDangerously.yields null
|
442 | @meshbluHttp.message.yields new Error 'duck army', null
|
443 | @sut.startFlow (@error, @result) => done()
|
444 |
|
445 | it 'should call the callback with the error', ->
|
446 | expect(@error).to.exist
|
447 |
|
448 | describe 'stopFlow', ->
|
449 | describe 'when called and there is no error', ->
|
450 | beforeEach (done) ->
|
451 | @meshbluHttp.updateDangerously.yields null
|
452 | @meshbluHttp.message.yields null, null
|
453 | @sut.stopFlow (@error, @result) => done()
|
454 |
|
455 | it 'should update the meshblu device with as offline', ->
|
456 | expect(@meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
|
457 | $set:
|
458 | online: false
|
459 | deploying: false
|
460 | stopping: false
|
461 |
|
462 | it 'should message meshblu with the a flow stop message', ->
|
463 | expect(@sut.meshbluHttp.message).to.have.been.calledWith
|
464 | devices: ['the-flow-uuid']
|
465 | payload:
|
466 | from: "engine-stop"
|
467 |
|
468 | describe 'when called and meshblu returns an error', ->
|
469 | beforeEach (done) ->
|
470 | @meshbluHttp.updateDangerously.yields null
|
471 | @meshbluHttp.message.yields new Error 'look at meeeeee', null
|
472 | @sut.stopFlow (@error, @result) => done()
|
473 |
|
474 | it 'should call the callback with the error', ->
|
475 | expect(@error).to.exist
|