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/.github/workflows/nodejs-macos.yml +26 -0
- package/.github/workflows/nodejs-ubuntu.yml +28 -0
- package/.github/workflows/nodejs-windows.yml +26 -0
- package/.github/workflows/release.yml +36 -0
- package/.node-version +1 -0
- package/README.md +4 -3
- package/bin/hubot +1 -1
- package/bin/hubot.js +1 -5
- package/docs/adapters/development.md +35 -36
- package/docs/deploying/windows.md +5 -5
- package/docs/implementation.md +1 -3
- package/docs/index.md +5 -10
- package/docs/patterns.md +146 -101
- package/docs/scripting.md +493 -411
- package/es2015.js +1 -1
- package/package.json +22 -21
- package/src/adapters/campfire.js +18 -18
- package/src/adapters/shell.js +11 -14
- package/src/brain.js +8 -8
- package/src/datastore.js +3 -3
- package/src/httpclient.js +312 -0
- package/src/robot.js +19 -16
- package/src/user.js +2 -2
- package/test/adapter_test.js +1 -1
- package/test/brain_test.js +9 -9
- package/test/datastore_test.js +20 -16
- package/test/es2015_test.js +1 -1
- package/test/fixtures/mock-adapter.js +5 -0
- package/test/listener_test.js +2 -2
- package/test/middleware_test.js +18 -17
- package/test/robot_test.js +28 -13
- package/test/shell_test.js +72 -0
- package/test/user_test.js +2 -2
- package/.travis.yml +0 -27
- package/ROADMAP.md +0 -37
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('
|
|
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(
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
const robot = this._getRobot()
|
|
59
59
|
if (robot) {
|
|
60
60
|
return robot.datastore
|
|
61
61
|
}
|
package/test/adapter_test.js
CHANGED
package/test/brain_test.js
CHANGED
|
@@ -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: {
|
|
66
|
-
|
|
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 (
|
|
325
|
+
for (const user of this.brain.usersForFuzzyName('Guy')) {
|
|
326
326
|
expect(user.constructor.name).to.equal('User')
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
for (
|
|
329
|
+
for (const user of this.brain.usersForRawFuzzyName('Guy One')) {
|
|
330
330
|
expect(user.constructor.name).to.equal('User')
|
|
331
331
|
}
|
|
332
332
|
|
package/test/datastore_test.js
CHANGED
|
@@ -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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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) => {
|
package/test/es2015_test.js
CHANGED
|
@@ -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
|
}
|
package/test/listener_test.js
CHANGED
|
@@ -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
|
|
package/test/middleware_test.js
CHANGED
|
@@ -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,
|
|
398
|
-
sinon.match.any
|
|
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'))),
|
|
410
|
-
sinon.match.any,
|
|
411
|
-
sinon.match.any
|
|
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,
|
|
427
|
-
sinon.match.any
|
|
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,
|
|
451
|
-
sinon.match.any
|
|
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,
|
|
470
|
+
sinon.match.any, // context
|
|
470
471
|
sinon.match.func.and(
|
|
471
472
|
sinon.match.has('length',
|
|
472
|
-
sinon.match(1))),
|
|
473
|
-
sinon.match.any
|
|
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,
|
|
491
|
-
sinon.match.any,
|
|
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)))
|
|
495
|
+
sinon.match(0))) // done
|
|
495
496
|
)
|
|
496
497
|
testDone()
|
|
497
498
|
})
|
package/test/robot_test.js
CHANGED
|
@@ -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 (
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
+
})
|