Skip to content

Commit

Permalink
fix(thought-process): Middleware and no recursion for act
Browse files Browse the repository at this point in the history
- add unload for catch all listeners
- update thought process docs
- minor fixes
  • Loading branch information
timkinnane committed Aug 12, 2018
1 parent 0169337 commit a66cfda
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 147 deletions.
12 changes: 7 additions & 5 deletions md/ThoughtProcess.md
Expand Up @@ -53,15 +53,17 @@ language listener, to interrupt or modify the state.

## Act

bBot takes any required action, locally or through external integrations.
bBot takes action when all else fails, locally or through external integrations.

This is an implicit outcome of `listen` and `understand` stages, but if reached
without any listener matching, bBot creates a special `CatchAllMessage` message
to receive. Effectively restarting the process for a new set of listeners that
can take action when nothing else did.
This is an outcome of `listen` and `understand` stages passing without any
listener matching, bBot creates a special `CatchAllMessage` message to receive
in new set of listeners can take action when nothing else did.

- `.listenCatchAll` adds a callback for any unmatched message

Add a middleware piece via `.actMiddleware` to execute on every matching
catch-all listener, to interrupt or modify the state.

## Respond.

bBot replies to the people it's engaged with appropriately. Canned responses
Expand Down
4 changes: 2 additions & 2 deletions src/lib/bit.ts
Expand Up @@ -71,8 +71,8 @@ export class Bit implements IBit {
constructor (options: IBit) {
this.id = (options.id) ? options.id : bot.counter('bit')
for (let key of Object.keys(options)) this[key] = options[key]
if (!this.send && !this.callback) {
bot.logger.warn('[bit] won\'t work without a send or callback attribute.')
if (!this.strings && !this.attach && !this.callback) {
bot.logger.warn(`[bit] won't work without a strings, attach or callback attribute.`)
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/brain.ts
Expand Up @@ -89,6 +89,7 @@ export function unset (key: string, collection: string = 'private') {
/**
* Keep serial data in collection, via adapter (converted to plain objects)
* @todo add test that stored state translates to plain object.
* @todo refactor object filtering as a single exported utility (+tests)
*/
export async function keep (collection: string, data: any) {
if (!bot.adapters.storage) {
Expand Down
8 changes: 4 additions & 4 deletions src/lib/listen.spec.ts
Expand Up @@ -234,7 +234,7 @@ describe('listen', () => {
})
})
describe('.listenLeave', () => {
it('.process calls callback on enter messages', async () => {
it('.process calls callback on leave messages', async () => {
const callback = sinon.spy()
const message = new bot.LeaveMessage(mockUser)
const id = listen.listenLeave(callback)
Expand All @@ -243,7 +243,7 @@ describe('listen', () => {
})
})
describe('.listenTopic', () => {
it('.process calls callback on enter messages', async () => {
it('.process calls callback on topic messages', async () => {
const callback = sinon.spy()
const message = new bot.TopicMessage(mockUser)
const id = listen.listenTopic(callback)
Expand All @@ -252,11 +252,11 @@ describe('listen', () => {
})
})
describe('.listenCatchAll', () => {
it('.process calls callback on enter messages', async () => {
it('.process calls callback on catchAll messages', async () => {
const callback = sinon.spy()
const message = new bot.CatchAllMessage(new bot.TextMessage(mockUser, ''))
const id = listen.listenCatchAll(callback)
await listen.listeners[id].process(new bot.B({ message }))
await listen.catchAllListeners[id].process(new bot.B({ message }))
sinon.assert.calledOnce(callback)
})
})
Expand Down
1 change: 1 addition & 0 deletions src/lib/listen.ts
Expand Up @@ -20,6 +20,7 @@ export const catchAllListeners: {
export function unloadListeners () {
for (let id in listeners) delete listeners[id]
for (let id in nluListeners) delete nluListeners[id]
for (let id in catchAllListeners) delete catchAllListeners[id]
}

/** Interface for matcher functions - resolved value must be truthy */
Expand Down
1 change: 0 additions & 1 deletion src/lib/logger.ts
@@ -1,5 +1,4 @@
import * as winston from 'winston'
import * as bot from '..'

/**
* Allows extensions to create new logs
Expand Down
46 changes: 33 additions & 13 deletions src/lib/middelware.spec.ts
Expand Up @@ -14,15 +14,15 @@ describe('middleware', () => {
beforeEach(() => middleware.unloadMiddleware())
describe('.register', () => {
it('adds a piece to the stack', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = (b, next, done) => next()
testMiddleware.register(piece)
expect(testMiddleware.stack).to.eql([piece])
})
})
describe('.execute', () => {
it('returns a promise', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = (b, next, done) => next()
const complete = (b, done) => done()
const callback = () => null
Expand All @@ -32,23 +32,35 @@ describe('middleware', () => {
return promise
})
it('executes synchronous pieces', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = sinon.spy((b, next, done) => next())
const complete = (b, done) => done()
const callback = () => sinon.assert.calledOnce(piece)
testMiddleware.register(piece)
return testMiddleware.execute({ message }, complete, callback)
})
it('executes asynchronous pieces', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = sinon.spy((b, next, done) => process.nextTick(() => next()))
const complete = (b, done) => done()
const callback = () => sinon.assert.calledOnce(piece)
testMiddleware.register(piece)
testMiddleware.execute({ message }, complete, callback)
})
it('resolves after asynchronous pieces', async () => {
const testMiddleware = new middleware.Middleware('test')
const asyncPiece = (b, next, done) => {
return delay(50).then(() => {
b.delayed = true
next()
})
}
testMiddleware.register(asyncPiece)
const b = await testMiddleware.execute({ message }, (b, done) => done())
expect(b.delayed).to.equal(true)
})
it('creates state when given plain object', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = sinon.spy((b, next, done) => next())
const complete = (b, done) => {
expect(b).to.be.instanceof(bot.B)
Expand All @@ -59,7 +71,7 @@ describe('middleware', () => {
})
it('accepts and processes initialised state', () => {
const b = new bot.B({ message })
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = sinon.spy((b, next, done) => next())
const complete = (b, done) => {
expect(b).to.eql(b)
Expand All @@ -69,7 +81,7 @@ describe('middleware', () => {
return testMiddleware.execute({ message }, complete)
})
it('executes synchronous pieces in order', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const pieceA = sinon.spy((b, next, done) => next())
const pieceB = sinon.spy((b, next, done) => next())
const complete = (b, done) => done()
Expand All @@ -79,7 +91,7 @@ describe('middleware', () => {
testMiddleware.execute({ message }, complete, callback)
})
it('executes asynchronous pieces in order', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const pieceA = sinon.spy((b, next, done) => {
return delay(20).then(() => next())
})
Expand All @@ -93,23 +105,23 @@ describe('middleware', () => {
return testMiddleware.execute({ message }, complete, callback)
})
it('executes the complete function when there are no other pieces', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const complete = sinon.spy((b, done) => done())
const callback = () => null
return testMiddleware.execute({ message }, complete, callback).then(() => {
sinon.assert.calledOnce(complete)
})
})
it('executes the callback when there are no pieces', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const complete = (b, done) => done()
const callback = sinon.spy()
return testMiddleware.execute({ message }, complete, callback).then(() => {
sinon.assert.calledOnce(callback)
})
})
it('executes the complete function when there are other pieces', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = (b, next, done) => next()
const complete = sinon.spy((b, done) => done())
const callback = () => null
Expand All @@ -119,7 +131,7 @@ describe('middleware', () => {
})
})
it('executes the callback after complete function', () => {
const testMiddleware = new middleware.Middleware('complete')
const testMiddleware = new middleware.Middleware('test')
const piece = (b, next, done) => next()
const complete = (b, done) => done()
const callback = sinon.spy()
Expand Down Expand Up @@ -229,7 +241,7 @@ describe('middleware', () => {
it('creates middleware for each thought process', () => {
middleware.loadMiddleware()
expect(middleware.middlewares).to.include.all.keys([
'hear', 'listen', 'understand', 'respond', 'remember'
'hear', 'listen', 'understand', 'act', 'respond', 'remember'
])
})
it('creates all middleware instances', () => {
Expand Down Expand Up @@ -278,6 +290,14 @@ describe('middleware', () => {
expect(middleware.middlewares.understand.stack[0]).to.eql(mockPiece)
})
})
describe('.actMiddleware', () => {
it('registers piece in act stack', () => {
middleware.loadMiddleware()
const mockPiece = sinon.spy()
middleware.actMiddleware(mockPiece)
expect(middleware.middlewares.act.stack[0]).to.eql(mockPiece)
})
})
describe('.respondMiddleware', () => {
it('registers piece in respond stack', () => {
middleware.loadMiddleware()
Expand Down
9 changes: 9 additions & 0 deletions src/lib/middleware.ts
Expand Up @@ -26,6 +26,8 @@ export interface IPiece {
* piece and can be called (with no arguments) to interrupt the stack and begin
* executing the chain of completion functions.
* @todo Test newDone is called if done called with a function - suspect not
* @todo let return void or promise<void>, wrap in promise resolve in execute,
* update tests that use `() => Promise.resolve()` to just `() => null`
*/
export interface IPieceDone {
(newDone?: IPieceDone): Promise<void>
Expand Down Expand Up @@ -151,6 +153,7 @@ export function loadMiddleware () {
middlewares.hear = new Middleware('hear')
middlewares.listen = new Middleware('listen')
middlewares.understand = new Middleware('understand')
middlewares.act = new Middleware('act')
middlewares.respond = new Middleware('respond')
middlewares.remember = new Middleware('remember')
}
Expand All @@ -160,6 +163,7 @@ export function unloadMiddleware () {
delete middlewares.hear
delete middlewares.listen
delete middlewares.understand
delete middlewares.act
delete middlewares.respond
delete middlewares.remember
}
Expand All @@ -179,6 +183,11 @@ export function understandMiddleware (middlewarePiece: IPiece) {
middlewares.understand.register(middlewarePiece)
}

/** Register middleware piece to execute with catch-all match */
export function actMiddleware (middlewarePiece: IPiece) {
middlewares.act.register(middlewarePiece)
}

/** Register middleware piece to execute before sending any response */
export function respondMiddleware (middlewarePiece: IPiece) {
middlewares.respond.register(middlewarePiece)
Expand Down

0 comments on commit a66cfda

Please sign in to comment.