hubot 3.3.2 → 3.5.0

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.
package/src/robot.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path')
6
6
 
7
7
  const async = require('async')
8
8
  const Log = require('log')
9
- const HttpClient = require('scoped-http-client')
9
+ const HttpClient = require('./httpclient')
10
10
 
11
11
  const Brain = require('./brain')
12
12
  const Response = require('./response')
@@ -132,7 +132,7 @@ class Robot {
132
132
  const name = this.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
133
133
 
134
134
  if (regexStartsWithAnchor) {
135
- this.logger.warning(`Anchors don’t work well with respond, perhaps you want to use 'hear'`)
135
+ this.logger.warning('Anchors don’t work well with respond, perhaps you want to use \'hear\'')
136
136
  this.logger.warning(`The regex in question was ${regex.toString()}`)
137
137
  }
138
138
 
@@ -204,7 +204,7 @@ class Robot {
204
204
  invokeErrorHandlers (error, res) {
205
205
  this.logger.error(error.stack)
206
206
 
207
- this.errorHandlers.map((errorHandler) => {
207
+ this.errorHandlers.forEach((errorHandler) => {
208
208
  try {
209
209
  errorHandler(error, res)
210
210
  } catch (errorHandlerError) {
@@ -317,13 +317,13 @@ class Robot {
317
317
  // stack doesn't get too big
318
318
  process.nextTick(() =>
319
319
  // Stop processing when message.done == true
320
- done(context.response.message.done)
320
+ done(null, context.response.message.done)
321
321
  )
322
322
  })
323
323
  } catch (err) {
324
324
  this.emit('error', err, new this.Response(this, context.response.message, []))
325
325
  // Continue to next listener when there is an error
326
- done(false)
326
+ done(null, false)
327
327
  }
328
328
  },
329
329
  // Ignore the result ( == the listener that set message.done = true)
@@ -352,7 +352,7 @@ class Robot {
352
352
  const full = path.join(filepath, path.basename(filename, ext))
353
353
 
354
354
  // see https://github.com/hubotio/hubot/issues/1355
355
- if (!require.extensions[ext]) { // eslint-disable-line
355
+ if (['.js', '.mjs', '.coffee'].indexOf(ext) == -1) { // eslint-disable-line
356
356
  return
357
357
  }
358
358
 
@@ -429,21 +429,24 @@ class Robot {
429
429
  const paramLimit = parseInt(process.env.EXPRESS_PARAMETER_LIMIT) || 1000
430
430
 
431
431
  const express = require('express')
432
+ const basicAuth = require('express-basic-auth')
432
433
  const multipart = require('connect-multiparty')
433
434
 
434
435
  const app = express()
435
436
 
436
437
  app.use((req, res, next) => {
437
- res.setHeader('X-Powered-By', `hubot/${this.name}`)
438
+ res.setHeader('X-Powered-By', `hubot/${encodeURI(this.name)}`)
438
439
  return next()
439
440
  })
440
441
 
441
442
  if (user && pass) {
442
- app.use(express.basicAuth(user, pass))
443
+ const authUser = {}
444
+ authUser[user] = pass
445
+ app.use(basicAuth({ users: authUser }))
443
446
  }
444
447
  app.use(express.query())
445
448
 
446
- app.use(express.json())
449
+ app.use(express.json({ limit }))
447
450
  app.use(express.urlencoded({ limit, parameterLimit: paramLimit, extended: true }))
448
451
  // replacement for deprecated express.multipart/connect.multipart
449
452
  // limit to 100mb, as per the old behavior
@@ -527,7 +530,7 @@ class Robot {
527
530
 
528
531
  const useStrictHeaderRegex = /^["']use strict['"];?\s+/
529
532
  const lines = body.replace(useStrictHeaderRegex, '').split(/(?:\n|\r\n|\r)/)
530
- .reduce(toHeaderCommentBlock, {lines: [], isHeader: true}).lines
533
+ .reduce(toHeaderCommentBlock, { lines: [], isHeader: true }).lines
531
534
  .filter(Boolean) // remove empty lines
532
535
  let currentSection = null
533
536
  let nextSection
@@ -577,11 +580,11 @@ class Robot {
577
580
  // envelope - A Object with message, room and user details.
578
581
  // strings - One or more Strings for each message to send.
579
582
  //
580
- // Returns nothing.
583
+ // Returns whatever the extending adapter returns.
581
584
  send (envelope/* , ...strings */) {
582
585
  const strings = [].slice.call(arguments, 1)
583
586
 
584
- this.adapter.send.apply(this.adapter, [envelope].concat(strings))
587
+ return this.adapter.send.apply(this.adapter, [envelope].concat(strings))
585
588
  }
586
589
 
587
590
  // Public: A helper reply function which delegates to the adapter's reply
@@ -590,11 +593,11 @@ class Robot {
590
593
  // envelope - A Object with message, room and user details.
591
594
  // strings - One or more Strings for each message to send.
592
595
  //
593
- // Returns nothing.
596
+ // Returns whatever the extending adapter returns.
594
597
  reply (envelope/* , ...strings */) {
595
598
  const strings = [].slice.call(arguments, 1)
596
599
 
597
- this.adapter.reply.apply(this.adapter, [envelope].concat(strings))
600
+ return this.adapter.reply.apply(this.adapter, [envelope].concat(strings))
598
601
  }
599
602
 
600
603
  // Public: A helper send function to message a room that the robot is in.
@@ -602,12 +605,12 @@ class Robot {
602
605
  // room - String designating the room to message.
603
606
  // strings - One or more Strings for each message to send.
604
607
  //
605
- // Returns nothing.
608
+ // Returns whatever the extending adapter returns.
606
609
  messageRoom (room/* , ...strings */) {
607
610
  const strings = [].slice.call(arguments, 1)
608
611
  const envelope = { room }
609
612
 
610
- this.adapter.send.apply(this.adapter, [envelope].concat(strings))
613
+ return this.adapter.send.apply(this.adapter, [envelope].concat(strings))
611
614
  }
612
615
 
613
616
  // Public: A wrapper around the EventEmitter API to make usage
package/src/user.js CHANGED
@@ -18,7 +18,7 @@ class User {
18
18
  // robot itself on the user object, preventing it from
19
19
  // being serialized into the brain.
20
20
  if (options.robot) {
21
- let robot = options.robot
21
+ const robot = options.robot
22
22
  delete options.robot
23
23
  this._getRobot = function () { return robot }
24
24
  } else {
@@ -55,7 +55,7 @@ class User {
55
55
  }
56
56
 
57
57
  _getDatastore () {
58
- let robot = this._getRobot()
58
+ const robot = this._getRobot()
59
59
  if (robot) {
60
60
  return robot.datastore
61
61
  }
@@ -12,7 +12,7 @@ const Adapter = require('../src/adapter')
12
12
 
13
13
  describe('Adapter', function () {
14
14
  beforeEach(function () {
15
- this.robot = {receive: sinon.spy()}
15
+ this.robot = { receive: sinon.spy() }
16
16
  })
17
17
 
18
18
  // this one is hard, as it requires files
@@ -30,9 +30,9 @@ describe('Brain', function () {
30
30
 
31
31
  this.brain = new Brain(this.mockRobot)
32
32
 
33
- this.user1 = this.brain.userForId('1', {name: 'Guy One'})
34
- this.user2 = this.brain.userForId('2', {name: 'Guy One Two'})
35
- this.user3 = this.brain.userForId('3', {name: 'Girl Three'})
33
+ this.user1 = this.brain.userForId('1', { name: 'Guy One' })
34
+ this.user2 = this.brain.userForId('2', { name: 'Guy One Two' })
35
+ this.user3 = this.brain.userForId('3', { name: 'Girl Three' })
36
36
  })
37
37
 
38
38
  afterEach(function () {
@@ -47,7 +47,7 @@ describe('Brain', function () {
47
47
  2: 'old'
48
48
  }
49
49
 
50
- this.brain.mergeData({2: 'new'})
50
+ this.brain.mergeData({ 2: 'new' })
51
51
 
52
52
  expect(this.brain.data).to.deep.equal({
53
53
  1: 'old',
@@ -62,8 +62,8 @@ describe('Brain', function () {
62
62
  })
63
63
 
64
64
  it('coerces loaded data into User objects', function () {
65
- this.brain.mergeData({users: {'4': {'name': 'new', 'id': '4'}}})
66
- let user = this.brain.userForId('4')
65
+ this.brain.mergeData({ users: { 4: { name: 'new', id: '4' } } })
66
+ const user = this.brain.userForId('4')
67
67
  expect(user.constructor.name).to.equal('User')
68
68
  expect(user.id).to.equal('4')
69
69
  expect(user.name).to.equal('new')
@@ -213,7 +213,7 @@ describe('Brain', function () {
213
213
  })
214
214
 
215
215
  it('passes the provided options to the new User', function () {
216
- const newUser = this.brain.userForId('all-new-user', {name: 'All New User', prop: 'mine'})
216
+ const newUser = this.brain.userForId('all-new-user', { name: 'All New User', prop: 'mine' })
217
217
  expect(newUser.name).to.equal('All New User')
218
218
  expect(newUser.prop).to.equal('mine')
219
219
  })
@@ -322,11 +322,11 @@ describe('Brain', function () {
322
322
 
323
323
  it('returns User objects, not POJOs', function () {
324
324
  expect(this.brain.userForId('1').constructor.name).to.equal('User')
325
- for (let user of this.brain.usersForFuzzyName('Guy')) {
325
+ for (const user of this.brain.usersForFuzzyName('Guy')) {
326
326
  expect(user.constructor.name).to.equal('User')
327
327
  }
328
328
 
329
- for (let user of this.brain.usersForRawFuzzyName('Guy One')) {
329
+ for (const user of this.brain.usersForRawFuzzyName('Guy One')) {
330
330
  expect(user.constructor.name).to.equal('User')
331
331
  }
332
332
 
@@ -26,8 +26,12 @@ describe('Datastore', function () {
26
26
 
27
27
  this.robot.brain = new Brain(this.robot)
28
28
  this.robot.datastore = new InMemoryDataStore(this.robot)
29
- this.robot.brain.userForId('1', {name: 'User One'})
30
- this.robot.brain.userForId('2', {name: 'User Two'})
29
+ this.robot.brain.userForId('1', { name: 'User One' })
30
+ this.robot.brain.userForId('2', { name: 'User Two' })
31
+ })
32
+
33
+ this.afterEach(function () {
34
+ this.clock.restore()
31
35
  })
32
36
 
33
37
  describe('global scope', function () {
@@ -46,9 +50,9 @@ describe('Datastore', function () {
46
50
  })
47
51
 
48
52
  it('can store arbitrary JavaScript values', function () {
49
- let object = {
50
- 'name': 'test',
51
- 'data': [1, 2, 3]
53
+ const object = {
54
+ name: 'test',
55
+ data: [1, 2, 3]
52
56
  }
53
57
  return this.robot.datastore.set('key', object).then(() => {
54
58
  return this.robot.datastore.get('key').then((value) => {
@@ -59,9 +63,9 @@ describe('Datastore', function () {
59
63
  })
60
64
 
61
65
  it('can dig inside objects for values', function () {
62
- let object = {
63
- 'a': 'one',
64
- 'b': 'two'
66
+ const object = {
67
+ a: 'one',
68
+ b: 'two'
65
69
  }
66
70
  return this.robot.datastore.set('key', object).then(() => {
67
71
  return this.robot.datastore.getObject('key', 'a').then((value) => {
@@ -71,9 +75,9 @@ describe('Datastore', function () {
71
75
  })
72
76
 
73
77
  it('can set individual keys inside objects', function () {
74
- let object = {
75
- 'a': 'one',
76
- 'b': 'two'
78
+ const object = {
79
+ a: 'one',
80
+ b: 'two'
77
81
  }
78
82
  return this.robot.datastore.set('object', object).then(() => {
79
83
  return this.robot.datastore.setObject('object', 'c', 'three').then(() => {
@@ -89,7 +93,7 @@ describe('Datastore', function () {
89
93
  it('creates an object from scratch when none exists', function () {
90
94
  return this.robot.datastore.setObject('object', 'key', 'value').then(() => {
91
95
  return this.robot.datastore.get('object').then((value) => {
92
- let expected = {'key': 'value'}
96
+ const expected = { key: 'value' }
93
97
  expect(value).to.deep.equal(expected)
94
98
  })
95
99
  })
@@ -116,12 +120,12 @@ describe('Datastore', function () {
116
120
 
117
121
  describe('User scope', function () {
118
122
  it('has access to the robot object', function () {
119
- let user = this.robot.brain.userForId('1')
123
+ const user = this.robot.brain.userForId('1')
120
124
  expect(user._getRobot()).to.equal(this.robot)
121
125
  })
122
126
 
123
127
  it('can store user data which is separate from global data', function () {
124
- let user = this.robot.brain.userForId('1')
128
+ const user = this.robot.brain.userForId('1')
125
129
  return user.set('blah', 'blah').then(() => {
126
130
  return user.get('blah').then((userBlah) => {
127
131
  return this.robot.datastore.get('blah').then((datastoreBlah) => {
@@ -134,8 +138,8 @@ describe('Datastore', function () {
134
138
  })
135
139
 
136
140
  it('stores user data separate per-user', function () {
137
- let userOne = this.robot.brain.userForId('1')
138
- let userTwo = this.robot.brain.userForId('2')
141
+ const userOne = this.robot.brain.userForId('1')
142
+ const userTwo = this.robot.brain.userForId('2')
139
143
  return userOne.set('blah', 'blah').then(() => {
140
144
  return userOne.get('blah').then((valueOne) => {
141
145
  return userTwo.get('blah').then((valueTwo) => {
@@ -31,7 +31,7 @@ const loadBot = Hubot.loadBot
31
31
  describe('hubot/es2015', function () {
32
32
  it('exports User class', function () {
33
33
  class MyUser extends User {}
34
- const user = new MyUser('id123', {foo: 'bar'})
34
+ const user = new MyUser('id123', { foo: 'bar' })
35
35
 
36
36
  expect(user).to.be.an.instanceof(User)
37
37
  expect(user.id).to.equal('id123')
@@ -7,21 +7,26 @@ class MockAdapter extends Adapter {
7
7
  const strings = [].slice.call(arguments, 1)
8
8
  this.emit('send', envelope, strings)
9
9
  }
10
+
10
11
  reply (envelope/* , ...strings */) {
11
12
  const strings = [].slice.call(arguments, 1)
12
13
  this.emit('reply', envelope, strings)
13
14
  }
15
+
14
16
  topic (envelope/* , ...strings */) {
15
17
  const strings = [].slice.call(arguments, 1)
16
18
  this.emit('topic', envelope, strings)
17
19
  }
20
+
18
21
  play (envelope/* , ...strings */) {
19
22
  const strings = [].slice.call(arguments, 1)
20
23
  this.emit('play', envelope, strings)
21
24
  }
25
+
22
26
  run () {
23
27
  this.emit('connected')
24
28
  }
29
+
25
30
  close () {
26
31
  this.emit('closed')
27
32
  }
@@ -303,7 +303,7 @@ describe('Listener', function () {
303
303
  const testMessage = {}
304
304
 
305
305
  const testListener = this.createListener(function () {})
306
- const testMiddleware = {execute: sinon.spy()}
306
+ const testMiddleware = { execute: sinon.spy() }
307
307
 
308
308
  testListener.call(testMessage, result => {
309
309
  expect(testMiddleware.execute).to.not.have.been.called
@@ -334,7 +334,7 @@ describe('Listener', function () {
334
334
  const listenerCallback = sinon.spy()
335
335
  const testListener = new Listener(this.robot, testMatcher, listenerCallback)
336
336
  // slightly brittle because we are testing for the default options Object
337
- expect(testListener.options).to.deep.equal({id: null})
337
+ expect(testListener.options).to.deep.equal({ id: null })
338
338
  expect(testListener.callback).to.be.equal(listenerCallback)
339
339
  })
340
340
 
@@ -23,7 +23,7 @@ describe('Middleware', function () {
23
23
  describe('Unit Tests', function () {
24
24
  beforeEach(function () {
25
25
  // Stub out event emitting
26
- this.robot = {emit: sinon.spy()}
26
+ this.robot = { emit: sinon.spy() }
27
27
 
28
28
  this.middleware = new Middleware(this.robot)
29
29
  })
@@ -284,7 +284,7 @@ describe('Middleware', function () {
284
284
  }
285
285
 
286
286
  this.middleware.execute(
287
- {response: testResponse},
287
+ { response: testResponse },
288
288
  middlewareFinished,
289
289
  middlewareFailed
290
290
  )
@@ -351,6 +351,7 @@ describe('Middleware', function () {
351
351
  warnOnUnregistered: false
352
352
  })
353
353
  mockery.registerMock('hubot-mock-adapter', require('./fixtures/mock-adapter'))
354
+ process.env.EXPRESS_PORT = 0
354
355
  this.robot = new Robot(null, 'mock-adapter', true, 'TestHubot')
355
356
  this.robot.run
356
357
 
@@ -394,8 +395,8 @@ describe('Middleware', function () {
394
395
  expect(this.middleware).to.have.been.calledWithMatch(
395
396
  sinon.match.has('listener',
396
397
  sinon.match.same(this.testListener)), // context
397
- sinon.match.any, // next
398
- sinon.match.any // done
398
+ sinon.match.any, // next
399
+ sinon.match.any // done
399
400
  )
400
401
  testDone()
401
402
  })
@@ -406,9 +407,9 @@ describe('Middleware', function () {
406
407
  expect(this.middleware).to.have.been.calledWithMatch(
407
408
  sinon.match.has('listener',
408
409
  sinon.match.has('options',
409
- sinon.match.has('id'))), // context
410
- sinon.match.any, // next
411
- sinon.match.any // done
410
+ sinon.match.has('id'))), // context
411
+ sinon.match.any, // next
412
+ sinon.match.any // done
412
413
  )
413
414
  testDone()
414
415
  })
@@ -423,8 +424,8 @@ describe('Middleware', function () {
423
424
  sinon.match.instanceOf(Response).and(
424
425
  sinon.match.has('message',
425
426
  sinon.match.same(this.testMessage)))), // context
426
- sinon.match.any, // next
427
- sinon.match.any // done
427
+ sinon.match.any, // next
428
+ sinon.match.any // done
428
429
  )
429
430
  testDone()
430
431
  })
@@ -447,8 +448,8 @@ describe('Middleware', function () {
447
448
  sinon.match.instanceOf(Response).and(
448
449
  sinon.match.has('message',
449
450
  sinon.match.same(this.testMessage)))), // context
450
- sinon.match.any, // next
451
- sinon.match.any // done
451
+ sinon.match.any, // next
452
+ sinon.match.any // done
452
453
  )
453
454
  testDone()
454
455
  })
@@ -466,11 +467,11 @@ describe('Middleware', function () {
466
467
  it('is a function with arity one', function (testDone) {
467
468
  this.robot.receive(this.testMessage, () => {
468
469
  expect(this.middleware).to.have.been.calledWithMatch(
469
- sinon.match.any, // context
470
+ sinon.match.any, // context
470
471
  sinon.match.func.and(
471
472
  sinon.match.has('length',
472
- sinon.match(1))), // next
473
- sinon.match.any // done
473
+ sinon.match(1))), // next
474
+ sinon.match.any // done
474
475
  )
475
476
  testDone()
476
477
  })
@@ -487,11 +488,11 @@ describe('Middleware', function () {
487
488
  it('is a function with arity zero', function (testDone) {
488
489
  this.robot.receive(this.testMessage, () => {
489
490
  expect(this.middleware).to.have.been.calledWithMatch(
490
- sinon.match.any, // context
491
- sinon.match.any, // next
491
+ sinon.match.any, // context
492
+ sinon.match.any, // next
492
493
  sinon.match.func.and(
493
494
  sinon.match.has('length',
494
- sinon.match(0))) // done
495
+ sinon.match(0))) // done
495
496
  )
496
497
  testDone()
497
498
  })
@@ -20,6 +20,7 @@ const TopicMessage = require('../src/message').TopicMessage
20
20
 
21
21
  // mock `hubot-mock-adapter` module from fixture
22
22
  const mockery = require('mockery')
23
+ const path = require('path')
23
24
 
24
25
  describe('Robot', function () {
25
26
  beforeEach(function () {
@@ -28,14 +29,14 @@ describe('Robot', function () {
28
29
  warnOnUnregistered: false
29
30
  })
30
31
  mockery.registerMock('hubot-mock-adapter', require('./fixtures/mock-adapter'))
32
+ process.env.EXPRESS_PORT = 0
31
33
  this.robot = new Robot(null, 'mock-adapter', true, 'TestHubot')
32
34
  this.robot.alias = 'Hubot'
33
35
  this.robot.run()
34
36
 
35
37
  // Re-throw AssertionErrors for clearer test failures
36
38
  this.robot.on('error', function (name, err, response) {
37
- if ((err != null ? err.constructor : undefined) == null) { }
38
- if (err.constructor.name === 'AssertionError') {
39
+ if (err?.constructor.name === 'AssertionError' || name instanceof chai.AssertionError) {
39
40
  process.nextTick(function () {
40
41
  throw err
41
42
  })
@@ -69,7 +70,7 @@ describe('Robot', function () {
69
70
 
70
71
  it('passes options through to the ScopedHttpClient', function () {
71
72
  const agent = {}
72
- const httpClient = this.robot.http('http://localhost', {agent})
73
+ const httpClient = this.robot.http('http://localhost', { agent })
73
74
  expect(httpClient.options.agent).to.equal(agent)
74
75
  })
75
76
 
@@ -79,7 +80,7 @@ describe('Robot', function () {
79
80
 
80
81
  it('merges in any global http options', function () {
81
82
  const agent = {}
82
- this.robot.globalHttpOptions = {agent}
83
+ this.robot.globalHttpOptions = { agent }
83
84
  const httpClient = this.robot.http('http://localhost')
84
85
  expect(httpClient.options.agent).to.equal(agent)
85
86
  })
@@ -87,10 +88,18 @@ describe('Robot', function () {
87
88
  it('local options override global http options', function () {
88
89
  const agentA = {}
89
90
  const agentB = {}
90
- this.robot.globalHttpOptions = {agent: agentA}
91
- const httpClient = this.robot.http('http://localhost', {agent: agentB})
91
+ this.robot.globalHttpOptions = { agent: agentA }
92
+ const httpClient = this.robot.http('http://localhost', { agent: agentB })
92
93
  expect(httpClient.options.agent).to.equal(agentB)
93
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
+ })
94
103
  })
95
104
 
96
105
  describe('#respondPattern', function () {
@@ -302,7 +311,7 @@ describe('Robot', function () {
302
311
  }
303
312
 
304
313
  const listenerSpy =
305
- {call: sinon.spy()}
314
+ { call: sinon.spy() }
306
315
 
307
316
  this.robot.listeners = [
308
317
  matchingListener,
@@ -365,7 +374,7 @@ describe('Robot', function () {
365
374
 
366
375
  describe('#loadFile', function () {
367
376
  beforeEach(function () {
368
- this.sandbox = sinon.sandbox.create()
377
+ this.sandbox = sinon.createSandbox()
369
378
  })
370
379
 
371
380
  afterEach(function () {
@@ -380,7 +389,7 @@ describe('Robot', function () {
380
389
  this.sandbox.stub(this.robot, 'parseHelp')
381
390
 
382
391
  this.robot.loadFile('./scripts', 'test-script.js')
383
- expect(module._load).to.have.been.calledWith('scripts/test-script')
392
+ expect(module._load).to.have.been.calledWith(path.join('scripts', 'test-script'))
384
393
  })
385
394
 
386
395
  describe('proper script', function () {
@@ -399,7 +408,7 @@ describe('Robot', function () {
399
408
 
400
409
  it('should parse the script documentation', function () {
401
410
  this.robot.loadFile('./scripts', 'test-script.js')
402
- expect(this.robot.parseHelp).to.have.been.calledWith('scripts/test-script.js')
411
+ expect(this.robot.parseHelp).to.have.been.calledWith(path.join('scripts', 'test-script.js'))
403
412
  })
404
413
  })
405
414
 
@@ -412,11 +421,17 @@ describe('Robot', function () {
412
421
  this.sandbox.stub(this.robot, 'parseHelp')
413
422
  })
414
423
 
415
- it('logs a warning', function () {
424
+ it('logs a warning for a .js file', function () {
416
425
  sinon.stub(this.robot.logger, 'warning')
417
426
  this.robot.loadFile('./scripts', 'test-script.js')
418
427
  expect(this.robot.logger.warning).to.have.been.called
419
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
+ })
420
435
  })
421
436
 
422
437
  describe('unsupported file extension', function () {
@@ -709,7 +724,7 @@ describe('Robot', function () {
709
724
  this.robot.catchAll(catchAllCallback)
710
725
 
711
726
  this.robot.receive(testMessage, function () {
712
- expect(listenerCallback).to.have.been.called.once
727
+ expect(listenerCallback).to.have.been.calledOnce
713
728
  expect(catchAllCallback).to.not.have.been.called
714
729
  done()
715
730
  })
@@ -1013,7 +1028,7 @@ describe('Robot', function () {
1013
1028
  })
1014
1029
 
1015
1030
  it('marks plaintext as plaintext', function (testDone) {
1016
- let sendSpy = sinon.spy()
1031
+ const sendSpy = sinon.spy()
1017
1032
  this.robot.adapter.send = sendSpy
1018
1033
  this.robot.hear(/^message123$/, response => response.send('foobar, sir, foobar.'))
1019
1034
  this.robot.hear(/^message456$/, response => response.play('good luck with that'))
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ /* global describe, beforeEach, it */
4
+
5
+ const chai = require('chai')
6
+ const sinon = require('sinon')
7
+ chai.use(require('sinon-chai'))
8
+
9
+ const expect = chai.expect
10
+
11
+ const Robot = require('../src/robot')
12
+
13
+ describe('Shell Adapter', function () {
14
+ beforeEach(function () {
15
+ this.robot = new Robot(null, 'shell', false, 'TestHubot')
16
+ this.robot.run()
17
+ })
18
+
19
+ this.afterEach(function () {
20
+ this.robot.shutdown()
21
+ })
22
+
23
+ describe('Public API', function () {
24
+ beforeEach(function () {
25
+ this.adapter = this.robot.adapter
26
+ })
27
+
28
+ it('assigns robot', function () {
29
+ expect(this.adapter.robot).to.equal(this.robot)
30
+ })
31
+
32
+ it('sends a message', function () {
33
+ this.adapter.send = sinon.spy()
34
+ this.adapter.send({ room: 'general' }, 'hello')
35
+
36
+ expect(this.adapter.send).to.have.been.calledWith({ room: 'general' }, 'hello')
37
+ })
38
+
39
+ it('emotes a message', function () {
40
+ this.adapter.send = sinon.spy()
41
+ this.adapter.emote({ room: 'general' }, 'hello')
42
+
43
+ expect(this.adapter.send).to.have.been.calledWith({ room: 'general' }, '* hello')
44
+ })
45
+
46
+ it('replies to a message', function () {
47
+ this.adapter.send = sinon.spy()
48
+ this.adapter.reply({ room: 'general', user: { name: 'mocha' } }, 'hello')
49
+
50
+ expect(this.adapter.send).to.have.been.calledWith({ room: 'general', user: { name: 'mocha' } }, 'mocha: hello')
51
+ })
52
+
53
+ it('runs the adapter and emits connected', function (done) {
54
+ const connected = () => {
55
+ this.adapter.off('connected', connected)
56
+ done()
57
+ }
58
+ this.adapter.on('connected', connected)
59
+ this.adapter.run()
60
+ })
61
+ })
62
+
63
+ it('dispatches received messages to the robot', function () {
64
+ this.robot.receive = sinon.spy()
65
+ this.adapter = this.robot.adapter
66
+ this.message = sinon.spy()
67
+
68
+ this.adapter.receive(this.message)
69
+
70
+ expect(this.robot.receive).to.have.been.calledWith(this.message)
71
+ })
72
+ })