hubot 3.3.1 → 3.4.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/.env.example +2 -0
- package/.envrc +5 -0
- 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/README.md +0 -2
- package/bin/hubot.js +1 -5
- package/docs/deploying/windows.md +5 -5
- package/docs/patterns.md +14 -0
- package/es2015.js +1 -1
- package/package.json +7 -7
- package/src/adapters/campfire.js +18 -18
- package/src/adapters/shell.js +7 -5
- package/src/brain.js +8 -8
- package/src/datastore.js +3 -3
- package/src/httpclient.js +312 -0
- package/src/robot.js +18 -15
- 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 +16 -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 +26 -11
- package/test/user_test.js +2 -2
- package/ROADMAP.md +0 -37
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2014 rick
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
a copy of this software and associated documentation files (the
|
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
|
|
26
|
+
April 15, 2023
|
|
27
|
+
Reasoning:
|
|
28
|
+
ScopedHttpClient is no longer maintained.
|
|
29
|
+
|
|
30
|
+
Decision:
|
|
31
|
+
Implement a phased approach to deprecate `robot.http` all together in favor of `fetch`.
|
|
32
|
+
1. Convert ScopedHttpClient to Javascript and include the module in this repo
|
|
33
|
+
2. Add a deprecation warning to `robot.http`
|
|
34
|
+
3. Remove `robot.http` in a future release
|
|
35
|
+
*/
|
|
36
|
+
const path = require('path')
|
|
37
|
+
const http = require('http')
|
|
38
|
+
const https = require('https')
|
|
39
|
+
const qs = require('querystring')
|
|
40
|
+
|
|
41
|
+
const nonPassThroughOptions = [
|
|
42
|
+
'headers', 'hostname', 'encoding', 'auth', 'port',
|
|
43
|
+
'protocol', 'agent', 'httpAgent', 'httpsAgent', 'query', 'host', 'path',
|
|
44
|
+
'pathname', 'slashes', 'hash'
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
class ScopedClient {
|
|
48
|
+
constructor (url, options) {
|
|
49
|
+
this.options = this.buildOptions(url, options)
|
|
50
|
+
this.passthroughOptions = reduce(extend({}, this.options), nonPassThroughOptions)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
request (method, reqBody, callback) {
|
|
54
|
+
let req
|
|
55
|
+
if (typeof (reqBody) === 'function') {
|
|
56
|
+
callback = reqBody
|
|
57
|
+
reqBody = null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
let requestModule
|
|
62
|
+
const headers = extend({}, this.options.headers)
|
|
63
|
+
const sendingData = reqBody && (reqBody.length > 0)
|
|
64
|
+
headers.Host = this.options.hostname
|
|
65
|
+
if (this.options.port) { headers.Host += `:${this.options.port}` }
|
|
66
|
+
|
|
67
|
+
// If `callback` is `undefined` it means the caller isn't going to stream
|
|
68
|
+
// the body of the request using `callback` and we can set the
|
|
69
|
+
// content-length header ourselves.
|
|
70
|
+
//
|
|
71
|
+
// There is no way to conveniently assert in an else clause because the
|
|
72
|
+
// transfer encoding could be chunked or using a newer framing mechanism.
|
|
73
|
+
// And this is why we should'nt be creating a wrapper around http.
|
|
74
|
+
// Please just use `fetch`.
|
|
75
|
+
if (callback === undefined) {
|
|
76
|
+
headers['Content-Length'] = sendingData ? Buffer.byteLength(reqBody, this.options.encoding) : 0
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.options.auth) {
|
|
80
|
+
headers.Authorization = 'Basic ' + Buffer.from(this.options.auth, 'base64')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const port = this.options.port ||
|
|
84
|
+
ScopedClient.defaultPort[this.options.protocol] || 80
|
|
85
|
+
|
|
86
|
+
let {
|
|
87
|
+
agent
|
|
88
|
+
} = this.options
|
|
89
|
+
if (this.options.protocol === 'https:') {
|
|
90
|
+
requestModule = https
|
|
91
|
+
if (this.options.httpsAgent) { agent = this.options.httpsAgent }
|
|
92
|
+
} else {
|
|
93
|
+
requestModule = http
|
|
94
|
+
if (this.options.httpAgent) { agent = this.options.httpAgent }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const requestOptions = {
|
|
98
|
+
port,
|
|
99
|
+
host: this.options.hostname,
|
|
100
|
+
method,
|
|
101
|
+
path: this.fullPath(),
|
|
102
|
+
headers,
|
|
103
|
+
agent
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Extends the previous request options with all remaining options
|
|
107
|
+
extend(requestOptions, this.passthroughOptions)
|
|
108
|
+
|
|
109
|
+
req = requestModule.request(requestOptions)
|
|
110
|
+
|
|
111
|
+
if (this.options.timeout) {
|
|
112
|
+
req.setTimeout(this.options.timeout, () => req.abort())
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (callback) {
|
|
116
|
+
req.on('error', callback)
|
|
117
|
+
}
|
|
118
|
+
if (sendingData) { req.write(reqBody, this.options.encoding) }
|
|
119
|
+
if (callback) { callback(null, req) }
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (callback) { callback(err, req) }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return callback => {
|
|
125
|
+
if (callback) {
|
|
126
|
+
req.on('response', res => {
|
|
127
|
+
res.setEncoding(this.options.encoding)
|
|
128
|
+
let body = ''
|
|
129
|
+
res.on('data', chunk => {
|
|
130
|
+
body += chunk
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return res.on('end', () => callback(null, res, body))
|
|
134
|
+
})
|
|
135
|
+
req.on('error', error => callback(error, null, null))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
req.end()
|
|
139
|
+
return this
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Adds the query string to the path.
|
|
144
|
+
fullPath (p) {
|
|
145
|
+
const search = qs.stringify(this.options.query)
|
|
146
|
+
let full = this.join(p)
|
|
147
|
+
if (search.length > 0) { full += `?${search}` }
|
|
148
|
+
return full
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
scope (url, options, callback) {
|
|
152
|
+
const override = this.buildOptions(url, options)
|
|
153
|
+
const scoped = new ScopedClient(this.options)
|
|
154
|
+
.protocol(override.protocol)
|
|
155
|
+
.host(override.hostname)
|
|
156
|
+
.path(override.pathname)
|
|
157
|
+
|
|
158
|
+
if (typeof (url) === 'function') {
|
|
159
|
+
callback = url
|
|
160
|
+
} else if (typeof (options) === 'function') {
|
|
161
|
+
callback = options
|
|
162
|
+
}
|
|
163
|
+
if (callback) { callback(scoped) }
|
|
164
|
+
return scoped
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
join (suffix) {
|
|
168
|
+
const p = this.options.pathname || '/'
|
|
169
|
+
if (suffix && (suffix.length > 0)) {
|
|
170
|
+
if (suffix.match(/^\//)) {
|
|
171
|
+
return suffix
|
|
172
|
+
} else {
|
|
173
|
+
return path.join(p, suffix)
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
return p
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
path (p) {
|
|
181
|
+
this.options.pathname = this.join(p)
|
|
182
|
+
return this
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
query (key, value) {
|
|
186
|
+
if (!this.options.query) { this.options.query = {} }
|
|
187
|
+
if (typeof (key) === 'string') {
|
|
188
|
+
if (value) {
|
|
189
|
+
this.options.query[key] = value
|
|
190
|
+
} else {
|
|
191
|
+
delete this.options.query[key]
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
extend(this.options.query, key)
|
|
195
|
+
}
|
|
196
|
+
return this
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
host (h) {
|
|
200
|
+
if (h && (h.length > 0)) { this.options.hostname = h }
|
|
201
|
+
return this
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
port (p) {
|
|
205
|
+
if (p && ((typeof (p) === 'number') || (p.length > 0))) {
|
|
206
|
+
this.options.port = p
|
|
207
|
+
}
|
|
208
|
+
return this
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protocol (p) {
|
|
212
|
+
if (p && (p.length > 0)) { this.options.protocol = p }
|
|
213
|
+
return this
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
encoding (e) {
|
|
217
|
+
if (e == null) { e = 'utf-8' }
|
|
218
|
+
this.options.encoding = e
|
|
219
|
+
return this
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
timeout (time) {
|
|
223
|
+
this.options.timeout = time
|
|
224
|
+
return this
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
auth (user, pass) {
|
|
228
|
+
if (!user) {
|
|
229
|
+
this.options.auth = null
|
|
230
|
+
} else if (!pass && user.match(/:/)) {
|
|
231
|
+
this.options.auth = user
|
|
232
|
+
} else {
|
|
233
|
+
this.options.auth = `${user}:${pass}`
|
|
234
|
+
}
|
|
235
|
+
return this
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
header (name, value) {
|
|
239
|
+
this.options.headers[name] = value
|
|
240
|
+
return this
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
headers (h) {
|
|
244
|
+
extend(this.options.headers, h)
|
|
245
|
+
return this
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
buildOptions () {
|
|
249
|
+
const options = {}
|
|
250
|
+
let i = 0
|
|
251
|
+
while (arguments[i]) {
|
|
252
|
+
const ty = typeof arguments[i]
|
|
253
|
+
if (ty === 'string') {
|
|
254
|
+
const parsedUrl = new URL(arguments[i])
|
|
255
|
+
const query = {}
|
|
256
|
+
parsedUrl.searchParams.forEach((v, k) => {
|
|
257
|
+
query[k] = v
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
extend(options, {
|
|
261
|
+
href: parsedUrl.href,
|
|
262
|
+
origin: parsedUrl.origin,
|
|
263
|
+
protocol: parsedUrl.protocol,
|
|
264
|
+
username: parsedUrl.username,
|
|
265
|
+
password: parsedUrl.password,
|
|
266
|
+
host: parsedUrl.host,
|
|
267
|
+
hostname: parsedUrl.hostname,
|
|
268
|
+
port: parsedUrl.port,
|
|
269
|
+
pathname: parsedUrl.pathname,
|
|
270
|
+
search: parsedUrl.search,
|
|
271
|
+
searchParams: parsedUrl.searchParams,
|
|
272
|
+
query,
|
|
273
|
+
hash: parsedUrl.hash
|
|
274
|
+
})
|
|
275
|
+
delete options.url
|
|
276
|
+
delete options.href
|
|
277
|
+
delete options.search
|
|
278
|
+
} else if (ty !== 'function') {
|
|
279
|
+
extend(options, arguments[i])
|
|
280
|
+
}
|
|
281
|
+
i += 1
|
|
282
|
+
}
|
|
283
|
+
if (!options.headers) { options.headers = {} }
|
|
284
|
+
if (options.encoding == null) { options.encoding = 'utf-8' }
|
|
285
|
+
return options
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
ScopedClient.methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD']
|
|
290
|
+
ScopedClient.methods.forEach(method => {
|
|
291
|
+
ScopedClient.prototype[method.toLowerCase()] = function (body, callback) { return this.request(method, body, callback) }
|
|
292
|
+
})
|
|
293
|
+
ScopedClient.prototype.del = ScopedClient.prototype.delete
|
|
294
|
+
|
|
295
|
+
ScopedClient.defaultPort = { 'http:': 80, 'https:': 443, http: 80, https: 443 }
|
|
296
|
+
|
|
297
|
+
const extend = function (a, b) {
|
|
298
|
+
Object.keys(b).forEach(prop => {
|
|
299
|
+
a[prop] = b[prop]
|
|
300
|
+
})
|
|
301
|
+
return a
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Removes keys specified in second parameter from first parameter
|
|
305
|
+
const reduce = function (a, b) {
|
|
306
|
+
for (const propName of Array.from(b)) {
|
|
307
|
+
delete a[propName]
|
|
308
|
+
}
|
|
309
|
+
return a
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
exports.create = (url, options) => new ScopedClient(url, options)
|
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) {
|
|
@@ -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,22 +429,25 @@ 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())
|
|
447
|
-
app.use(express.urlencoded({ limit, parameterLimit: paramLimit }))
|
|
449
|
+
app.use(express.json({ limit }))
|
|
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
|
|
450
453
|
app.use(multipart({ maxFilesSize: 100 * 1024 * 1024 }))
|
|
@@ -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,8 @@ 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
31
|
})
|
|
32
32
|
|
|
33
33
|
describe('global scope', function () {
|
|
@@ -46,9 +46,9 @@ describe('Datastore', function () {
|
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
it('can store arbitrary JavaScript values', function () {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const object = {
|
|
50
|
+
name: 'test',
|
|
51
|
+
data: [1, 2, 3]
|
|
52
52
|
}
|
|
53
53
|
return this.robot.datastore.set('key', object).then(() => {
|
|
54
54
|
return this.robot.datastore.get('key').then((value) => {
|
|
@@ -59,9 +59,9 @@ describe('Datastore', function () {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
it('can dig inside objects for values', function () {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const object = {
|
|
63
|
+
a: 'one',
|
|
64
|
+
b: 'two'
|
|
65
65
|
}
|
|
66
66
|
return this.robot.datastore.set('key', object).then(() => {
|
|
67
67
|
return this.robot.datastore.getObject('key', 'a').then((value) => {
|
|
@@ -71,9 +71,9 @@ describe('Datastore', function () {
|
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
it('can set individual keys inside objects', function () {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
const object = {
|
|
75
|
+
a: 'one',
|
|
76
|
+
b: 'two'
|
|
77
77
|
}
|
|
78
78
|
return this.robot.datastore.set('object', object).then(() => {
|
|
79
79
|
return this.robot.datastore.setObject('object', 'c', 'three').then(() => {
|
|
@@ -89,7 +89,7 @@ describe('Datastore', function () {
|
|
|
89
89
|
it('creates an object from scratch when none exists', function () {
|
|
90
90
|
return this.robot.datastore.setObject('object', 'key', 'value').then(() => {
|
|
91
91
|
return this.robot.datastore.get('object').then((value) => {
|
|
92
|
-
|
|
92
|
+
const expected = { key: 'value' }
|
|
93
93
|
expect(value).to.deep.equal(expected)
|
|
94
94
|
})
|
|
95
95
|
})
|
|
@@ -116,12 +116,12 @@ describe('Datastore', function () {
|
|
|
116
116
|
|
|
117
117
|
describe('User scope', function () {
|
|
118
118
|
it('has access to the robot object', function () {
|
|
119
|
-
|
|
119
|
+
const user = this.robot.brain.userForId('1')
|
|
120
120
|
expect(user._getRobot()).to.equal(this.robot)
|
|
121
121
|
})
|
|
122
122
|
|
|
123
123
|
it('can store user data which is separate from global data', function () {
|
|
124
|
-
|
|
124
|
+
const user = this.robot.brain.userForId('1')
|
|
125
125
|
return user.set('blah', 'blah').then(() => {
|
|
126
126
|
return user.get('blah').then((userBlah) => {
|
|
127
127
|
return this.robot.datastore.get('blah').then((datastoreBlah) => {
|
|
@@ -134,8 +134,8 @@ describe('Datastore', function () {
|
|
|
134
134
|
})
|
|
135
135
|
|
|
136
136
|
it('stores user data separate per-user', function () {
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
const userOne = this.robot.brain.userForId('1')
|
|
138
|
+
const userTwo = this.robot.brain.userForId('2')
|
|
139
139
|
return userOne.set('blah', 'blah').then(() => {
|
|
140
140
|
return userOne.get('blah').then((valueOne) => {
|
|
141
141
|
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
|
|