import request from 'supertest' import { CronConnector, HttpConnector, Reshuffle } from '../src' import { BaseConnector, EventConfiguration } from 'reshuffle-base-connector' describe('Reshuffle', () => { describe('create, start and restart', () => { describe('create Reshuffle application', () => { it('creates a new app running on default port', () => { const app = new Reshuffle() expect(app.port).toEqual(8000) expect(app.registry).toEqual({ common: { webserver: undefined }, connectors: {}, handlers: {}, }) expect(app.httpDelegates).toEqual({}) }) it('creates a new app running on a specific port', () => { process.env.PORT = '1234' const app = new Reshuffle() expect(app.port).toEqual(parseInt(process.env.PORT, 10)) }) it('creates a new app using custom logger options', () => { const app = new Reshuffle({ level: 'debug' }) expect(app.getLogger().isDebugEnabled()).toBe(true) }) }) describe('start', () => { it('starts the app on a specific port', () => { const app = new Reshuffle() const myPort = 2345 app.start(myPort) expect(app.port).toBe(myPort) }) it('starts the app and invoke callback function id provided', () => { const callbackOnStart = jest.fn() const app = new Reshuffle() app.start(undefined, callbackOnStart) expect(callbackOnStart).toHaveBeenCalledTimes(1) }) }) describe('restart', () => { it('restarts the app', () => { const myPort = 6789 const app = new Reshuffle() app.start = jest.fn(app.start) app.stopWebServer = jest.fn(app.stopWebServer) app.restart(myPort) expect(app.stopWebServer).toHaveBeenCalled() expect(app.start).toHaveBeenCalledWith(myPort, expect.anything()) }) }) }) describe('get, register and unregister connectors', () => { it('registers a connector, getConnector and unregisters it', async () => { const app = new Reshuffle() const connector = new BaseConnector(app) expect(Object.keys(app.registry.connectors)).toHaveLength(1) expect(app.getConnector(connector.id)).toBe(connector) await app.unregister(connector) expect(Object.keys(app.registry.connectors)).toHaveLength(0) expect(app.getConnector(connector.id)).toBe(undefined) }) it('starts all registered connectors on application start', async () => { const app = new Reshuffle() const connector1 = new BaseConnector(app) connector1.start = jest.fn() const connector2 = new BaseConnector(app) connector2.start = jest.fn() const connector3 = new BaseConnector(app) connector3.start = jest.fn() expect(Object.keys(app.registry.connectors)).toHaveLength(3) app.start() expect(connector1.start).toHaveBeenCalledTimes(1) expect(connector2.start).toHaveBeenCalledTimes(1) expect(connector3.start).toHaveBeenCalledTimes(1) }) }) describe('connect event and handlers using `when`', () => { it('registers a new handler for the event', async (done) => { const app = new Reshuffle() const httpConnector = new HttpConnector(app) const cronConnector = new CronConnector(app) const cronHandler = jest.fn() httpConnector.on({ method: 'GET', path: '/test1' }, () => console.log('http')) cronConnector.on({ expression: '*/1 * * * * *' }, cronHandler) expect(Object.keys(app.registry.handlers)).toHaveLength(2) app.start() setTimeout(() => { expect(cronHandler).toHaveBeenCalledTimes(1) app.unregister(httpConnector) app.unregister(cronConnector) app.stopWebServer() done() }, 1000) }) it('supports multi handlers per event', async (done) => { const app = new Reshuffle() const timerConnector = new CronConnector(app) const cronHandler1 = jest.fn() const cronHandler2 = jest.fn() const event = timerConnector.on({ expression: '*/1 * * * * *' }, cronHandler1) timerConnector.on({ expression: '*/1 * * * * *' }, cronHandler2, event.id) expect(app.registry.handlers[event.id]).toHaveLength(2) app.start() setTimeout(() => { expect(cronHandler1).toHaveBeenCalledTimes(1) expect(cronHandler2).toHaveBeenCalledTimes(1) done() app.stopWebServer() }, 1000) }) it('supports passing our own Handler object', () => { const app = new Reshuffle() const timerConnector = new CronConnector(app) const cronHandler = { handle: (e: any) => console.log(e), id: 'myCustomId' } const event = timerConnector.on({ expression: '*/1 * * * *' }, cronHandler) expect(app.registry.handlers[event.id][0].id).toEqual('myCustomId') }) }) describe('handleEvent', () => { it('returns false if no handler found', async () => { const app = new Reshuffle() const connector = new BaseConnector(app) const anEvent = new EventConfiguration('id', connector, {}) expect(await app.handleEvent('id', anEvent)).toBe(false) }) it('keeps running when handler throw errors', async () => { const app = new Reshuffle() const connector = new BaseConnector(app) const anEvent = new EventConfiguration('id', connector, {}) app.when(anEvent, () => { throw new Error() }) expect(await app.handleEvent('id', anEvent)).toBe(false) }) }) describe('http server', () => { it('registers http delegate and start web server', () => { const app = new Reshuffle() expect(app.registry.common.webserver).toBeUndefined() const connector1 = new HttpConnector(app) connector1.start = jest.fn() const connector2 = new HttpConnector(app) connector2.start = jest.fn() app.registerHTTPDelegate('/test1', connector1) app.registerHTTPDelegate('/test2', connector2) expect(Object.keys(app.httpDelegates)).toHaveLength(2) app.start() expect(app.registry.common.webserver).toBeDefined() app.stopWebServer() }) it('unregisters http delegate', async () => { const app = new Reshuffle() expect(app.registry.common.webserver).toBeUndefined() const connector1 = new HttpConnector(app) connector1.start = jest.fn() connector1.on({ method: 'GET', path: '/test1' }, () => console.log('test1')) expect(Object.keys(app.httpDelegates['/test1'].delegates)).toHaveLength(1) app.start() await app.unregister(connector1) expect(Object.keys(app.httpDelegates['/test1'].delegates)).toHaveLength(0) app.stopWebServer() }) describe('web server', () => { it('can perform an healthcheck when process.env.RESHUFFLE_HEALTH_PATH is set', async () => { const myHealthCheckPath = '/reshuffle-healthcheck' const OLD_ENV = process.env process.env.RESHUFFLE_HEALTH_PATH = myHealthCheckPath const app = new Reshuffle() const connector = new HttpConnector(app) connector.on({ method: 'GET', path: '/test' }, () => console.log('test')) app.start() const response = await request(app.registry.common.webserver).get(myHealthCheckPath) expect(response.status).toBe(200) const { ok, uptime } = JSON.parse(response.text) expect(ok).toBe(true) expect(uptime).toBeGreaterThan(0) app.stopWebServer() process.env = { ...OLD_ENV } }) it('delegates to the connector handler if the route matches and returns a 200', async () => { const app = new Reshuffle() expect(app.registry.common.webserver).toBeUndefined() const connector1 = new HttpConnector(app) connector1.start = jest.fn() const connector2 = new HttpConnector(app) connector2.start = jest.fn() const mockHandler = (key: string) => jest.fn().mockImplementation((event) => event.res.end(`Success ${key}`)) connector1.on({ method: 'GET', path: '/test1' }, mockHandler('test1')) connector1.on({ method: 'GET', path: '/test2' }, mockHandler('test2')) app.start() const responseTest1Call = await request(app.registry.common.webserver).get('/test1') expect(responseTest1Call.status).toBe(200) expect(responseTest1Call.text).toEqual('Success test1') const responseTest2Call = await request(app.registry.common.webserver).get('/test2') expect(responseTest2Call.status).toBe(200) expect(responseTest2Call.text).toEqual('Success test2') app.stopWebServer() }) it('returns a 500 when handler throw an exception', async () => { const app = new Reshuffle() const connector = new HttpConnector(app) const mockHandler = () => jest.fn().mockImplementation(() => { throw new Error(':(') }) connector.on({ method: 'GET', path: '/test' }, mockHandler()) app.start() const responseTest1Call = await request(app.registry.common.webserver).get('/test') expect(responseTest1Call.status).toEqual(500) app.stopWebServer() }) it("returns a 501 with 'No handler registered for /route' when path matches but method doesn't", async () => { const app = new Reshuffle() expect(app.registry.common.webserver).toBeUndefined() const connector1 = new HttpConnector(app) connector1.start = jest.fn() connector1.on({ method: 'GET', path: '/test' }, () => console.log('test')) app.start() const responseTest3Call = await request(app.registry.common.webserver).post('/test') expect(responseTest3Call.status).toBe(501) expect(responseTest3Call.text).toEqual('No handler registered for POST /test') app.stopWebServer() }) it("returns a 404 with 'No handler registered for /unknownPath' whe path unknown", async () => { const app = new Reshuffle() expect(app.registry.common.webserver).toBeUndefined() const connector1 = new HttpConnector(app) connector1.start = jest.fn() connector1.on({ method: 'GET', path: '/test' }, () => console.log('test')) app.start() const responseTest3Call = await request(app.registry.common.webserver).get('/unknownPath') expect(responseTest3Call.status).toBe(501) expect(responseTest3Call.text).toContain('No handler registered for GET /unknownPath') app.stopWebServer() }) it("returns a 501 with 'No handler registered for /route' once the connector is unregistered", async () => { const app = new Reshuffle() expect(app.registry.common.webserver).toBeUndefined() const connector1 = new HttpConnector(app) connector1.on({ method: 'GET', path: '/test' }, (event: any) => event.res.end('Success')) app.start() const res = await request(app.registry.common.webserver).get('/test') expect(res.text).toContain('Success') await app.unregister(connector1) const responseTest3Call = await request(app.registry.common.webserver).get('/test') expect(responseTest3Call.status).toBe(501) expect(responseTest3Call.text).toBe('No handler registered for GET /test') app.stopWebServer() }) }) }) describe('persistentStore', () => { it('provides a default in memory persistentStore', async () => { const app = new Reshuffle() const store = app.getPersistentStore() const myText = 'my text is the persistent store' const myObject = { first: 'foo', second: 'bar' } store.set('myText', myText) store.set('myObject', myObject) expect(await store.get('myText')).toEqual(myText) expect(await store.get('myObject')).toEqual(myObject) }) }) describe('chaining', () => { it('supports chaining methods', () => { const app = new Reshuffle() const connector1 = new HttpConnector(app) const connector2 = new CronConnector(app) connector1.on({ method: 'GET', path: 'test' }, () => console.log('connector1 triggered')) app.start() expect(Object.keys(app.registry.connectors)).toHaveLength(2) expect(connector1.started).toBe(true) expect(connector2.started).toBe(true) app.stopWebServer() }) }) })