reshuffle
Version:
Reshuffle is a fast, unopinionated, minimalist integration framework
393 lines (279 loc) • 12.6 kB
text/typescript
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()
})
})
})