guvnor
Version:
A node process manager that isn't spanners all the way down
322 lines (245 loc) • 8.26 kB
JavaScript
var expect = require('chai').expect,
sinon = require('sinon'),
RemoteDaemon = require('../../../lib/remote/RemoteDaemon')
describe('RemoteDaemon', function () {
var remoteDaemon, clock
beforeEach(function () {
clock = sinon.useFakeTimers()
remoteDaemon = new RemoteDaemon()
remoteDaemon._logger = {
info: function () {
},
warn: function () {
},
error: function () {
},
debug: function () {
}
}
remoteDaemon._dnode = sinon.stub()
remoteDaemon._crypto = {}
remoteDaemon._tls = {
connect: sinon.stub()
}
remoteDaemon._semver = {
satisfies: sinon.stub().returns(true)
}
remoteDaemon._processStore = {
removeAll: sinon.stub()
}
remoteDaemon._appStore = {
removeAll: sinon.stub()
}
})
afterEach(function () {
clock.restore()
})
it('should connect to a remote daemon if already connected', function (done) {
remoteDaemon._connected = true
remoteDaemon.connect(function (error, remote) {
expect(error).to.not.exist
expect(remote).to.equal(remoteDaemon)
done()
})
})
it('should connect to a remote daemon', function (done) {
var port = 80
var host = 'foo'
remoteDaemon._port = port
remoteDaemon._host = host
var stream = {
on: sinon.stub(),
pipe: sinon.stub()
}
stream.pipe.returnsArg(0)
var d = {
on: sinon.stub(),
pipe: sinon.stub()
}
d.pipe.returnsArg(0)
d.on.withArgs('remote', sinon.match.func).callsArgWith(1, {
foo: sinon.stub()
})
remoteDaemon._dnode.returns(d)
remoteDaemon._tls.connect.returns(stream)
remoteDaemon.connect(function (error, remote) {
expect(error).to.not.exist
expect(d.on.calledWith('remote', sinon.match.func)).to.be.true
expect(remote.foo).to.be.a('function')
done()
})
expect(remoteDaemon._tls.connect.callCount).to.equal(1)
expect(remoteDaemon._tls.connect.getCall(0).args[0]).to.equal(port)
expect(remoteDaemon._tls.connect.getCall(0).args[1].host).to.equal(host)
remoteDaemon._tls.connect.getCall(0).args[2]({
foo: sinon.stub()
})
})
it('should call callback when disconnecting from a remote daemon', function (done) {
var client = {
end: sinon.stub(),
once: sinon.stub()
}
remoteDaemon._client = client
remoteDaemon.disconnect(done)
expect(client.end.callCount).to.equal(1)
expect(client.once.callCount).to.equal(2)
expect(client.once.getCall(0).args[0]).to.equal('end')
expect(remoteDaemon._disconnecting).to.be.true
client.once.getCall(0).args[1]()
})
it('should only end connection once no matter how many times disconnect is invoked', function () {
var client = {
end: sinon.stub(),
once: sinon.stub()
}
remoteDaemon._client = client
var invocations = 0
var func = function () {
invocations++
}
remoteDaemon.disconnect(func)
remoteDaemon.disconnect(func)
remoteDaemon.disconnect(func)
expect(client.end.callCount).to.equal(1)
expect(client.once.callCount).to.equal(4)
client.once.getCall(0).args[1]()
client.once.getCall(1).args[1]()
client.once.getCall(2).args[1]()
client.once.getCall(3).args[1]()
expect(invocations).to.equal(3)
})
it('should call callback when disconnecting from a remote daemon even if not connected', function (done) {
remoteDaemon.disconnect(done)
})
it('should connect to a remote process', function (done) {
var remoteProcess = {
foo: sinon.stub
}
var processId = 'foo'
remoteDaemon._connectToProcess = sinon.stub()
remoteDaemon._connectToProcess.withArgs(processId, sinon.match.func).callsArgWith(1, undefined, remoteProcess)
remoteDaemon.connectToProcess(processId, function (error, remote) {
expect(error).to.not.exist
expect(remote.foo).to.be.a('function')
done()
})
})
it('should not wrap dumpHeap with timeoutify', function (done) {
var remoteProcess = {
dumpHeap: sinon.stub(),
meh: sinon.stub()
}
var processId = 'foo'
remoteDaemon._connectToProcess = sinon.stub()
remoteDaemon._connectToProcess.withArgs(processId, sinon.match.func).callsArgWith(1, undefined, remoteProcess)
remoteDaemon.connectToProcess(processId, function (error, remote) {
expect(error).to.not.exist
// wrapped
expect(remote.meh).to.not.equal(remoteProcess.meh)
// not wrapped
expect(remote.dumpHeap).to.equal(remoteProcess.dumpHeap)
done()
})
})
it('should reconnect to a remote daemon if the connection is refused', function (done) {
testErrorHandler('ECONNREFUSED', 'CONNECTIONREFUSED', done)
})
it('should reconnect to a remote daemon if the connection is reset', function (done) {
testErrorHandler('ECONNRESET', 'CONNECTIONRESET', done)
})
it('should reconnect to a remote daemon if the host is not found', function (done) {
testErrorHandler('ENOTFOUND', 'HOSTNOTFOUND', done)
})
it('should reconnect to a remote daemon if the connection times out', function (done) {
testErrorHandler('ETIMEDOUT', 'TIMEDOUT', done)
})
it('should reconnect to a remote daemon if the network goes away', function (done) {
testErrorHandler('ENETDOWN', 'NETWORKDOWN', done)
})
it('should reconnect to a remote daemon the connection ends and we are not disconnecting', function (done) {
var stream = {
on: sinon.stub(),
pipe: sinon.stub()
}
stream.pipe.returnsArg(0)
remoteDaemon._tls.connect.returns(stream)
remoteDaemon.connect(function (error) {
expect(error).to.not.exist
done()
})
expect(remoteDaemon._tls.connect.callCount).to.equal(1)
expect(stream.on.getCall(1).args[0]).to.equal('end')
stream.on.getCall(1).args[1]()
var d = {
on: sinon.stub(),
pipe: sinon.stub()
}
d.pipe.returnsArg(0)
remoteDaemon._dnode.returns(d)
clock.tick(6000)
// get ready to pretend we've connected to something
d.on.withArgs('remote').callsArgWith(1, {
foo: sinon.stub()
})
// should have reconnected
expect(remoteDaemon._tls.connect.callCount).to.equal(2)
// invoke the connection callback
remoteDaemon._tls.connect.getCall(1).args[2]()
})
it('should sign a message', function (done) {
var principal = 'principal'
var secret = 'secret'
var signature = 'signature'
remoteDaemon._principal = principal
remoteDaemon._secret = secret
remoteDaemon._crypto.sign = sinon.stub()
remoteDaemon._crypto.sign.withArgs(principal, secret, sinon.match.func).callsArgWith(2, undefined, signature)
remoteDaemon._signAndInvoke(function (sig, one, two, three) {
expect(sig).to.equal(signature)
expect(one).to.equal('one')
expect(two).to.equal('two')
expect(three).to.equal('three')
done()
}, 'one', 'two', 'three')
})
function testErrorHandler(code, friendlyCode, done) {
var error = new Error('nope!')
error.code = code
var stream = {
on: sinon.stub(),
pipe: sinon.stub()
}
stream.pipe.returnsArg(0)
remoteDaemon._tls.connect.returns(stream)
var invocations = 0
remoteDaemon.connect(function (error) {
if (invocations == 0) {
expect(error.message).to.contain('Could not connect')
expect(error.code).to.equal(friendlyCode)
} else if (invocations == 1) {
expect(error).to.not.exist
done()
}
invocations++
})
expect(remoteDaemon._tls.connect.callCount).to.equal(1)
expect(stream.on.getCall(0).args[0]).to.equal('error')
stream.on.getCall(0).args[1](error)
var d = {
on: sinon.stub(),
pipe: sinon.stub()
}
d.pipe.returnsArg(0)
remoteDaemon._dnode.returns(d)
clock.tick(6000)
// get ready to pretend we've connected to something
d.on.withArgs('remote').callsArgWith(1, {
foo: sinon.stub()
})
// should have reconnected
expect(remoteDaemon._tls.connect.callCount).to.equal(2)
// invoke the connection callback
remoteDaemon._tls.connect.getCall(1).args[2]()
}
})