nanocyte-deployer
Version:
Deploy flows to nanocyte
627 lines (534 loc) • 21.8 kB
text/coffeescript
_ = require 'lodash'
Redis = require 'ioredis'
FlowDeployer = require '../src/flow-deployer'
shmock = require 'shmock'
enableDestroy = require 'server-destroy'
describe 'FlowDeployer', ->
beforeEach (done) ->
= shmock done
enableDestroy
afterEach (done) ->
.destroy done
describe 'when constructed with a flow', ->
beforeEach (done) ->
= new Redis dropBufferSupport: true
.on 'ready', done
beforeEach ->
= erik_is_happy: true
options =
flowUuid: 'the-flow-uuid'
flowToken: 'the-flow-token'
forwardUrl: 'http://www.zombo.com'
instanceId: 'an-instance-id'
userUuid: 'some-user-uuid'
userToken: 'some-user-token'
octobluUrl: 'https://api.octoblu.com'
deploymentUuid: 'the-deployment-uuid'
flowLoggerUuid: 'flow-logger-uuid'
client:
intervalServiceUri: "http://localhost:#{@intervalService.address().port}"
=
configure: sinon.stub()
=
save: sinon.stub()
stop: sinon.stub()
=
message: sinon.stub()
updateDangerously: sinon.stub()
createSubscription: sinon.stub()
search: sinon.stub()
MeshbluHttp = sinon.spy =>
= new FlowDeployer options,
configurationGenerator:
configurationSaver:
MeshbluHttp: MeshbluHttp
sinon.stub(, 'registerIntervalDevices').yields null
flowData =
flow:
nodes: [
id: 'a'
class: 'interval'
]
.search.yields null, [flowData]
describe 'when deploy is called', ->
beforeEach (done)->
flowConfig =
'some': 'thing'
'subscribe-devices':
config:
'broadcast.sent': ['subscribe-to-this-uuid']
.registerIntervalDevices.yields null, [
id: 'a'
class: 'interval'
deviceId: 'interval-a'
]
.configure.yields null, flowConfig, {stop: 'config'}
.stop.yields null
.save.yields null
.setupDevice = sinon.stub().yields null
.deploy => done()
it 'should message the FLOW_LOGGER_UUID', ->
expect(.message).to.have.been.called
firstArg = .message.firstCall.args[0]
delete firstArg.payload.date
expect(firstArg).to.deep.equal
devices: ['flow-logger-uuid']
payload:
application: 'flow-deploy-service'
deploymentUuid: 'the-deployment-uuid'
flowUuid: 'the-flow-uuid'
userUuid: 'some-user-uuid'
workflow: 'flow-start'
state: 'begin'
message: undefined
it 'should call registerIntervalDevices with the flow', ->
nodes = [
id: 'a'
class: 'interval'
]
expect(.registerIntervalDevices).to.have.been.calledWith nodes
it 'should call configuration generator with the flow', ->
expect(.configure).to.have.been.calledWith
flowData: nodes: [{id: 'a', class: 'interval', deviceId: 'interval-a'}]
deploymentUuid: 'the-deployment-uuid'
flowToken: 'the-flow-token'
it 'should call configuration saver with the flow', ->
expect(.save).to.have.been.calledWith(
flowId: 'the-flow-uuid'
instanceId: 'an-instance-id'
flowData:
'some': 'thing'
'subscribe-devices':
config:
'broadcast.sent': ['subscribe-to-this-uuid']
)
expect(.save).to.have.been.calledWith(
flowId: 'the-flow-uuid-stop'
instanceId: 'an-instance-id'
flowData:
stop: 'config'
)
it 'should call meshbluHttp.search', ->
expect(.search).to.have.been.calledWith uuid: 'the-flow-uuid'
it 'should message the FLOW_LOGGER_UUID', ->
expect(.message).to.have.been.called
firstArg = .message.secondCall.args[0]
delete firstArg.payload.date
expect(firstArg).to.deep.equal
devices: ['flow-logger-uuid']
payload:
application: 'flow-deploy-service'
deploymentUuid: 'the-deployment-uuid'
flowUuid: 'the-flow-uuid'
userUuid: 'some-user-uuid'
workflow: 'flow-start'
state: 'end'
message: undefined
describe 'when deploy is called and flow get errored', ->
beforeEach (done) ->
.search.yields new Error 'whoa, shoots bad', null
.deploy (, ) => done()
it 'should call meshbluHttp.search', ->
expect(.search).to.have.been.called
it 'should yield and error', ->
expect().to.exist
it 'should not give us a result', ->
expect().to.not.exist
it 'should message the FLOW_LOGGER_UUID', ->
expect(.message).to.have.been.calledTwice
firstArg = .message.secondCall.args[0]
delete firstArg.payload.date
expect(firstArg).to.deep.equal
devices: ['flow-logger-uuid']
payload:
application: 'flow-deploy-service'
deploymentUuid: 'the-deployment-uuid'
flowUuid: 'the-flow-uuid'
userUuid: 'some-user-uuid'
workflow: 'flow-start'
state: 'error'
message: 'whoa, shoots bad'
describe 'when deploy is called and the configuration generator returns an error', ->
beforeEach (done)->
.configure.yields new Error 'Oh noes'
.deploy (, )=> done()
it 'should return an error with an error', ->
expect().to.exist
it 'should not give us a result', ->
expect().to.not.exist
it 'should message the FLOW_LOGGER_UUID', ->
expect(.message).to.have.been.calledTwice
firstArg = .message.secondCall.args[0]
delete firstArg.payload.date
expect(firstArg).to.deep.equal
devices: ['flow-logger-uuid']
payload:
application: 'flow-deploy-service'
deploymentUuid: 'the-deployment-uuid'
flowUuid: 'the-flow-uuid'
userUuid: 'some-user-uuid'
workflow: 'flow-start'
state: 'error'
message: 'Oh noes'
describe 'when deploy is called and the configuration stop returns an error', ->
beforeEach (done)->
.configure.yields null, { erik_likes_me: true}
.stop.yields new Error 'Erik can never like me enough'
.deploy (, )=> done()
it 'should yield and error', ->
expect().to.exist
it 'should not give us a result', ->
expect().to.not.exist
it 'should not call save', ->
expect(.save).to.not.have.been.called
it 'should message the FLOW_LOGGER_UUID', ->
expect(.message).to.have.been.calledTwice
firstArg = .message.secondCall.args[0]
delete firstArg.payload.date
expect(firstArg).to.deep.equal
devices: ['flow-logger-uuid']
payload:
application: 'flow-deploy-service'
deploymentUuid: 'the-deployment-uuid'
flowUuid: 'the-flow-uuid'
userUuid: 'some-user-uuid'
workflow: 'flow-start'
state: 'error'
message: 'Erik can never like me enough'
describe 'when deploy is called and the configuration save returns an error', ->
beforeEach (done)->
.configure.yields null, { erik_likes_me: true}
.stop.yields null
.save.yields new Error 'Erik can never like me enough'
.deploy (, )=> done()
it 'should yield and error', ->
expect().to.exist
it 'should not give us a result', ->
expect().to.not.exist
it 'should message the FLOW_LOGGER_UUID', ->
expect(.message).to.have.been.calledTwice
firstArg = .message.secondCall.args[0]
delete firstArg.payload.date
expect(firstArg).to.deep.equal
devices: ['flow-logger-uuid']
payload:
application: 'flow-deploy-service'
deploymentUuid: 'the-deployment-uuid'
flowUuid: 'the-flow-uuid'
userUuid: 'some-user-uuid'
workflow: 'flow-start'
state: 'error'
message: 'Erik can never like me enough'
describe 'when deploy is called and the generator and saver actually worked', ->
beforeEach (done) ->
.configure.yields null, { erik_likes_me: 'more than you know'}
.stop.yields null
.save.yields null, {finally_i_am_happy: true}
.setupDevice = sinon.stub().yields null
.deploy (, ) => done()
it 'should call setupDeviceForwarding', ->
expect(.setupDevice).to.have.been.called
describe 'createSubscriptions', ->
beforeEach (done) ->
.createSubscription.yields null
flowConfig =
'subscribe-devices':
config:
'broadcast.sent': ['subscribe-to-this-uuid']
.createSubscriptions flowConfig, done
it "should create the subscription to the devices", ->
subscriberUuid = 'the-flow-uuid'
emitterUuid = 'subscribe-to-this-uuid'
type = 'broadcast.sent'
expect(.createSubscription).to.have.been.calledWith {subscriberUuid, emitterUuid, type}
describe 'setupDeviceForwarding', ->
beforeEach (done) ->
=
$addToSet:
'meshblu.forwarders.broadcast.received':
signRequest: true
url: 'http://www.zombo.com'
method: 'POST'
name: 'nanocyte-flow-deploy'
type: 'webhook'
'meshblu.forwarders.message.received':
signRequest: true
url: 'http://www.zombo.com'
method: 'POST'
name: 'nanocyte-flow-deploy'
type: 'webhook'
'meshblu.forwarders.configure.received':
signRequest: true
url: 'http://www.zombo.com'
method: 'POST'
name: 'nanocyte-flow-deploy'
type: 'webhook'
=
$pull:
'meshblu.forwarders.received':
name: 'nanocyte-flow-deploy'
'meshblu.messageHooks':
name: 'nanocyte-flow-deploy'
'meshblu.forwarders.broadcast.received':
name: 'nanocyte-flow-deploy'
'meshblu.forwarders.message.received':
name: 'nanocyte-flow-deploy'
'meshblu.forwarders.configure.received':
name: 'nanocyte-flow-deploy'
=
$unset:
'meshblu.forwarders.broadcast': ''
=
uuid: 1
flow: {a: 1, b: 5}
meshblu:
messageHooks: [
generateAndForwardMeshbluCredentials: true
url: 'http://www.neopets.com'
method: 'DELETE'
name: 'nanocyte-flow-deploy'
]
.search.yields null, [flow: {}, meshblu: forwarders: broadcast: []]
.updateDangerously.yields null, null
.setupDeviceForwarding (, ) => done()
it "should update a meshblu device with the webhook to wherever it's going", ->
expect(.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
expect(.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
expect(.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
describe 'setupMessageSchema', ->
beforeEach (done) ->
= $set:
instanceId: 'an-instance-id'
messageSchema:
type: 'object'
properties:
from:
type: 'string'
title: 'Trigger'
required: true
enum: [ 'a', 'c' ]
payload:
title: "payload"
description: "Use {{msg}} to send the entire message"
replacePayload:
type: 'string'
default: 'payload'
messageFormSchema: [
{
key: 'from'
titleMap:
'a' : 'multiply (a)'
'c' : 'rabbits (c)'
}
{ key: 'payload', 'type': 'input', title: "Payload", description: "Use {{msg}} to send the entire message"}
]
nodes = [
{
class: 'trigger'
id: 'a'
name: 'multiply'
},
{
class: 'not-a-trigger'
id: 'b'
name: 'like'
},
{
class: 'trigger'
id: 'c'
name: 'rabbits'
}
]
.meshbluHttp.updateDangerously.yields null, null
.setupMessageSchema nodes, (, ) => done()
it "should update a meshblu device with message schema for triggers", ->
expect(.meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
describe 'registerIntervalDevices', ->
beforeEach (done) ->
.registerIntervalDevices.restore()
.meshbluHttp.updateDangerously = sinon.stub().yields null
= $set:
instanceId: 'an-instance-id'
messageSchema:
type: 'object'
properties:
from:
type: 'string'
title: 'Trigger'
required: true
enum: [ 'a', 'c' ]
payload:
title: "payload"
description: "Use {{msg}} to send the entire message"
replacePayload:
type: 'string'
default: 'payload'
messageFormSchema: [
{
key: 'from'
titleMap:
'a' : 'multiply (a)'
'c' : 'rabbits (c)'
}
{ key: 'payload', 'type': 'input', title: "Payload", description: "Use {{msg}} to send the entire message"}
]
nodes = [
{
class: 'interval'
id: 'a'
name: 'divide'
},
{
class: 'schedule'
id: 'b'
name: 'everything'
},
{
class: 'throttle'
id: 'c'
name: 'by'
},
{
class: 'debounce'
id: 'd'
name: 'zero'
},
{
class: 'delay'
id: 'e'
name: 'or'
},
{
class: 'not-an-interval'
id: 'f'
name: 'infinity'
}
]
= .post '/nodes/a/intervals'
.reply '201', uuid: 'interval-a'
= .post '/nodes/b/intervals'
.reply '201', uuid: 'interval-b'
= .post '/nodes/c/intervals'
.reply '201', uuid: 'interval-c'
= .post '/nodes/d/intervals'
.reply '201', uuid: 'interval-d'
= .post '/nodes/e/intervals'
.reply '201', uuid: 'interval-e'
.registerIntervalDevices nodes, (, ) =>
done()
it 'should create the intervals', ->
expect(.isDone).to.be.true
expect(.isDone).to.be.true
expect(.isDone).to.be.true
expect(.isDone).to.be.true
expect(.isDone).to.be.true
it 'should set the deviceId of the node', ->
nodeA = _.find , id: 'a'
expect(nodeA.deviceId).to.equal 'interval-a'
nodeB = _.find , id: 'b'
expect(nodeB.deviceId).to.equal 'interval-b'
nodeC = _.find , id: 'c'
expect(nodeC.deviceId).to.equal 'interval-c'
nodeD = _.find , id: 'd'
expect(nodeD.deviceId).to.equal 'interval-d'
nodeE = _.find , id: 'e'
expect(nodeE.deviceId).to.equal 'interval-e'
it 'should update sendWhitelist', ->
data =
$addToSet:
sendWhitelist:
$each: ['interval-a', 'interval-b', 'interval-c', 'interval-d', 'interval-e']
expect(.meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid', data
describe 'startFlow', ->
describe 'when called and there is no errors', ->
beforeEach (done) ->
.updateDangerously.yields null
.message.yields null, null
.startFlow (, ) => done()
it 'should update meshblu device status', ->
expect(.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
$set:
online: true
deploying: false
stopping: false
it 'should message meshblu with the a flow start message', ->
expect(.message).to.have.been.calledWith
devices: ['the-flow-uuid']
payload:
from: "engine-start"
it 'should message meshblu with a subscribe:pulse message', ->
expect(.message).to.have.been.calledWith
devices: ['the-flow-uuid']
topic: 'subscribe:pulse'
describe 'when called and meshblu returns an error', ->
beforeEach (done) ->
=
payload:
from: "engine-start"
.updateDangerously.yields null
.message.yields new Error 'duck army', null
.startFlow (, ) => done()
it 'should call the callback with the error', ->
expect().to.exist
describe 'stopFlow', ->
describe 'when called and there is no error', ->
beforeEach (done) ->
.updateDangerously.yields null
.message.yields null, null
.stopFlow (, ) => done()
it 'should update the meshblu device with as offline', ->
expect(.updateDangerously).to.have.been.calledWith 'the-flow-uuid',
$set:
online: false
deploying: false
stopping: false
it 'should message meshblu with the a flow stop message', ->
expect(.meshbluHttp.message).to.have.been.calledWith
devices: ['the-flow-uuid']
payload:
from: "engine-stop"
describe 'when called and meshblu returns an error', ->
beforeEach (done) ->
.updateDangerously.yields null
.message.yields new Error 'look at meeeeee', null
.stopFlow (, ) => done()
it 'should call the callback with the error', ->
expect().to.exist
describe 'destroy', ->
describe 'when called and there is no error', ->
beforeEach (done) ->
.set 'the-flow-uuid', Date.now(), done
beforeEach (done) ->
.meshbluHttp.updateDangerously = sinon.stub().yields null
= .delete '/nodes/a/intervals/interval-a'
.reply '204'
flowData =
"f0ab929d-0709-4fb4-a482-f9808e961682":
config:
id: "a"
class: "interval"
deviceId: "interval-a"
"d511bb27-046b-4efa-847b-0f382db688de":
config:
id: "a"
class: "interval"
deviceId: "interval-a"
stopConfig =
flowData: JSON.stringify flowData
.stop.yields null, [stopConfig]
.destroy (, ) => done()
it 'should call stop', ->
expect(.stop).to.have.been.called
it 'should unregister the interval', ->
.done()
it 'should remove the redis key', (done) ->
.exists 'the-flow-uuid', (error, exists) =>
return done error if error?
expect(exists).to.equal 0
done()
it 'should remove devices from sendWhitelist', ->
data =
$pullAll:
sendWhitelist: ['interval-a']
expect(.meshbluHttp.updateDangerously).to.have.been.calledWith 'the-flow-uuid', data
expect(.meshbluHttp.updateDangerously).to.have.been.calledOnce