UNPKG

12.7 kBJavaScriptView Raw
1'use strict'
2
3/* global describe, beforeEach, it */
4/* eslint-disable no-unused-expressions */
5
6// Assertions and Stubbing
7const chai = require('chai')
8const sinon = require('sinon')
9chai.use(require('sinon-chai'))
10
11const expect = chai.expect
12
13// Hubot classes
14const EnterMessage = require('../src/message').EnterMessage
15const TextMessage = require('../src/message').TextMessage
16const Listener = require('../src/listener').Listener
17const TextListener = require('../src/listener').TextListener
18const Response = require('../src/response')
19const User = require('../src/user')
20
21describe('Listener', function () {
22 beforeEach(function () {
23 // Dummy robot
24 this.robot = {
25 // Re-throw AssertionErrors for clearer test failures
26 emit (name, err, response) {
27 if (err.constructor.name === 'AssertionError') {
28 return process.nextTick(function () {
29 throw err
30 })
31 }
32 },
33 // Ignore log messages
34 logger: {
35 debug () {}
36 },
37 // Why is this part of the Robot object??
38 Response
39 }
40
41 // Test user
42 this.user = new User({
43 id: 1,
44 name: 'hubottester',
45 room: '#mocha'
46 })
47 })
48
49 describe('Unit Tests', function () {
50 describe('#call', function () {
51 it('calls the matcher', function (done) {
52 const callback = sinon.spy()
53 const testMatcher = sinon.spy()
54 const testMessage = {}
55
56 const testListener = new Listener(this.robot, testMatcher, callback)
57 testListener.call(testMessage, function (_) {
58 expect(testMatcher).to.have.been.calledWith(testMessage)
59 done()
60 })
61 })
62
63 it('passes the matcher result on to the listener callback', function (done) {
64 const matcherResult = {}
65 const testMatcher = sinon.stub().returns(matcherResult)
66 const testMessage = {}
67 const listenerCallback = response => expect(response.match).to.be.equal(matcherResult)
68
69 // sanity check; matcherResult must be truthy
70 expect(matcherResult).to.be.ok
71
72 const testListener = new Listener(this.robot, testMatcher, listenerCallback)
73 testListener.call(testMessage, function (result) {
74 // sanity check; message should have been processed
75 expect(testMatcher).to.have.been.called
76 expect(result).to.be.ok
77
78 done()
79 })
80 })
81
82 describe('if the matcher returns true', function () {
83 beforeEach(function () {
84 this.createListener = function (cb) {
85 return new Listener(this.robot, sinon.stub().returns(true), cb)
86 }
87 })
88
89 it('executes the listener callback', function (done) {
90 const listenerCallback = sinon.spy()
91 const testMessage = {}
92
93 const testListener = this.createListener(listenerCallback)
94 testListener.call(testMessage, function (_) {
95 expect(listenerCallback).to.have.been.called
96 done()
97 })
98 })
99
100 it('returns true', function () {
101 const testMessage = {}
102
103 const testListener = this.createListener(function () {})
104 const result = testListener.call(testMessage)
105 expect(result).to.be.ok
106 })
107
108 it('calls the provided callback with true', function (done) {
109 const testMessage = {}
110
111 const testListener = this.createListener(function () {})
112 testListener.call(testMessage, function (result) {
113 expect(result).to.be.ok
114 done()
115 })
116 })
117
118 it('calls the provided callback after the function returns', function (done) {
119 const testMessage = {}
120
121 const testListener = this.createListener(function () {})
122 let finished = false
123 testListener.call(testMessage, function (result) {
124 expect(finished).to.be.ok
125 done()
126 })
127 finished = true
128 })
129
130 it('handles uncaught errors from the listener callback', function (done) {
131 const testMessage = {}
132 const theError = new Error()
133
134 const listenerCallback = function (response) {
135 throw theError
136 }
137
138 this.robot.emit = function (name, err, response) {
139 expect(name).to.equal('error')
140 expect(err).to.equal(theError)
141 expect(response.message).to.equal(testMessage)
142 done()
143 }
144
145 const testListener = this.createListener(listenerCallback)
146 testListener.call(testMessage, sinon.spy())
147 })
148
149 it('calls the provided callback with true if there is an error thrown by the listener callback', function (done) {
150 const testMessage = {}
151 const theError = new Error()
152
153 const listenerCallback = function (response) {
154 throw theError
155 }
156
157 const testListener = this.createListener(listenerCallback)
158 testListener.call(testMessage, function (result) {
159 expect(result).to.be.ok
160 done()
161 })
162 })
163
164 it('calls the listener callback with a Response that wraps the Message', function (done) {
165 const testMessage = {}
166
167 const listenerCallback = function (response) {
168 expect(response.message).to.equal(testMessage)
169 done()
170 }
171
172 const testListener = this.createListener(listenerCallback)
173
174 testListener.call(testMessage, sinon.spy())
175 })
176
177 it('passes through the provided middleware stack', function (testDone) {
178 const testMessage = {}
179
180 const testListener = this.createListener(function () {})
181 const testMiddleware = {
182 execute (context, next, done) {
183 expect(context.listener).to.be.equal(testListener)
184 expect(context.response).to.be.instanceof(Response)
185 expect(context.response.message).to.be.equal(testMessage)
186 expect(next).to.be.a('function')
187 expect(done).to.be.a('function')
188 testDone()
189 }
190 }
191
192 testListener.call(testMessage, testMiddleware, sinon.spy())
193 })
194
195 it('executes the listener callback if middleware succeeds', function (testDone) {
196 const listenerCallback = sinon.spy()
197 const testMessage = {}
198
199 const testListener = this.createListener(listenerCallback)
200
201 testListener.call(testMessage, function (result) {
202 expect(listenerCallback).to.have.been.called
203 // Matcher matched, so we true
204 expect(result).to.be.ok
205 testDone()
206 })
207 })
208
209 it('does not execute the listener callback if middleware fails', function (testDone) {
210 const listenerCallback = sinon.spy()
211 const testMessage = {}
212
213 const testListener = this.createListener(listenerCallback)
214 const testMiddleware = {
215 execute (context, next, done) {
216 // Middleware fails
217 done()
218 }
219 }
220
221 testListener.call(testMessage, testMiddleware, function (result) {
222 expect(listenerCallback).to.not.have.been.called
223 // Matcher still matched, so we true
224 expect(result).to.be.ok
225 testDone()
226 })
227 })
228
229 it('unwinds the middleware stack if there is an error in the listener callback', function (testDone) {
230 const listenerCallback = sinon.stub().throws(new Error())
231 const testMessage = {}
232 let extraDoneFunc = null
233
234 const testListener = this.createListener(listenerCallback)
235 const testMiddleware = {
236 execute (context, next, done) {
237 extraDoneFunc = sinon.spy(done)
238 next(context, extraDoneFunc)
239 }
240 }
241
242 testListener.call(testMessage, testMiddleware, function (result) {
243 // Listener callback was called (and failed)
244 expect(listenerCallback).to.have.been.called
245 // Middleware stack was unwound correctly
246 expect(extraDoneFunc).to.have.been.called
247 // Matcher still matched, so we true
248 expect(result).to.be.ok
249 testDone()
250 })
251 })
252 })
253
254 describe('if the matcher returns false', function () {
255 beforeEach(function () {
256 this.createListener = function (cb) {
257 return new Listener(this.robot, sinon.stub().returns(false), cb)
258 }
259 })
260
261 it('does not execute the listener callback', function (done) {
262 const listenerCallback = sinon.spy()
263 const testMessage = {}
264
265 const testListener = this.createListener(listenerCallback)
266 testListener.call(testMessage, function (_) {
267 expect(listenerCallback).to.not.have.been.called
268 done()
269 })
270 })
271
272 it('returns false', function () {
273 const testMessage = {}
274
275 const testListener = this.createListener(function () {})
276 const result = testListener.call(testMessage)
277 expect(result).to.not.be.ok
278 })
279
280 it('calls the provided callback with false', function (done) {
281 const testMessage = {}
282
283 const testListener = this.createListener(function () {})
284 testListener.call(testMessage, function (result) {
285 expect(result).to.not.be.ok
286 done()
287 })
288 })
289
290 it('calls the provided callback after the function returns', function (done) {
291 const testMessage = {}
292
293 const testListener = this.createListener(function () {})
294 let finished = false
295 testListener.call(testMessage, function (result) {
296 expect(finished).to.be.ok
297 done()
298 })
299 finished = true
300 })
301
302 it('does not execute any middleware', function (done) {
303 const testMessage = {}
304
305 const testListener = this.createListener(function () {})
306 const testMiddleware = {execute: sinon.spy()}
307
308 testListener.call(testMessage, result => {
309 expect(testMiddleware.execute).to.not.have.been.called
310 done()
311 })
312 })
313 })
314 })
315
316 describe('#constructor', function () {
317 it('requires a matcher', () => expect(function () {
318 return new Listener(this.robot, undefined, {}, sinon.spy())
319 }).to.throw(Error))
320
321 it('requires a callback', function () {
322 // No options
323 expect(function () {
324 return new Listener(this.robot, sinon.spy())
325 }).to.throw(Error)
326 // With options
327 expect(function () {
328 return new Listener(this.robot, sinon.spy(), {})
329 }).to.throw(Error)
330 })
331
332 it('gracefully handles missing options', function () {
333 const testMatcher = sinon.spy()
334 const listenerCallback = sinon.spy()
335 const testListener = new Listener(this.robot, testMatcher, listenerCallback)
336 // slightly brittle because we are testing for the default options Object
337 expect(testListener.options).to.deep.equal({id: null})
338 expect(testListener.callback).to.be.equal(listenerCallback)
339 })
340
341 it('gracefully handles a missing ID (set to null)', function () {
342 const testMatcher = sinon.spy()
343 const listenerCallback = sinon.spy()
344 const testListener = new Listener(this.robot, testMatcher, {}, listenerCallback)
345 expect(testListener.options.id).to.be.null
346 })
347 })
348
349 describe('TextListener', () =>
350 describe('#matcher', function () {
351 it('matches TextMessages', function () {
352 const callback = sinon.spy()
353 const testMessage = new TextMessage(this.user, 'test')
354 testMessage.match = sinon.stub().returns(true)
355 const testRegex = /test/
356
357 const testListener = new TextListener(this.robot, testRegex, callback)
358 const result = testListener.matcher(testMessage)
359
360 expect(result).to.be.ok
361 expect(testMessage.match).to.have.been.calledWith(testRegex)
362 })
363
364 it('does not match EnterMessages', function () {
365 const callback = sinon.spy()
366 const testMessage = new EnterMessage(this.user)
367 testMessage.match = sinon.stub().returns(true)
368 const testRegex = /test/
369
370 const testListener = new TextListener(this.robot, testRegex, callback)
371 const result = testListener.matcher(testMessage)
372
373 expect(result).to.not.be.ok
374 expect(testMessage.match).to.not.have.been.called
375 })
376 })
377 )
378 })
379})