hubot 5.0.4 → 5.0.6

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