guvnor
Version:
A node process manager that isn't spanners all the way down
729 lines (553 loc) • 16.6 kB
JavaScript
var HostData = require('../../../../lib/web/domain/HostData')
var sinon = require('sinon')
var expect = require('chai').expect
var EventEmitter = require('wildemitter')
describe('HostData', function () {
var data
var credentials = {}
beforeEach(function () {
data = new HostData('test', credentials)
data._logger = {
info: sinon.stub(),
warn: sinon.stub(),
error: sinon.stub(),
debug: sinon.stub()
}
data._config = {
frequency: 5000
}
data._processDataFactory = {
create: sinon.stub()
}
data._webSocketResponder = {
broadcast: sinon.stub()
}
})
it('should remove missing processes', function () {
data.processes.push({
id: 'foo'
})
data.processes.push({
id: 'bar'
})
expect(data.processes.length).to.equal(2)
data._removeMissingProcesses([{
id: 'foo'
}])
expect(data.processes.length).to.equal(1)
expect(data.processes[0].id).to.equal('foo')
})
it('should find process by id', function () {
data.processes.push({
id: 'foo'
})
data.processes.push({
id: 'bar'
})
var returned = data.findProcessById('bar')
expect(returned.id).to.equal('bar')
})
it('should fail to find process by id', function () {
data.processes.push({
id: 'foo'
})
data.processes.push({
id: 'bar'
})
var returned = data.findProcessById('baz')
expect(returned).to.not.exist
})
it('should find worker process by id', function () {
data.processes.push({
id: 'foo'
})
data.processes.push({
id: 'bar',
workers: [
{id: 'qux'},
{id: 'baz'}
]
})
var returned = data.findProcessById('baz')
expect(returned.id).to.equal('baz')
})
it('should find apps', function (done) {
var apps = []
data._daemon = {
listApplications: sinon.stub()
}
data._daemon.listApplications.callsArgWithAsync(0, undefined, apps)
data.findApps(function (error, returnedApps) {
expect(error).not.to.exist
expect(returnedApps).to.equal(apps)
done()
})
})
it('should return empty array for apps when remote not connected', function (done) {
data.findApps(function (error, apps) {
expect(error).not.to.exist
expect(apps.length).to.equal(0)
done()
})
})
it('should handle a remote timeout', function () {
var error = new Error()
error.code = 'TIMEOUT'
data.lastUpdated = 10
data._handleRemoteError(error)
expect(data.status).to.equal('timeout')
})
it('should not mark data as timedout when a recent update had occured', function () {
var error = new Error()
error.code = 'TIMEOUT'
data.lastUpdated = Date.now() - 10
data.status = 'connected'
data._handleRemoteError(error)
expect(data.status).to.equal('connected')
})
it('should handle a remote signature failure', function () {
var error = new Error()
error.code = 'INVALIDSIGNATURE'
data._handleRemoteError(error)
expect(data.status).to.equal('badsignature')
})
it('should handle an unknown remote error', function () {
var error = new Error()
data._handleRemoteError(error)
expect(data.status).to.equal('error')
})
it('should connect to remote after properties set', function (done) {
data._connectToDaemon = done
data.afterPropertiesSet()
})
it('should connect to remote', function (done) {
data._remote = function (logger, credentials, callback) {
expect(data.status).to.equal('connecting')
expect(logger).to.equal(data._logger)
expect(credentials).to.equal(data._data)
expect(callback).to.be.a('function')
done()
}
data._connectToDaemon()
})
it('should update details from server', function (done) {
var arg = {}
data._daemon = {
foo: sinon.stub()
}
data._daemon.foo.callsArgWithAsync(0, undefined, arg)
data._update('foo', function (returned, callback) {
expect(returned).to.equal(arg)
callback()
expect(data.bar).to.be.ok
clearTimeout(data.bar)
done()
}, 'bar')
})
it('should handle error when updating details from server', function () {
data._daemon = {
foo: sinon.stub()
}
data._daemon.foo.callsArgWith(0, new Error())
var update = sinon.stub()
data._update('foo', update, 'bar')
// should be have error status
expect(data.status).to.equal('error')
// should not have updated anything
expect(update.called).to.be.false
// should have set a timeout to try again later
expect(data.bar).to.be.ok
clearTimeout(data.bar)
})
it('should handle updated server status', function (done) {
var status = {
foo: 'bar'
}
expect(data.foo).to.not.exist
data._handleUpdatedServerStatus(status, function () {
expect(data.foo).to.equal('bar')
expect(data._webSocketResponder.broadcast.called).to.be.true
done()
})
})
it('should handle new processes', function (done) {
var processes = [{
id: 'foo'
}, {
id: 'bar'
}]
var createdProcesses = [{
id: 'foo',
update: sinon.stub()
}, {
id: 'bar',
update: sinon.stub()
}]
data._processDataFactory.create.withArgs([processes[0]]).callsArgWithAsync(1, undefined, createdProcesses[0])
data._processDataFactory.create.withArgs([processes[1]]).callsArgWithAsync(1, undefined, createdProcesses[1])
expect(data.processes.length).to.equal(0)
data._handleUpdatedProcesses(processes, function () {
expect(data.processes.length).to.equal(2)
done()
})
})
it('should handle updated processes', function (done) {
var processes = [{
id: 'foo'
}, {
id: 'bar'
}]
var createdProcesses = [{
id: 'foo',
update: sinon.stub()
}, {
id: 'bar',
update: sinon.stub()
}]
data._processDataFactory.create.withArgs([processes[0]]).callsArgWithAsync(1, undefined, createdProcesses[0])
data.processes.push(createdProcesses[1])
data._handleUpdatedProcesses(processes, function () {
expect(data.processes.length).to.equal(2)
expect(createdProcesses[0].update.called).to.be.false
expect(createdProcesses[1].update.called).to.be.true
done()
})
})
it('should handle connection refused when connecting to remote', function () {
var error = new Error()
error.code = 'CONNECTIONREFUSED'
data._connectedToDaemon(error)
expect(data.status).to.equal('connectionrefused')
})
it('should handle connection reset when connecting to remote', function () {
var error = new Error()
error.code = 'CONNECTIONRESET'
data._connectedToDaemon(error)
expect(data.status).to.equal('connectionreset')
})
it('should handle host not found when connecting to remote', function () {
var error = new Error()
error.code = 'HOSTNOTFOUND'
data._connectedToDaemon(error)
expect(data.status).to.equal('hostnotfound')
})
it('should handle connection timeout when connecting to remote', function () {
var error = new Error()
error.code = 'TIMEDOUT'
data._connectedToDaemon(error)
expect(data.status).to.equal('connectiontimedout')
})
it('should handle network down when connecting to remote', function () {
var error = new Error()
error.code = 'NETWORKDOWN'
data._connectedToDaemon(error)
expect(data.status).to.equal('networkdown')
})
it('should handle unknown error when connecting to remote', function () {
var error = new Error()
data._connectedToDaemon(error)
expect(data.status).to.equal('error')
})
it('should remove disconnected listener if reconnecting to daemon', function () {
var oldDaemon = {
off: sinon.stub()
}
var newDaemon = {
once: sinon.stub(),
getDetails: sinon.stub()
}
data._daemon = oldDaemon
data._connectedToDaemon(undefined, newDaemon)
expect(oldDaemon.off.calledWith('disconnected')).to.be.true
})
it('should remove listeners when daemon disconnects', function () {
var newDaemon = {
on: sinon.stub(),
once: sinon.stub(),
getDetails: sinon.stub(),
off: sinon.stub()
}
var details = {
guvnor: '2.4.2'
}
data._config.minVersion = '^2.0.0'
data._update = sinon.stub()
newDaemon.getDetails.callsArgWith(0, undefined, details)
data._connectedToDaemon(undefined, newDaemon)
expect(newDaemon.once.callCount).to.equal(1)
expect(newDaemon.once.getCall(0).args[0]).to.equal('disconnected')
newDaemon.once.getCall(0).args[1]()
for (var i = 0; i < newDaemon.on.callCount; i++) {
expect(newDaemon.off.calledWith(newDaemon.on.getCall(i).args[0])).to.be.true
}
expect(data.status).to.equal('connecting')
})
it('should handle remote error when getting daemon status', function () {
var newDaemon = {
once: sinon.stub(),
getDetails: sinon.stub(),
off: sinon.stub()
}
newDaemon.getDetails.callsArgWith(0, new Error())
data._connectedToDaemon(undefined, newDaemon)
expect(data.status).to.equal('error')
})
it('should mark daemon as incompatible when version is too old', function () {
var newDaemon = {
once: sinon.stub(),
getDetails: sinon.stub(),
off: sinon.stub()
}
var details = {
guvnor: '1.0.0'
}
data._config.minVersion = '^2.0.0'
newDaemon.getDetails.callsArgWith(0, undefined, details)
data._connectedToDaemon(undefined, newDaemon)
expect(data.guvnor).to.equal('1.0.0')
expect(data.status).to.equal('incompatible')
})
it('should mark daemon as connected when connection is successful', function () {
var newDaemon = {
once: sinon.stub(),
getDetails: sinon.stub(),
off: sinon.stub(),
on: sinon.stub()
}
var details = {
guvnor: '2.4.2'
}
data._config.minVersion = '^2.0.0'
data._update = sinon.stub()
newDaemon.getDetails.callsArgWith(0, undefined, details)
data._connectedToDaemon(undefined, newDaemon)
expect(data.guvnor).to.equal('2.4.2')
expect(data.status).to.equal('connected')
expect(data._update.calledWith('getServerStatus')).to.be.true
expect(data._update.calledWith('listProcesses')).to.be.true
})
it('should pass log event to process', function () {
var proc = {
id: 'foo',
log: sinon.stub()
}
data.processes.push(proc)
data._daemon = new EventEmitter()
data._listenForLogEvents()
data._daemon.emit('process:log:error', {
id: 'foo'
}, {
date: 'bar',
message: 'baz'
})
expect(proc.log.calledWith('error', 'bar', 'baz')).to.be.true
})
it('should survive not finding a process when passing a log event to process', function () {
data._daemon = new EventEmitter()
data._listenForLogEvents()
data._daemon.emit('process:log:error', {
id: 'foo'
}, {
date: 'bar',
message: 'baz'
})
})
it('should pass cluster event to process', function () {
var proc = {
id: 'foo',
log: sinon.stub()
}
data.processes.push(proc)
data._daemon = new EventEmitter()
data._listenForLogEvents()
data._daemon.emit('cluster:log:error', {
id: 'foo'
}, {
date: 'bar',
message: 'baz'
})
expect(proc.log.calledWith('error', 'bar', 'baz')).to.be.true
})
it('should survive not finding a process when passing a cluster event to process', function () {
data._daemon = new EventEmitter()
data._listenForLogEvents()
data._daemon.emit('cluster:log:error', {
id: 'foo'
}, {
date: 'bar',
message: 'baz'
})
})
it('should pass worker event to process', function () {
var proc = {
id: 'bar',
log: sinon.stub()
}
data.processes.push(proc)
data._daemon = new EventEmitter()
data._listenForLogEvents()
data._daemon.emit('worker:log:error', {
id: 'foo'
}, {
id: 'bar'
}, {
date: 'baz',
message: 'qux'
})
expect(proc.log.calledWith('error', 'baz', 'qux')).to.be.true
})
it('should survive not finding a process when passing a worker event to process', function () {
data._daemon = new EventEmitter()
data._listenForLogEvents()
data._daemon.emit('worker:log:error', {
id: 'foo'
}, {
id: 'bar'
}, {
date: 'baz',
message: 'qux'
})
})
it('should pass uncaught exception to process', function () {
data._daemon = new EventEmitter()
data._listenForUncaughtExceptions()
var proc = {
id: 'foo',
exception: sinon.stub()
}
data.processes.push(proc)
data._daemon.emit('process:uncaughtexception', {
id: 'foo'
}, {
date: 'bar',
message: 'baz',
code: 'qux',
stack: 'quux'
})
expect(proc.exception.calledWith('bar', 'baz', 'qux', 'quux')).to.be.true
})
it('should survive not finding a process when passing uncaught exception to process', function () {
data._daemon = new EventEmitter()
data._listenForUncaughtExceptions()
data._daemon.emit('process:uncaughtexception', {
id: 'foo'
}, {
date: 'bar',
message: 'baz',
code: 'qux',
stack: 'quux'
})
})
it('should store exception when process fails', function () {
data._daemon = new EventEmitter()
data._listenForUncaughtExceptions()
var proc = {
id: 'foo',
exception: sinon.stub()
}
data.processes.push(proc)
data._daemon.emit('process:failed', {
id: 'foo'
}, {
date: 'bar',
message: 'baz',
code: 'qux',
stack: 'quux'
})
expect(proc.exception.calledWith('bar', 'baz', 'qux', 'quux')).to.be.true
})
it('should survive not finding a process when a process fails', function () {
data._daemon = new EventEmitter()
data._listenForUncaughtExceptions()
data._daemon.emit('process:failed', {
id: 'foo'
}, {
date: 'bar',
message: 'baz',
code: 'qux',
stack: 'quux'
})
})
it('should broadcast process events', function () {
var newDaemon = {
once: sinon.stub(),
getDetails: sinon.stub(),
off: sinon.stub(),
on: sinon.stub()
}
var details = {
guvnor: '2.4.2'
}
data._config.minVersion = '^2.0.0'
data._update = sinon.stub()
var proc = {
id: 'foo',
exception: sinon.stub()
}
data.processes.push(proc)
newDaemon.getDetails.callsArgWith(0, undefined, details)
data._connectedToDaemon(undefined, newDaemon)
expect(newDaemon.on.getCall(9).args[0]).to.equal('*')
newDaemon.on.getCall(9).args[1]('foo', 'bar')
expect(data._webSocketResponder.broadcast.calledWith('foo', 'test', 'bar')).to.be.true
})
it('should store heap snapshots', function () {
var proc = {
id: 'foo',
snapshot: sinon.stub()
}
var snapshot = {
id: 'bar',
date: 'baz',
path: 'qux',
size: 'quux'
}
data.processes.push(proc)
data._daemon = new EventEmitter()
data._listenForHeapSnapshots()
data._daemon.emit('process:heapdump:complete', {
id: proc.id
}, snapshot)
expect(proc.snapshot.called).to.be.true
expect(proc.snapshot.getCall(0).args).to.deep.equal([
snapshot.id, snapshot.date, snapshot.path, snapshot.size
])
})
it('should survive not finding a process when adding heap snapshots', function () {
var snapshot = {
id: 'foo'
}
data._daemon = new EventEmitter()
data._listenForHeapSnapshots()
data._daemon.emit('process:heapdump:complete', {
id: 'bar'
}, snapshot)
})
it('should remove heap snapshots', function () {
var proc = {
id: 'foo',
removeSnapshot: sinon.stub()
}
var snapshot = {
id: 'bar'
}
data.processes.push(proc)
data._daemon = new EventEmitter()
data._listenForHeapSnapshots()
data._daemon.emit('process:heapdump:removed', {
id: proc.id
}, snapshot)
expect(proc.removeSnapshot.called).to.be.true
expect(proc.removeSnapshot.getCall(0).args[0]).to.equal(snapshot.id)
})
it('should survive not finding a process when removing heap snapshots', function () {
var snapshot = {
id: 'foo'
}
data._daemon = new EventEmitter()
data._listenForHeapSnapshots()
data._daemon.emit('process:heapdump:removed', {
id: 'bar'
}, snapshot)
})
})