1 | 'use strict'
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const chai = require('chai')
|
8 | const sinon = require('sinon')
|
9 | chai.use(require('sinon-chai'))
|
10 |
|
11 | const expect = chai.expect
|
12 |
|
13 |
|
14 | const EnterMessage = require('../src/message').EnterMessage
|
15 | const TextMessage = require('../src/message').TextMessage
|
16 | const Listener = require('../src/listener').Listener
|
17 | const TextListener = require('../src/listener').TextListener
|
18 | const Response = require('../src/response')
|
19 | const User = require('../src/user')
|
20 |
|
21 | describe('Listener', function () {
|
22 | beforeEach(function () {
|
23 |
|
24 | this.robot = {
|
25 |
|
26 | emit (name, err, response) {
|
27 | if (err.constructor.name === 'AssertionError') {
|
28 | return process.nextTick(function () {
|
29 | throw err
|
30 | })
|
31 | }
|
32 | },
|
33 |
|
34 | logger: {
|
35 | debug () {}
|
36 | },
|
37 |
|
38 | Response
|
39 | }
|
40 |
|
41 |
|
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 |
|
70 | expect(matcherResult).to.be.ok
|
71 |
|
72 | const testListener = new Listener(this.robot, testMatcher, listenerCallback)
|
73 | testListener.call(testMessage, function (result) {
|
74 |
|
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 |
|
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 |
|
217 | done()
|
218 | }
|
219 | }
|
220 |
|
221 | testListener.call(testMessage, testMiddleware, function (result) {
|
222 | expect(listenerCallback).to.not.have.been.called
|
223 |
|
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 |
|
244 | expect(listenerCallback).to.have.been.called
|
245 |
|
246 | expect(extraDoneFunc).to.have.been.called
|
247 |
|
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 |
|
323 | expect(function () {
|
324 | return new Listener(this.robot, sinon.spy())
|
325 | }).to.throw(Error)
|
326 |
|
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 |
|
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 | })
|