UNPKG

35.1 kBJavaScriptView Raw
1'use strict'
2
3/* global describe, beforeEach, it, afterEach */
4/* eslint-disable no-unused-expressions */
5
6// Assertions and Stubbing
7const chai = require('chai')
8const sinon = require('sinon')
9chai.use(require('sinon-chai'))
10
11const expect = chai.expect
12
13// Hubot classes
14const Robot = require('../src/robot')
15const CatchAllMessage = require('../src/message').CatchAllMessage
16const EnterMessage = require('../src/message').EnterMessage
17const LeaveMessage = require('../src/message').LeaveMessage
18const TextMessage = require('../src/message').TextMessage
19const TopicMessage = require('../src/message').TopicMessage
20
21// mock `hubot-mock-adapter` module from fixture
22const mockery = require('mockery')
23
24describe('Robot', function () {
25 beforeEach(function () {
26 mockery.enable({
27 warnOnReplace: false,
28 warnOnUnregistered: false
29 })
30 mockery.registerMock('hubot-mock-adapter', require('./fixtures/mock-adapter'))
31 this.robot = new Robot(null, 'mock-adapter', true, 'TestHubot')
32 this.robot.alias = 'Hubot'
33 this.robot.run()
34
35 // Re-throw AssertionErrors for clearer test failures
36 this.robot.on('error', function (name, err, response) {
37 if ((err != null ? err.constructor : undefined) == null) { }
38 if (err.constructor.name === 'AssertionError') {
39 process.nextTick(function () {
40 throw err
41 })
42 }
43 })
44
45 this.user = this.robot.brain.userForId('1', {
46 name: 'hubottester',
47 room: '#mocha'
48 })
49 })
50
51 afterEach(function () {
52 mockery.disable()
53 this.robot.shutdown()
54 })
55
56 describe('Unit Tests', function () {
57 describe('#http', function () {
58 beforeEach(function () {
59 const url = 'http://localhost'
60 this.httpClient = this.robot.http(url)
61 })
62
63 it('creates a new ScopedHttpClient', function () {
64 // 'instanceOf' check doesn't work here due to the design of
65 // ScopedHttpClient
66 expect(this.httpClient).to.have.property('get')
67 expect(this.httpClient).to.have.property('post')
68 })
69
70 it('passes options through to the ScopedHttpClient', function () {
71 const agent = {}
72 const httpClient = this.robot.http('http://localhost', {agent})
73 expect(httpClient.options.agent).to.equal(agent)
74 })
75
76 it('sets a sane user agent', function () {
77 expect(this.httpClient.options.headers['User-Agent']).to.contain('Hubot')
78 })
79
80 it('merges in any global http options', function () {
81 const agent = {}
82 this.robot.globalHttpOptions = {agent}
83 const httpClient = this.robot.http('http://localhost')
84 expect(httpClient.options.agent).to.equal(agent)
85 })
86
87 it('local options override global http options', function () {
88 const agentA = {}
89 const agentB = {}
90 this.robot.globalHttpOptions = {agent: agentA}
91 const httpClient = this.robot.http('http://localhost', {agent: agentB})
92 expect(httpClient.options.agent).to.equal(agentB)
93 })
94 })
95
96 describe('#respondPattern', function () {
97 it('matches messages starting with robot\'s name', function () {
98 const testMessage = this.robot.name + 'message123'
99 const testRegex = /(.*)/
100
101 const pattern = this.robot.respondPattern(testRegex)
102 expect(testMessage).to.match(pattern)
103 const match = testMessage.match(pattern)[1]
104 expect(match).to.equal('message123')
105 })
106
107 it('matches messages starting with robot\'s alias', function () {
108 const testMessage = this.robot.alias + 'message123'
109 const testRegex = /(.*)/
110
111 const pattern = this.robot.respondPattern(testRegex)
112 expect(testMessage).to.match(pattern)
113 const match = testMessage.match(pattern)[1]
114 expect(match).to.equal('message123')
115 })
116
117 it('does not match unaddressed messages', function () {
118 const testMessage = 'message123'
119 const testRegex = /(.*)/
120
121 const pattern = this.robot.respondPattern(testRegex)
122 expect(testMessage).to.not.match(pattern)
123 })
124
125 it('matches properly when name is substring of alias', function () {
126 this.robot.name = 'Meg'
127 this.robot.alias = 'Megan'
128 const testMessage1 = this.robot.name + ' message123'
129 const testMessage2 = this.robot.alias + ' message123'
130 const testRegex = /(.*)/
131
132 const pattern = this.robot.respondPattern(testRegex)
133
134 expect(testMessage1).to.match(pattern)
135 const match1 = testMessage1.match(pattern)[1]
136 expect(match1).to.equal('message123')
137
138 expect(testMessage2).to.match(pattern)
139 const match2 = testMessage2.match(pattern)[1]
140 expect(match2).to.equal('message123')
141 })
142
143 it('matches properly when alias is substring of name', function () {
144 this.robot.name = 'Megan'
145 this.robot.alias = 'Meg'
146 const testMessage1 = this.robot.name + ' message123'
147 const testMessage2 = this.robot.alias + ' message123'
148 const testRegex = /(.*)/
149
150 const pattern = this.robot.respondPattern(testRegex)
151
152 expect(testMessage1).to.match(pattern)
153 const match1 = testMessage1.match(pattern)[1]
154 expect(match1).to.equal('message123')
155
156 expect(testMessage2).to.match(pattern)
157 const match2 = testMessage2.match(pattern)[1]
158 expect(match2).to.equal('message123')
159 })
160 })
161
162 describe('#listen', () =>
163 it('registers a new listener directly', function () {
164 expect(this.robot.listeners).to.have.length(0)
165 this.robot.listen(function () {}, function () {})
166 expect(this.robot.listeners).to.have.length(1)
167 })
168 )
169
170 describe('#hear', () =>
171 it('registers a new listener directly', function () {
172 expect(this.robot.listeners).to.have.length(0)
173 this.robot.hear(/.*/, function () {})
174 expect(this.robot.listeners).to.have.length(1)
175 })
176 )
177
178 describe('#respond', () =>
179 it('registers a new listener using hear', function () {
180 sinon.spy(this.robot, 'hear')
181 this.robot.respond(/.*/, function () {})
182 expect(this.robot.hear).to.have.been.called
183 })
184 )
185
186 describe('#enter', () =>
187 it('registers a new listener using listen', function () {
188 sinon.spy(this.robot, 'listen')
189 this.robot.enter(function () {})
190 expect(this.robot.listen).to.have.been.called
191 })
192 )
193
194 describe('#leave', () =>
195 it('registers a new listener using listen', function () {
196 sinon.spy(this.robot, 'listen')
197 this.robot.leave(function () {})
198 expect(this.robot.listen).to.have.been.called
199 })
200 )
201
202 describe('#topic', () =>
203 it('registers a new listener using listen', function () {
204 sinon.spy(this.robot, 'listen')
205 this.robot.topic(function () {})
206 expect(this.robot.listen).to.have.been.called
207 })
208 )
209
210 describe('#catchAll', () =>
211 it('registers a new listener using listen', function () {
212 sinon.spy(this.robot, 'listen')
213 this.robot.catchAll(function () {})
214 expect(this.robot.listen).to.have.been.called
215 })
216 )
217
218 describe('#receive', function () {
219 it('calls all registered listeners', function (done) {
220 // Need to use a real Message so that the CatchAllMessage constructor works
221 const testMessage = new TextMessage(this.user, 'message123')
222
223 const listener = {
224 call (response, middleware, cb) {
225 cb()
226 }
227 }
228 sinon.spy(listener, 'call')
229
230 this.robot.listeners = [
231 listener,
232 listener,
233 listener,
234 listener
235 ]
236
237 this.robot.receive(testMessage, function () {
238 // When no listeners match, each listener is called twice: once with
239 // the original message and once with a CatchAll message
240 expect(listener.call).to.have.callCount(8)
241 done()
242 })
243 })
244
245 it('sends a CatchAllMessage if no listener matches', function (done) {
246 // Testing for recursion with a new CatchAllMessage that wraps the
247 // original message
248
249 const testMessage = new TextMessage(this.user, 'message123')
250 this.robot.listeners = []
251
252 // Replace @robot.receive so we can catch when the functions recurses
253 const oldReceive = this.robot.receive
254 this.robot.receive = function (message, cb) {
255 expect(message).to.be.instanceof(CatchAllMessage)
256 expect(message.message).to.be.equal(testMessage)
257 cb()
258 }
259 sinon.spy(this.robot, 'receive')
260
261 // Call the original receive method that we want to test
262 oldReceive.call(this.robot, testMessage, () => {
263 expect(this.robot.receive).to.have.been.called
264 done()
265 })
266 })
267
268 it('does not trigger a CatchAllMessage if a listener matches', function (done) {
269 const testMessage = new TextMessage(this.user, 'message123')
270
271 const matchingListener = {
272 call (message, middleware, doesMatch) {
273 // indicate that the message matched the listener
274 doesMatch(true)
275 }
276 }
277
278 // Replace @robot.receive so we can catch if the functions recurses
279 const oldReceive = this.robot.receive
280 this.robot.receive = sinon.spy()
281
282 this.robot.listeners = [
283 matchingListener
284 ]
285
286 // Call the original receive method that we want to test
287 oldReceive.call(this.robot, testMessage, done)
288
289 // Ensure the function did not recurse
290 expect(this.robot.receive).to.not.have.been.called
291 })
292
293 it('stops processing if a listener marks the message as done', function (done) {
294 const testMessage = new TextMessage(this.user, 'message123')
295
296 const matchingListener = {
297 call (message, middleware, doesMatch) {
298 message.done = true
299 // Listener must have matched
300 doesMatch(true)
301 }
302 }
303
304 const listenerSpy =
305 {call: sinon.spy()}
306
307 this.robot.listeners = [
308 matchingListener,
309 listenerSpy
310 ]
311
312 this.robot.receive(testMessage, function () {
313 expect(listenerSpy.call).to.not.have.been.called
314 done()
315 })
316 })
317
318 it('gracefully handles listener uncaughtExceptions (move on to next listener)', function (done) {
319 const testMessage = {}
320 const theError = new Error()
321
322 const badListener = {
323 call () {
324 throw theError
325 }
326 }
327
328 let goodListenerCalled = false
329 const goodListener = {
330 call (_, middleware, doesMatch) {
331 goodListenerCalled = true
332 doesMatch(true)
333 }
334 }
335
336 this.robot.listeners = [
337 badListener,
338 goodListener
339 ]
340
341 this.robot.emit = function (name, err, response) {
342 expect(name).to.equal('error')
343 expect(err).to.equal(theError)
344 expect(response.message).to.equal(testMessage)
345 }
346 sinon.spy(this.robot, 'emit')
347
348 this.robot.receive(testMessage, () => {
349 expect(this.robot.emit).to.have.been.called
350 expect(goodListenerCalled).to.be.ok
351 done()
352 })
353 })
354
355 it('executes the callback after the function returns when there are no listeners', function (done) {
356 const testMessage = new TextMessage(this.user, 'message123')
357 let finished = false
358 this.robot.receive(testMessage, function () {
359 expect(finished).to.be.ok
360 done()
361 })
362 finished = true
363 })
364 })
365
366 describe('#loadFile', function () {
367 beforeEach(function () {
368 this.sandbox = sinon.sandbox.create()
369 })
370
371 afterEach(function () {
372 this.sandbox.restore()
373 })
374
375 it('should require the specified file', function () {
376 const module = require('module')
377
378 const script = sinon.spy(function (robot) {})
379 this.sandbox.stub(module, '_load').returns(script)
380 this.sandbox.stub(this.robot, 'parseHelp')
381
382 this.robot.loadFile('./scripts', 'test-script.js')
383 expect(module._load).to.have.been.calledWith('scripts/test-script')
384 })
385
386 describe('proper script', function () {
387 beforeEach(function () {
388 const module = require('module')
389
390 this.script = sinon.spy(function (robot) {})
391 this.sandbox.stub(module, '_load').returns(this.script)
392 this.sandbox.stub(this.robot, 'parseHelp')
393 })
394
395 it('should call the script with the Robot', function () {
396 this.robot.loadFile('./scripts', 'test-script.js')
397 expect(this.script).to.have.been.calledWith(this.robot)
398 })
399
400 it('should parse the script documentation', function () {
401 this.robot.loadFile('./scripts', 'test-script.js')
402 expect(this.robot.parseHelp).to.have.been.calledWith('scripts/test-script.js')
403 })
404 })
405
406 describe('non-Function script', function () {
407 beforeEach(function () {
408 const module = require('module')
409
410 this.script = {}
411 this.sandbox.stub(module, '_load').returns(this.script)
412 this.sandbox.stub(this.robot, 'parseHelp')
413 })
414
415 it('logs a warning', function () {
416 sinon.stub(this.robot.logger, 'warning')
417 this.robot.loadFile('./scripts', 'test-script.js')
418 expect(this.robot.logger.warning).to.have.been.called
419 })
420 })
421
422 describe('unsupported file extension', function () {
423 beforeEach(function () {
424 const module = require('module')
425
426 this.script = sinon.spy(function (robot) {})
427 this.sandbox.stub(module, '_load').returns(this.script)
428 this.sandbox.stub(this.robot, 'parseHelp')
429 })
430
431 it('should not be loaded by the Robot', function () {
432 this.robot.loadFile('./scripts', 'unsupported.yml')
433 expect(this.script).to.not.have.been.calledWith(this.robot)
434 })
435 })
436 })
437
438 describe('#send', function () {
439 beforeEach(function () {
440 sinon.spy(this.robot.adapter, 'send')
441 })
442
443 it('delegates to adapter "send" with proper context', function () {
444 this.robot.send({}, 'test message')
445 expect(this.robot.adapter.send).to.have.been.calledOn(this.robot.adapter)
446 })
447 })
448
449 describe('#reply', function () {
450 beforeEach(function () {
451 sinon.spy(this.robot.adapter, 'reply')
452 })
453
454 it('delegates to adapter "reply" with proper context', function () {
455 this.robot.reply({}, 'test message')
456 expect(this.robot.adapter.reply).to.have.been.calledOn(this.robot.adapter)
457 })
458 })
459
460 describe('#messageRoom', function () {
461 beforeEach(function () {
462 sinon.spy(this.robot.adapter, 'send')
463 })
464
465 it('delegates to adapter "send" with proper context', function () {
466 this.robot.messageRoom('testRoom', 'messageRoom test')
467 expect(this.robot.adapter.send).to.have.been.calledOn(this.robot.adapter)
468 })
469 })
470
471 describe('#on', function () {
472 beforeEach(function () {
473 sinon.spy(this.robot.events, 'on')
474 })
475
476 it('delegates to events "on" with proper context', function () {
477 this.robot.on('event', function () {})
478 expect(this.robot.events.on).to.have.been.calledOn(this.robot.events)
479 })
480 })
481
482 describe('#emit', function () {
483 beforeEach(function () {
484 sinon.spy(this.robot.events, 'emit')
485 })
486
487 it('delegates to events "emit" with proper context', function () {
488 this.robot.emit('event', function () {})
489 expect(this.robot.events.emit).to.have.been.calledOn(this.robot.events)
490 })
491 })
492 })
493
494 describe('Listener Registration', function () {
495 describe('#listen', () =>
496 it('forwards the matcher, options, and callback to Listener', function () {
497 const callback = sinon.spy()
498 const matcher = sinon.spy()
499 const options = {}
500
501 this.robot.listen(matcher, options, callback)
502 const testListener = this.robot.listeners[0]
503
504 expect(testListener.matcher).to.equal(matcher)
505 expect(testListener.callback).to.equal(callback)
506 expect(testListener.options).to.equal(options)
507 })
508 )
509
510 describe('#hear', function () {
511 it('matches TextMessages', function () {
512 const callback = sinon.spy()
513 const testMessage = new TextMessage(this.user, 'message123')
514 const testRegex = /^message123$/
515
516 this.robot.hear(testRegex, callback)
517 const testListener = this.robot.listeners[0]
518 const result = testListener.matcher(testMessage)
519
520 expect(result).to.be.ok
521 })
522
523 it('does not match EnterMessages', function () {
524 const callback = sinon.spy()
525 const testMessage = new EnterMessage(this.user)
526 const testRegex = /.*/
527
528 this.robot.hear(testRegex, callback)
529 const testListener = this.robot.listeners[0]
530 const result = testListener.matcher(testMessage)
531
532 expect(result).to.not.be.ok
533 })
534 })
535
536 describe('#respond', function () {
537 it('matches TextMessages addressed to the robot', function () {
538 const callback = sinon.spy()
539 const testMessage = new TextMessage(this.user, 'TestHubot message123')
540 const testRegex = /message123$/
541
542 this.robot.respond(testRegex, callback)
543 const testListener = this.robot.listeners[0]
544 const result = testListener.matcher(testMessage)
545
546 expect(result).to.be.ok
547 })
548
549 it('does not match EnterMessages', function () {
550 const callback = sinon.spy()
551 const testMessage = new EnterMessage(this.user)
552 const testRegex = /.*/
553
554 this.robot.respond(testRegex, callback)
555 const testListener = this.robot.listeners[0]
556 const result = testListener.matcher(testMessage)
557
558 expect(result).to.not.be.ok
559 })
560 })
561
562 describe('#enter', function () {
563 it('matches EnterMessages', function () {
564 const callback = sinon.spy()
565 const testMessage = new EnterMessage(this.user)
566
567 this.robot.enter(callback)
568 const testListener = this.robot.listeners[0]
569 const result = testListener.matcher(testMessage)
570
571 expect(result).to.be.ok
572 })
573
574 it('does not match TextMessages', function () {
575 const callback = sinon.spy()
576 const testMessage = new TextMessage(this.user, 'message123')
577
578 this.robot.enter(callback)
579 const testListener = this.robot.listeners[0]
580 const result = testListener.matcher(testMessage)
581
582 expect(result).to.not.be.ok
583 })
584 })
585
586 describe('#leave', function () {
587 it('matches LeaveMessages', function () {
588 const callback = sinon.spy()
589 const testMessage = new LeaveMessage(this.user)
590
591 this.robot.leave(callback)
592 const testListener = this.robot.listeners[0]
593 const result = testListener.matcher(testMessage)
594
595 expect(result).to.be.ok
596 })
597
598 it('does not match TextMessages', function () {
599 const callback = sinon.spy()
600 const testMessage = new TextMessage(this.user, 'message123')
601
602 this.robot.leave(callback)
603 const testListener = this.robot.listeners[0]
604 const result = testListener.matcher(testMessage)
605
606 expect(result).to.not.be.ok
607 })
608 })
609
610 describe('#topic', function () {
611 it('matches TopicMessages', function () {
612 const callback = sinon.spy()
613 const testMessage = new TopicMessage(this.user)
614
615 this.robot.topic(callback)
616 const testListener = this.robot.listeners[0]
617 const result = testListener.matcher(testMessage)
618
619 expect(result).to.be.ok
620 })
621
622 it('does not match TextMessages', function () {
623 const callback = sinon.spy()
624 const testMessage = new TextMessage(this.user, 'message123')
625
626 this.robot.topic(callback)
627 const testListener = this.robot.listeners[0]
628 const result = testListener.matcher(testMessage)
629
630 expect(result).to.not.be.ok
631 })
632 })
633
634 describe('#catchAll', function () {
635 it('matches CatchAllMessages', function () {
636 const callback = sinon.spy()
637 const testMessage = new CatchAllMessage(new TextMessage(this.user, 'message123'))
638
639 this.robot.catchAll(callback)
640 const testListener = this.robot.listeners[0]
641 const result = testListener.matcher(testMessage)
642
643 expect(result).to.be.ok
644 })
645
646 it('does not match TextMessages', function () {
647 const callback = sinon.spy()
648 const testMessage = new TextMessage(this.user, 'message123')
649
650 this.robot.catchAll(callback)
651 const testListener = this.robot.listeners[0]
652 const result = testListener.matcher(testMessage)
653
654 expect(result).to.not.be.ok
655 })
656 })
657 })
658
659 describe('Message Processing', function () {
660 it('calls a matching listener', function (done) {
661 const testMessage = new TextMessage(this.user, 'message123')
662 this.robot.hear(/^message123$/, function (response) {
663 expect(response.message).to.equal(testMessage)
664 done()
665 })
666 this.robot.receive(testMessage)
667 })
668
669 it('calls multiple matching listeners', function (done) {
670 const testMessage = new TextMessage(this.user, 'message123')
671
672 let listenersCalled = 0
673 const listenerCallback = function (response) {
674 expect(response.message).to.equal(testMessage)
675 listenersCalled++
676 }
677
678 this.robot.hear(/^message123$/, listenerCallback)
679 this.robot.hear(/^message123$/, listenerCallback)
680
681 this.robot.receive(testMessage, function () {
682 expect(listenersCalled).to.equal(2)
683 done()
684 })
685 })
686
687 it('calls the catch-all listener if no listeners match', function (done) {
688 const testMessage = new TextMessage(this.user, 'message123')
689
690 const listenerCallback = sinon.spy()
691 this.robot.hear(/^no-matches$/, listenerCallback)
692
693 this.robot.catchAll(function (response) {
694 expect(listenerCallback).to.not.have.been.called
695 expect(response.message).to.equal(testMessage)
696 done()
697 })
698
699 this.robot.receive(testMessage)
700 })
701
702 it('does not call the catch-all listener if any listener matched', function (done) {
703 const testMessage = new TextMessage(this.user, 'message123')
704
705 const listenerCallback = sinon.spy()
706 this.robot.hear(/^message123$/, listenerCallback)
707
708 const catchAllCallback = sinon.spy()
709 this.robot.catchAll(catchAllCallback)
710
711 this.robot.receive(testMessage, function () {
712 expect(listenerCallback).to.have.been.called.once
713 expect(catchAllCallback).to.not.have.been.called
714 done()
715 })
716 })
717
718 it('stops processing if message.finish() is called synchronously', function (done) {
719 const testMessage = new TextMessage(this.user, 'message123')
720
721 this.robot.hear(/^message123$/, response => response.message.finish())
722
723 const listenerCallback = sinon.spy()
724 this.robot.hear(/^message123$/, listenerCallback)
725
726 this.robot.receive(testMessage, function () {
727 expect(listenerCallback).to.not.have.been.called
728 done()
729 })
730 })
731
732 it('calls non-TextListener objects', function (done) {
733 const testMessage = new EnterMessage(this.user)
734
735 this.robot.enter(function (response) {
736 expect(response.message).to.equal(testMessage)
737 done()
738 })
739
740 this.robot.receive(testMessage)
741 })
742
743 it('gracefully handles listener uncaughtExceptions (move on to next listener)', function (done) {
744 const testMessage = new TextMessage(this.user, 'message123')
745 const theError = new Error()
746
747 this.robot.hear(/^message123$/, function () {
748 throw theError
749 })
750
751 let goodListenerCalled = false
752 this.robot.hear(/^message123$/, () => {
753 goodListenerCalled = true
754 })
755
756 this.robot.emit = function (name, err, response) {
757 expect(name).to.equal('error')
758 expect(err).to.equal(theError)
759 expect(response.message).to.equal(testMessage)
760 }
761 sinon.spy(this.robot, 'emit')
762
763 this.robot.receive(testMessage, () => {
764 expect(this.robot.emit).to.have.been.called
765 expect(goodListenerCalled).to.be.ok
766 done()
767 })
768 })
769
770 describe('Listener Middleware', function () {
771 it('allows listener callback execution', function (testDone) {
772 const listenerCallback = sinon.spy()
773 this.robot.hear(/^message123$/, listenerCallback)
774 this.robot.listenerMiddleware((context, next, done) =>
775 // Allow Listener callback execution
776 next(done)
777 )
778
779 const testMessage = new TextMessage(this.user, 'message123')
780 this.robot.receive(testMessage, function () {
781 expect(listenerCallback).to.have.been.called
782 testDone()
783 })
784 })
785
786 it('can block listener callback execution', function (testDone) {
787 const listenerCallback = sinon.spy()
788 this.robot.hear(/^message123$/, listenerCallback)
789 this.robot.listenerMiddleware((context, next, done) =>
790 // Block Listener callback execution
791 done()
792 )
793
794 const testMessage = new TextMessage(this.user, 'message123')
795 this.robot.receive(testMessage, function () {
796 expect(listenerCallback).to.not.have.been.called
797 testDone()
798 })
799 })
800
801 it('receives the correct arguments', function (testDone) {
802 this.robot.hear(/^message123$/, function () {})
803 const testListener = this.robot.listeners[0]
804 const testMessage = new TextMessage(this.user, 'message123')
805
806 this.robot.listenerMiddleware((context, next, done) => {
807 // Escape middleware error handling for clearer test failures
808 process.nextTick(() => {
809 expect(context.listener).to.equal(testListener)
810 expect(context.response.message).to.equal(testMessage)
811 expect(next).to.be.a('function')
812 expect(done).to.be.a('function')
813 testDone()
814 })
815 })
816
817 this.robot.receive(testMessage)
818 })
819
820 it('executes middleware in order of definition', function (testDone) {
821 const execution = []
822
823 const testMiddlewareA = function (context, next, done) {
824 execution.push('middlewareA')
825 next(function () {
826 execution.push('doneA')
827 done()
828 })
829 }
830
831 const testMiddlewareB = function (context, next, done) {
832 execution.push('middlewareB')
833 next(function () {
834 execution.push('doneB')
835 done()
836 })
837 }
838
839 this.robot.listenerMiddleware(testMiddlewareA)
840 this.robot.listenerMiddleware(testMiddlewareB)
841
842 this.robot.hear(/^message123$/, () => execution.push('listener'))
843
844 const testMessage = new TextMessage(this.user, 'message123')
845 this.robot.receive(testMessage, function () {
846 expect(execution).to.deep.equal([
847 'middlewareA',
848 'middlewareB',
849 'listener',
850 'doneB',
851 'doneA'
852 ])
853 testDone()
854 })
855 })
856 })
857
858 describe('Receive Middleware', function () {
859 it('fires for all messages, including non-matching ones', function (testDone) {
860 const middlewareSpy = sinon.spy()
861 const listenerCallback = sinon.spy()
862 this.robot.hear(/^message123$/, listenerCallback)
863 this.robot.receiveMiddleware(function (context, next, done) {
864 middlewareSpy()
865 next(done)
866 })
867
868 const testMessage = new TextMessage(this.user, 'not message 123')
869
870 this.robot.receive(testMessage, function () {
871 expect(listenerCallback).to.not.have.been.called
872 expect(middlewareSpy).to.have.been.called
873 testDone()
874 })
875 })
876
877 it('can block listener execution', function (testDone) {
878 const middlewareSpy = sinon.spy()
879 const listenerCallback = sinon.spy()
880 this.robot.hear(/^message123$/, listenerCallback)
881 this.robot.receiveMiddleware(function (context, next, done) {
882 // Block Listener callback execution
883 middlewareSpy()
884 done()
885 })
886
887 const testMessage = new TextMessage(this.user, 'message123')
888 this.robot.receive(testMessage, function () {
889 expect(listenerCallback).to.not.have.been.called
890 expect(middlewareSpy).to.have.been.called
891 testDone()
892 })
893 })
894
895 it('receives the correct arguments', function (testDone) {
896 this.robot.hear(/^message123$/, function () {})
897 const testMessage = new TextMessage(this.user, 'message123')
898
899 this.robot.receiveMiddleware(function (context, next, done) {
900 // Escape middleware error handling for clearer test failures
901 expect(context.response.message).to.equal(testMessage)
902 expect(next).to.be.a('function')
903 expect(done).to.be.a('function')
904 testDone()
905 next(done)
906 })
907
908 this.robot.receive(testMessage)
909 })
910
911 it('executes receive middleware in order of definition', function (testDone) {
912 const execution = []
913
914 const testMiddlewareA = function (context, next, done) {
915 execution.push('middlewareA')
916 next(function () {
917 execution.push('doneA')
918 done()
919 })
920 }
921
922 const testMiddlewareB = function (context, next, done) {
923 execution.push('middlewareB')
924 next(function () {
925 execution.push('doneB')
926 done()
927 })
928 }
929
930 this.robot.receiveMiddleware(testMiddlewareA)
931 this.robot.receiveMiddleware(testMiddlewareB)
932
933 this.robot.hear(/^message123$/, () => execution.push('listener'))
934
935 const testMessage = new TextMessage(this.user, 'message123')
936 this.robot.receive(testMessage, function () {
937 expect(execution).to.deep.equal([
938 'middlewareA',
939 'middlewareB',
940 'listener',
941 'doneB',
942 'doneA'
943 ])
944 testDone()
945 })
946 })
947
948 it('allows editing the message portion of the given response', function (testDone) {
949 const testMiddlewareA = function (context, next, done) {
950 context.response.message.text = 'foobar'
951 next()
952 }
953
954 const testMiddlewareB = function (context, next, done) {
955 // Subsequent middleware should see the modified message
956 expect(context.response.message.text).to.equal('foobar')
957 next()
958 }
959
960 this.robot.receiveMiddleware(testMiddlewareA)
961 this.robot.receiveMiddleware(testMiddlewareB)
962
963 const testCallback = sinon.spy()
964 // We'll never get to this if testMiddlewareA has not modified the message.
965 this.robot.hear(/^foobar$/, testCallback)
966
967 const testMessage = new TextMessage(this.user, 'message123')
968 this.robot.receive(testMessage, function () {
969 expect(testCallback).to.have.been.called
970 testDone()
971 })
972 })
973 })
974
975 describe('Response Middleware', function () {
976 it('executes response middleware in order', function (testDone) {
977 let sendSpy
978 this.robot.adapter.send = (sendSpy = sinon.spy())
979 this.robot.hear(/^message123$/, response => response.send('foobar, sir, foobar.'))
980
981 this.robot.responseMiddleware(function (context, next, done) {
982 context.strings[0] = context.strings[0].replace(/foobar/g, 'barfoo')
983 next()
984 })
985
986 this.robot.responseMiddleware(function (context, next, done) {
987 context.strings[0] = context.strings[0].replace(/barfoo/g, 'replaced bar-foo')
988 next()
989 })
990
991 const testMessage = new TextMessage(this.user, 'message123')
992 this.robot.receive(testMessage, function () {
993 expect(sendSpy.getCall(0).args[1]).to.equal('replaced bar-foo, sir, replaced bar-foo.')
994 testDone()
995 })
996 })
997
998 it('allows replacing outgoing strings', function (testDone) {
999 let sendSpy
1000 this.robot.adapter.send = (sendSpy = sinon.spy())
1001 this.robot.hear(/^message123$/, response => response.send('foobar, sir, foobar.'))
1002
1003 this.robot.responseMiddleware(function (context, next, done) {
1004 context.strings = ['whatever I want.']
1005 next()
1006 })
1007
1008 const testMessage = new TextMessage(this.user, 'message123')
1009 this.robot.receive(testMessage, function () {
1010 expect(sendSpy.getCall(0).args[1]).to.deep.equal('whatever I want.')
1011 testDone()
1012 })
1013 })
1014
1015 it('marks plaintext as plaintext', function (testDone) {
1016 let sendSpy = sinon.spy()
1017 this.robot.adapter.send = sendSpy
1018 this.robot.hear(/^message123$/, response => response.send('foobar, sir, foobar.'))
1019 this.robot.hear(/^message456$/, response => response.play('good luck with that'))
1020
1021 let method
1022 let plaintext
1023 this.robot.responseMiddleware(function (context, next, done) {
1024 method = context.method
1025 plaintext = context.plaintext
1026 next(done)
1027 })
1028
1029 const testMessage = new TextMessage(this.user, 'message123')
1030
1031 this.robot.receive(testMessage, () => {
1032 expect(plaintext).to.equal(true)
1033 expect(method).to.equal('send')
1034 const testMessage2 = new TextMessage(this.user, 'message456')
1035 this.robot.receive(testMessage2, function () {
1036 expect(plaintext).to.equal(undefined)
1037 expect(method).to.equal('play')
1038 testDone()
1039 })
1040 })
1041 })
1042
1043 it('does not send trailing functions to middleware', function (testDone) {
1044 let sendSpy
1045 this.robot.adapter.send = (sendSpy = sinon.spy())
1046 let asserted = false
1047 const postSendCallback = function () {}
1048 this.robot.hear(/^message123$/, response => response.send('foobar, sir, foobar.', postSendCallback))
1049
1050 this.robot.responseMiddleware(function (context, next, done) {
1051 // We don't send the callback function to middleware, so it's not here.
1052 expect(context.strings).to.deep.equal(['foobar, sir, foobar.'])
1053 expect(context.method).to.equal('send')
1054 asserted = true
1055 next()
1056 })
1057
1058 const testMessage = new TextMessage(this.user, 'message123')
1059 this.robot.receive(testMessage, function () {
1060 expect(asserted).to.equal(true)
1061 expect(sendSpy.getCall(0).args[1]).to.equal('foobar, sir, foobar.')
1062 expect(sendSpy.getCall(0).args[2]).to.equal(postSendCallback)
1063 testDone()
1064 })
1065 })
1066 })
1067 })
1068})