UNPKG

sedmolestiae

Version:

A JavaScript implementation the Netflix OSS service registry, Eureka.

602 lines (480 loc) 17.8 kB
import sinon from 'sinon'; import chai from 'chai'; import sinonChai from 'sinon-chai'; import request from 'request'; import dns from 'dns'; import {Eureka} from '../src/eureka-client'; let expect = chai.expect; chai.use(sinonChai); describe('Eureka client', () => { describe('Eureka()', () => { it('should throw an error if no config is found', () => { function fn() { return new Eureka(); } expect(fn).to.throw(); }); it('should construct with the correct configuration values', () => { function shouldThrow() { return new Eureka(); } function noApp() { return new Eureka({ instance: { vipAddress: true, port: true, dataCenterInfo: { name: 'MyOwn' } }, eureka: { host: true, port: true } }); } function shouldWork() { return new Eureka({ instance: { app: true, vipAddress: true, port: true, dataCenterInfo: { name: 'MyOwn' } }, eureka: { host: true, port: true } }); } expect(shouldThrow).to.throw(); expect(noApp).to.throw(/app/); expect(shouldWork).to.not.throw(); }); }); describe('get instanceId()', () => { it('should return hostname for non-AWS datacenters', () => { let config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; let client = new Eureka(config); expect(client.instanceId).to.equal('myhost'); }); it('should return instance ID for AWS datacenters', () => { let config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'Amazon', metadata: {'instance-id': 'i123'}}}, eureka: {host: '127.0.0.1', port: 9999} }; let client = new Eureka(config); expect(client.instanceId).to.equal('i123'); }); }); describe('start()', () => { let config, client, registerSpy, fetchRegistrySpy; before(() => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); registerSpy = sinon.stub(client, 'register').callsArg(0); fetchRegistrySpy = sinon.stub(client, 'fetchRegistry').callsArg(0); }); after(() => { registerSpy.restore(); fetchRegistrySpy.restore(); }); it('should call register and fetch registry', (done) => { client.start(function() { expect(registerSpy).to.have.been.calledOnce; expect(fetchRegistrySpy).to.have.been.calledOnce; done(); }); }); }); describe('stop()', () => { let config, client, deregisterSpy; before(() => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); deregisterSpy = sinon.stub(client, 'deregister').callsArg(0); }); after(() => { deregisterSpy.restore(); }); it('should call deregister', () => { let stopCb = sinon.spy(); client.stop(stopCb); expect(deregisterSpy).to.have.been.calledOnce; expect(stopCb).to.have.been.calledOnce; }); }); describe('register()', () => { let config, client, heartbeatsSpy, registryFetchSpy; beforeEach(() => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); heartbeatsSpy = sinon.stub(client, 'startHeartbeats'); registryFetchSpy = sinon.stub(client, 'startRegistryFetches'); }); afterEach(() => { request.post.restore(); heartbeatsSpy.restore(); registryFetchSpy.restore(); }); it('should call register URI, and initiate heartbeats / registry fetches', () => { sinon.stub(request, 'post').yields(null, {statusCode: 204}, null) let registerCb = sinon.spy(); client.register(registerCb); expect(request.post).to.have.been.calledWithMatch({ body: { instance: { app: 'app', hostName: 'myhost', dataCenterInfo: { name: 'MyOwn' }, port: 9999, status: 'UP', vipAddress: '1.2.2.3' } }, json: true, url: 'http://127.0.0.1:9999/eureka/v2/apps/app' }); expect(heartbeatsSpy).to.have.been.calledOnce; expect(registryFetchSpy).to.have.been.calledOnce; expect(registerCb).to.have.been.calledWithMatch(null); }); it('should throw error for non-204 response', () => { sinon.stub(request, 'post').yields(null, {statusCode: 500}, null); let registerCb = sinon.spy(); client.register(registerCb); expect(registerCb).to.have.been.calledWithMatch({message:'eureka registration FAILED: status: 500 body: null'}); expect(heartbeatsSpy).to.have.callCount(0); expect(registryFetchSpy).to.have.callCount(0); }); it('should throw error for request error', () => { sinon.stub(request, 'post').yields(new Error('request error'), null, null); let registerCb = sinon.spy(); client.register(registerCb); expect(registerCb).to.have.been.calledWithMatch({message:'request error'}); expect(heartbeatsSpy).to.have.callCount(0); expect(registryFetchSpy).to.have.callCount(0); }); }); describe('deregister()', () => { let config, client; beforeEach(() => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); }); afterEach(() => { request.del.restore(); }); it('should call deregister URI', () => { sinon.stub(request, 'del').yields(null, {statusCode: 200}, null);; let deregisterCb = sinon.spy(); client.deregister(deregisterCb); expect(request.del).to.have.been.calledWithMatch({ url: 'http://127.0.0.1:9999/eureka/v2/apps/app/myhost' }); expect(deregisterCb).to.have.been.calledWithMatch(null); }); it('should throw error for non-200 response', () => { sinon.stub(request, 'del').yields(null, {statusCode: 500}, null);; let deregisterCb = sinon.spy(); client.deregister(deregisterCb); expect(deregisterCb).to.have.been.calledWithMatch({message:'eureka deregistration FAILED: status: 500 body: null'}); }); it('should throw error for request error', () => { sinon.stub(request, 'del').yields(new Error('request error'), null, null); let deregisterCb = sinon.spy(); client.deregister(deregisterCb); expect(deregisterCb).to.have.been.calledWithMatch({message:'request error'}); }); }); describe('renew()', () => { let config, client; beforeEach(() => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); }); afterEach(() => { request.put.restore(); }); it('should call heartbeat URI', () => { sinon.stub(request, 'put').yields(null, {statusCode: 200}, null) client.renew(); expect(request.put).to.have.been.calledWithMatch({ url: 'http://127.0.0.1:9999/eureka/v2/apps/app/myhost' }); }); }); describe('validateConfig()', () => { let config; beforeEach(() => { config = { instance: {app: 'app', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: 'Amazon'}, eureka: {host: '127.0.0.1', port: 9999} }; }); it('should throw an exception with a missing instance.app', () => { function badConfig() { delete config.instance.app; return new Eureka(config); } expect(badConfig).to.throw(TypeError); }); it('should throw an exception with a missing instance.vipAddress', () => { function badConfig() { delete config.instance.vipAddress; return new Eureka(config); } expect(badConfig).to.throw(TypeError); }); it('should throw an exception with a missing instance.port', () => { function badConfig() { delete config.instance.port; return new Eureka(config); } expect(badConfig).to.throw(TypeError); }); it('should throw an exception with a missing instance.dataCenterInfo', () => { function badConfig() { delete config.instance.dataCenterInfo; return new Eureka(config); } expect(badConfig).to.throw(TypeError); }); it('should throw an exception with a missing eureka.host', () => { function badConfig() { delete config.eureka.host; return new Eureka(config); } expect(badConfig).to.throw(TypeError); }); it('should throw an exception with a missing eureka.port', () => { function badConfig() { delete config.eureka.port; return new Eureka(config); } expect(badConfig).to.throw(TypeError); }); }); describe('getInstancesByAppId()', () => { let client, config; beforeEach(() => { config = { instance: {app: 'app', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: 'Amazon'}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); }); it('should throw an exception if no appId is provided', () => { function noAppId() { client.getInstancesByAppId(); } expect(noAppId).to.throw(Error); }); it('should return a list of instances if appId is registered', () => { let appId = 'theservicename'.toUpperCase(); let expectedInstances = [{host: '127.0.0.1'}]; client.cache.app[appId] = expectedInstances; let actualInstances = client.getInstancesByAppId(appId); expect(actualInstances).to.equal(expectedInstances); }); it('should throw an error if no instances were found for given appId', () => { let appId = 'theservicename'.toUpperCase(); client.cache.app[appId] = null; function shouldThrow() { client.getInstancesByAppId(appId) } expect(shouldThrow).to.throw(); }); }); describe('getInstancesByVipAddress()', () => { let client, config; beforeEach(() => { config = { instance: {app: 'app', vipAddress: '1.2.3.4', port: 9999, dataCenterInfo: 'Amazon'}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); }); it('should throw an exception if no vipAddress is provided', () => { function noVipAddress() { client.getInstancesByVipAddress(); } expect(noVipAddress).to.throw(Error); }); it('should return a list of instances if vipAddress is registered', () => { let vipAddress = 'the.vip.address'; let expectedInstances = [{host: '127.0.0.1'}]; client.cache.vip[vipAddress] = expectedInstances; let actualInstances = client.getInstancesByVipAddress(vipAddress); expect(actualInstances).to.equal(expectedInstances); }); it('should throw an error if no instances were found for given vipAddress', () => { let vipAddress = 'the.vip.address'; client.cache.vip[vipAddress] = null; function shouldThrow() { client.getInstancesByVipAddress(vipAddress) } expect(shouldThrow).to.throw(); }); }); describe('fetchRegistry()', () => { let config, client, transformRegistrySpy; beforeEach(() => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); transformRegistrySpy = sinon.stub(client, 'transformRegistry'); }); afterEach(() => { request.get.restore(); client.transformRegistry.restore(); }); it('should call registry URI', () => { sinon.stub(request, 'get').yields(null, {statusCode: 200}, null) let registryCb = sinon.spy(); client.fetchRegistry(registryCb); expect(request.get).to.have.been.calledWithMatch({ url: 'http://127.0.0.1:9999/eureka/v2/apps/', headers: { Accept: 'application/json' } }); expect(registryCb).to.have.been.calledWithMatch(null); }); it('should throw error for non-200 response', () => { sinon.stub(request, 'get').yields(null, {statusCode: 500}, null); let registryCb = sinon.spy(); client.fetchRegistry(registryCb); expect(registryCb).to.have.been.calledWithMatch({message:'Unable to retrieve registry from Eureka server'}); }); it('should throw error for request error', () => { sinon.stub(request, 'get').yields(new Error('request error'), null, null); let registryCb = sinon.spy(); client.fetchRegistry(registryCb); expect(registryCb).to.have.been.calledWithMatch({message:'request error'}); }); }); describe('transformRegistry()', () => { let client, config, registry, app1, app2, transformSpy; beforeEach(() => { config = { instance: {app: 'app', vipAddress: '1.2.3.4', port: 9999, dataCenterInfo: 'Amazon'}, eureka: {host: '127.0.0.1', port: 9999} }; registry = { applications: {application: {}} }; app1 = {}; app2 = {}; client = new Eureka(config); transformSpy = sinon.stub(client, 'transformApp'); }); afterEach(() => { transformSpy.restore(); }); it('should throw an error if no registry is provided', () => { function noRegistry() { client.transformRegistry(); } expect(noRegistry).to.throw(); }); it('should return clear the cache if no applications exist', () => { registry.applications.application = null; client.transformRegistry(registry); expect(client.cache.vip).to.be.empty; expect(client.cache.app).to.be.empty; }); it('should transform a registry with one app', () => { registry.applications.application = app1; client.transformRegistry(registry); expect(transformSpy.callCount).to.equal(1); }); it('should transform a registry with two or more apps', () => { registry.applications.application = [app1, app2]; client.transformRegistry(registry); expect(transformSpy.callCount).to.equal(2); }); }); describe('transformApp()', () => { let client, config, app, instance1, instance2, theVip; beforeEach(() => { config = { instance: {app: 'app', vipAddress: '1.2.3.4', port: 9999, dataCenterInfo: 'Amazon'}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); theVip = 'theVip'; instance1 = {host: '127.0.0.1', port: 1000, vipAddress: theVip}; instance2 = {host: '127.0.0.2', port: 2000, vipAddress: theVip}; app = {name: 'theapp'}; }); it('should transform an app with one instance', () => { app.instance = instance1; client.transformApp(app); expect(client.cache.app[app.name.toUpperCase()].length).to.equal(1); expect(client.cache.vip[theVip].length).to.equal(1); }); it('should transform an app with two or more instances', () => { app.instance = [instance1, instance2]; client.transformApp(app); expect(client.cache.app[app.name.toUpperCase()].length).to.equal(2); expect(client.cache.vip[theVip].length).to.equal(2); }); }); describe('locateEurekaHostUsingDns()', () => { let config, client; let eurekaHosts = ['1a.eureka.mydomain.com','1b.eureka.mydomain.com','1c.eureka.mydomain.com']; afterEach(() => { dns.resolveTxt.restore(); }); it('should throw error when ec2Region is undefined', () => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: '127.0.0.1', port: 9999} }; client = new Eureka(config); sinon.stub(dns, 'resolveTxt').yields(null, []); function noRegion() { client.locateEurekaHostUsingDns(); } expect(noRegion).to.throw(Error); }); it('should lookup server list using DNS', () => { config = { instance: {app: 'app', hostName: 'myhost', vipAddress: '1.2.2.3', port: 9999, dataCenterInfo: {name:'MyOwn'}}, eureka: {host: 'eureka.mydomain.com', port: 9999, ec2Region: 'my-region'} }; client = new Eureka(config); let locateCb = sinon.spy(); let resolveStub = sinon.stub(dns, 'resolveTxt'); resolveStub.onCall(0).yields(null, [eurekaHosts]); resolveStub.onCall(1).yields(null, [['1.2.3.4']]); client.locateEurekaHostUsingDns(locateCb); expect(dns.resolveTxt).to.have.been.calledWithMatch('txt.my-region.eureka.mydomain.com'); expect(dns.resolveTxt).to.have.been.calledWith(sinon.match(function(value) { return eurekaHosts.indexOf(value); })); expect(locateCb).to.have.been.calledWithMatch('1.2.3.4'); }); }); });