tribune
Version:
Holy advocate to the Roman Senate, your Tribune will ensure the Consuls have your interests in mind.
472 lines (390 loc) • 14.4 kB
JavaScript
/* eslint-disable max-len */
/* global describe it */
const nock = require('nock');
const assert = require('assert');
const Tribune = require('../lib/tribune');
const Service = require('../lib/service');
describe('new Tribune(opts) -> this', () => {
it('throws if opts.agentUrl is not given', () => {
assert.throws(
() => new Tribune({}),
/agentUrl/
);
});
describe('#get isRegistered -> isRegistered', () => {
it('evaluates to true if _serviceId and _preparedQueryId is truthy', () => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._serviceId = '__SERVICE_ID__';
tribune._preparedQueryId = '__PREPARED_QUERY_ID__';
assert.ok(tribune.isRegistered);
});
it('evaluates to false if _serviceId or _preparedQueryId is falsy', () => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._serviceId = '';
tribune._preparedQueryId = '';
assert.ok(!tribune.isRegistered);
});
});
describe('#register(url, opts, cb(err))', () => {
it('calls back with error if serviceName is not given', (cb) => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.register(undefined, {
url : 'http://service.url',
healthCheckUrl: 'http://service.url/status',
routes : ['foo/bar']
}, (err) => {
assert.ok(err.message.match(/serviceName/));
cb(null);
});
});
it('calls back with error if opts.url is not given', (cb) => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.register('serviceName', {
healthCheckUrl: 'http://service.url/status',
routes : ['foo/bar']
}, (err) => {
assert.ok(err.message.match(/url/));
cb(null);
});
});
it('calls back with error if opts.healthCheckUrl is not given', (cb) => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.register('serviceName', {
url : 'http://service.url',
routes: ['foo/bar']
}, (err) => {
assert.ok(err.message.match(/healthCheckUrl/));
cb(null);
});
});
it('removes ghosts and dupes of itself from consul then registers itself', (cb) => {
nock('http://agent.url')
.get('/v1/agent/services')
.reply(200, {
__SERVICE_ID__: {
ID: '__SERVICE_ID__',
Service: 'serviceName',
Tags: ['%24protocol%3Ahttp%5C%3A', '%24routes%3A%2Ffoo%2Fbar%2C%2Fbaz'],
Address: 'service.url',
Port: 8000,
EnableTagOverride: false,
CreateIndex: 0,
ModifyIndex: 0
}
})
.get('/v1/agent/service/deregister/__SERVICE_ID__')
.reply(200)
.get('/v1/agent/checks')
.reply(200, {
'service:__SERVICE_ID__': {
Node: 'computer.local',
CheckID: 'service:__SERVICE_ID__',
Name: 'Service \'serviceName\' check',
Status: 'critical',
Notes: '',
Output: 'HTTP GET http://localhost:8000/status: 204 No Content Output: ',
ServiceID: '__SERVICE_ID__',
ServiceName: 'serviceName',
CreateIndex: 0,
ModifyIndex: 0
}
})
.get('/v1/agent/service/deregister/__SERVICE_ID__')
.reply(200)
.put('/v1/agent/service/register', {
ID: '__SERVICE_ID__',
Name: 'serviceName',
Tags: ['%24protocol%3Ahttp%5C%3A', '%24routes%3A%2Ffoo%2Fbar%2C%2Fbaz'],
Address: 'service.url',
Port: 8000,
Check: {
HTTP: 'http://service.url:8000/status',
Interval: '1000ms'
}
})
.reply(200)
.get('/v1/query')
.reply(200, [])
.post('/v1/query', {
Name : 'serviceName',
Service: { Service: 'serviceName' }
})
.reply(200, {
ID: '__PREPARED_QUERY_ID__'
});
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._generateServiceId = function() { this._serviceId = '__SERVICE_ID__'; };
tribune.register('serviceName', {
url : 'http://service.url:8000',
healthCheckUrl: 'http://service.url:8000/status',
routes : ['/foo/bar', '/baz'],
interval : 1000
}, (err) => {
assert.ifError(err);
nock.cleanAll();
cb(null);
});
});
it('calls back with an error if service registration responds with a non 200 status code', (cb) => {
nock('http://agent.url')
.get('/v1/agent/services')
.reply(200, {})
.get('/v1/agent/checks')
.reply(200, {})
.put('/v1/agent/service/register')
.reply(400)
.get('/v1/query')
.reply(200, [])
.post('/v1/query', {})
.reply(200, {});
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.register('serviceName', {
url : 'http://service.url:8000',
healthCheckUrl: 'http://service.url:8000/status',
routes : ['/foo/bar', '/baz'],
interval : 1000
}, (err) => {
assert.ok(err.message.match(/400/));
nock.cleanAll();
cb(null);
});
});
it('calls back with an error if prepared query registration responds with a non 200 status code', (cb) => {
nock('http://agent.url')
.get('/v1/agent/services')
.reply(200, {})
.get('/v1/agent/checks')
.reply(200, {})
.put('/v1/agent/service/register')
.reply(200, {})
.get('/v1/query')
.reply(200, [])
.post('/v1/query', {})
.reply(400, {});
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.register('serviceName', {
url : 'http://service.url:8000',
healthCheckUrl: 'http://service.url:8000/status',
routes : ['/foo/bar', '/baz'],
interval : 1000
}, (err) => {
assert.ok(err.message.match(/400/));
nock.cleanAll();
cb(null);
});
});
it('sets a _serviceId and _preparedQueryId during register', (cb) => {
nock('http://agent.url')
.get('/v1/agent/services')
.reply(200, {})
.get('/v1/agent/checks')
.reply(200, {})
.put('/v1/agent/service/register')
.reply(200)
.get('/v1/query')
.reply(200, [])
.post('/v1/query', {
Name : 'serviceName',
Service: { Service: 'serviceName' }
})
.reply(200, {
ID: '__PREPARED_QUERY_ID__'
});
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.register('serviceName', {
url : 'http://service.url:8000',
healthCheckUrl: 'http://service.url:8000/status',
routes : ['/foo/bar', '/baz'],
interval : 1000
}, (err) => {
assert.ifError(err);
assert.ok(tribune._serviceId.match(/serviceName-[\d]{12}/));
assert.equal(tribune._preparedQueryId, '__PREPARED_QUERY_ID__');
nock.cleanAll();
cb(null);
});
});
});
describe('#deregister(cb(err))', () => {
const executePreparedQueryResponse = {
Service : 'serviceName',
DNS : { TTL: '' },
Datacenter : 'dc1',
Failovers : 0,
Index : 0,
KnownLeader: true,
LastContact: 0,
Nodes: [{
Service: {
ID : '__SERVICE_ID__',
Address : 'service.url',
CreateIndex : 0,
EnableTagOverride: false,
ModifyIndex : 0,
Port : 8000,
Service : 'serviceName',
Tags : ['%24protocol%3Ahttp%5C%3A', '%24routes%3A%2Ffoo%2Fbar%2C%2Fbaz']
}
}]
};
it('returns an error if service register was not previously called', (cb) => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._preparedQueryId = true;
tribune._serviceId = undefined;
tribune.deregister((err) => {
assert.ok(err.message.match(/Cannot deregister\. Register not called\./));
cb(null);
});
});
it('returns an error if prepared query register was not previously called', (cb) => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._serviceId = true;
tribune._preparedQueryId = undefined;
tribune.deregister((err) => {
assert.ok(err.message.match(/Cannot deregister\. Register not called\./));
cb(null);
});
});
it('makes a request to the consul agent to deregister as a service', (cb) => {
nock('http://agent.url')
.get('/v1/agent/service/deregister/__SERVICE_ID__')
.reply(200)
.get('/v1/query/serviceName/execute')
.reply(200, executePreparedQueryResponse);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._serviceName = 'serviceName';
tribune._serviceId = '__SERVICE_ID__';
tribune._preparedQueryId = '__PREPARED_QUERY_ID__';
tribune.deregister((err) => {
assert.ifError(err);
nock.cleanAll();
cb(null);
});
});
it('calls back with an error if consul agent deregister responds with a non 200 status code', (cb) => {
nock('http://agent.url')
.get('/v1/agent/service/deregister/__SERVICE_ID__')
.reply(400);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._serviceId = '__SERVICE_ID__';
tribune._preparedQueryId = '__PREPARED_QUERY_ID__';
tribune.deregister((err) => {
assert.ok(err.message.match(/400/));
nock.cleanAll();
cb(null);
});
});
it('calls back with an error if consul prepared query deregister responds with a non 200 || 404 status code', (cb) => {
nock('http://agent.url')
.get('/v1/agent/service/deregister/__SERVICE_ID__')
.reply(200)
.get('/v1/query/serviceName/execute')
.reply(400, executePreparedQueryResponse);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._serviceName = 'serviceName';
tribune._serviceId = '__SERVICE_ID__';
tribune._preparedQueryId = '__PREPARED_QUERY_ID__';
tribune.deregister((err) => {
assert.ok(err.message.match(/400/));
nock.cleanAll();
cb(null);
});
});
it('sets _serviceId and _preparedQueryId to an empty string during deregister', (cb) => {
nock('http://agent.url')
.get('/v1/agent/service/deregister/__SERVICE_ID__')
.reply(200)
.get('/v1/query/serviceName/execute')
.reply(200, {
Service : 'serviceName',
DNS : { TTL: '' },
Datacenter : 'dc1',
Failovers : 0,
Index : 0,
KnownLeader: true,
LastContact: 0,
Nodes: []
})
.delete('/v1/query/__PREPARED_QUERY_ID__')
.reply(200);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune._serviceName = 'serviceName';
tribune._serviceId = '__SERVICE_ID__';
tribune._preparedQueryId = '__PREPARED_QUERY_ID__';
tribune.deregister((err) => {
assert.ifError(err);
assert.equal(tribune._serviceId, '');
assert.equal(tribune._preparedQueryId, '');
nock.cleanAll();
cb(null);
});
});
});
describe('#service(serviceName) -> Service', () => {
it('returns a service instance with the service name set', () => {
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
const service = tribune.service('serviceName');
assert.ok(service instanceof Service);
assert.equal(service._tribune, tribune);
assert.equal(service._name, 'serviceName');
});
});
describe('#status(opts, cb(err))', () => {
it('returns a status code of 503 if consult leader check returns an empty string', (cb) => {
nock('http://agent.url')
.get('/v1/status/leader')
.reply(200, '')
.get('/v1/status/peers')
.reply(200, []);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.status({}, (err, res) => {
if (err) { return cb(err); }
assert.equal(res.statusCode, 503);
nock.cleanAll();
cb(null);
});
});
it('returns a 503 status code if consult peers check returns an empty array', (cb) => {
nock('http://agent.url')
.get('/v1/status/leader')
.reply(200, 'http://localhost:8100')
.get('/v1/status/peers')
.reply(200, []);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.status({}, (err, res) => {
if (err) { return cb(err); }
assert.equal(res.statusCode, 503);
nock.cleanAll();
cb(null);
});
});
it('returns a 503 status code if consult peers check returns less peers than defined in options', (cb) => {
nock('http://agent.url')
.get('/v1/status/leader')
.reply(200, 'http://localhost:8100')
.get('/v1/status/peers')
.reply(200, ['peer1']);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.status({ minPeers: 2 }, (err, res) => {
if (err) { return cb(err); }
assert.equal(res.statusCode, 503);
nock.cleanAll();
cb(null);
});
});
it('returns a 200 status code if consult peers and leader checks are returns a success response', (cb) => {
nock('http://agent.url')
.get('/v1/status/leader')
.reply(200, 'http://localhost:8100')
.get('/v1/status/peers')
.reply(200, ['peer1', 'peer1']);
const tribune = new Tribune({ agentUrl: 'http://agent.url' });
tribune.status({ minPeers: 2 }, (err, res) => {
if (err) { return cb(err); }
assert.equal(res.statusCode, 200);
nock.cleanAll();
cb(null);
});
});
});
});