UNPKG

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