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 Robot = require('../src/robot')
|
15 | const TextMessage = require('../src/message').TextMessage
|
16 | const Response = require('../src/response')
|
17 | const Middleware = require('../src/middleware')
|
18 |
|
19 |
|
20 | const mockery = require('mockery')
|
21 |
|
22 | describe('Middleware', function () {
|
23 | describe('Unit Tests', function () {
|
24 | beforeEach(function () {
|
25 |
|
26 | this.robot = { emit: sinon.spy() }
|
27 |
|
28 | this.middleware = new Middleware(this.robot)
|
29 | })
|
30 |
|
31 | describe('#execute', function () {
|
32 | it('executes synchronous middleware', function (testDone) {
|
33 | const testMiddleware = sinon.spy((context, next, done) => {
|
34 | next(done)
|
35 | })
|
36 |
|
37 | this.middleware.register(testMiddleware)
|
38 |
|
39 | const middlewareFinished = function () {
|
40 | expect(testMiddleware).to.have.been.called
|
41 | testDone()
|
42 | }
|
43 |
|
44 | this.middleware.execute(
|
45 | {},
|
46 | (_, done) => done(),
|
47 | middlewareFinished
|
48 | )
|
49 | })
|
50 |
|
51 | it('executes asynchronous middleware', function (testDone) {
|
52 | const testMiddleware = sinon.spy((context, next, done) =>
|
53 |
|
54 | process.nextTick(() => next(done))
|
55 | )
|
56 |
|
57 | this.middleware.register(testMiddleware)
|
58 |
|
59 | const middlewareFinished = function (context, done) {
|
60 | expect(testMiddleware).to.have.been.called
|
61 | testDone()
|
62 | }
|
63 |
|
64 | this.middleware.execute(
|
65 | {},
|
66 | (_, done) => done(),
|
67 | middlewareFinished
|
68 | )
|
69 | })
|
70 |
|
71 | it('passes the correct arguments to each middleware', function (testDone) {
|
72 | const testContext = {}
|
73 | const testMiddleware = (context, next, done) =>
|
74 |
|
75 |
|
76 | process.nextTick(function () {
|
77 |
|
78 | expect(context).to.equal(testContext)
|
79 | next(done)
|
80 | })
|
81 |
|
82 | this.middleware.register(testMiddleware)
|
83 |
|
84 | this.middleware.execute(
|
85 | testContext,
|
86 | (_, done) => done(),
|
87 | () => testDone())
|
88 | })
|
89 |
|
90 | it('executes all registered middleware in definition order', function (testDone) {
|
91 | const middlewareExecution = []
|
92 |
|
93 | const testMiddlewareA = (context, next, done) => {
|
94 | middlewareExecution.push('A')
|
95 | next(done)
|
96 | }
|
97 |
|
98 | const testMiddlewareB = function (context, next, done) {
|
99 | middlewareExecution.push('B')
|
100 | next(done)
|
101 | }
|
102 |
|
103 | this.middleware.register(testMiddlewareA)
|
104 | this.middleware.register(testMiddlewareB)
|
105 |
|
106 | const middlewareFinished = function () {
|
107 | expect(middlewareExecution).to.deep.equal(['A', 'B'])
|
108 | testDone()
|
109 | }
|
110 |
|
111 | this.middleware.execute(
|
112 | {},
|
113 | (_, done) => done(),
|
114 | middlewareFinished
|
115 | )
|
116 | })
|
117 |
|
118 | it('executes the next callback after the function returns when there is no middleware', function (testDone) {
|
119 | let finished = false
|
120 | this.middleware.execute(
|
121 | {},
|
122 | function () {
|
123 | expect(finished).to.be.ok
|
124 | testDone()
|
125 | },
|
126 | function () {}
|
127 | )
|
128 | finished = true
|
129 | })
|
130 |
|
131 | it('always executes middleware after the function returns', function (testDone) {
|
132 | let finished = false
|
133 |
|
134 | this.middleware.register(function (context, next, done) {
|
135 | expect(finished).to.be.ok
|
136 | testDone()
|
137 | })
|
138 |
|
139 | this.middleware.execute({}, function () {}, function () {})
|
140 | finished = true
|
141 | })
|
142 |
|
143 | it('creates a default "done" function', function (testDone) {
|
144 | let finished = false
|
145 |
|
146 | this.middleware.register(function (context, next, done) {
|
147 | expect(finished).to.be.ok
|
148 | testDone()
|
149 | })
|
150 |
|
151 |
|
152 | this.middleware.execute({}, function () {})
|
153 | finished = true
|
154 | })
|
155 |
|
156 | it('does the right thing with done callbacks', function (testDone) {
|
157 |
|
158 |
|
159 | const execution = []
|
160 |
|
161 | const testMiddlewareA = function (context, next, done) {
|
162 | execution.push('middlewareA')
|
163 | next(function () {
|
164 | execution.push('doneA')
|
165 | done()
|
166 | })
|
167 | }
|
168 |
|
169 | const testMiddlewareB = function (context, next, done) {
|
170 | execution.push('middlewareB')
|
171 | next(function () {
|
172 | execution.push('doneB')
|
173 | done()
|
174 | })
|
175 | }
|
176 |
|
177 | this.middleware.register(testMiddlewareA)
|
178 | this.middleware.register(testMiddlewareB)
|
179 |
|
180 | const allDone = function () {
|
181 | expect(execution).to.deep.equal(['middlewareA', 'middlewareB', 'doneB', 'doneA'])
|
182 | testDone()
|
183 | }
|
184 |
|
185 | this.middleware.execute(
|
186 | {},
|
187 |
|
188 | (_, done) => done(),
|
189 | allDone
|
190 | )
|
191 | })
|
192 |
|
193 | it('defaults to the latest done callback if none is provided', function (testDone) {
|
194 |
|
195 |
|
196 | const execution = []
|
197 |
|
198 | const testMiddlewareA = function (context, next, done) {
|
199 | execution.push('middlewareA')
|
200 | next(function () {
|
201 | execution.push('doneA')
|
202 | done()
|
203 | })
|
204 | }
|
205 |
|
206 | const testMiddlewareB = function (context, next, done) {
|
207 | execution.push('middlewareB')
|
208 | next()
|
209 | }
|
210 |
|
211 | this.middleware.register(testMiddlewareA)
|
212 | this.middleware.register(testMiddlewareB)
|
213 |
|
214 | const allDone = function () {
|
215 | expect(execution).to.deep.equal(['middlewareA', 'middlewareB', 'doneA'])
|
216 | testDone()
|
217 | }
|
218 |
|
219 | this.middleware.execute(
|
220 | {},
|
221 |
|
222 | (_, done) => done(),
|
223 | allDone
|
224 | )
|
225 | })
|
226 |
|
227 | describe('error handling', function () {
|
228 | it('does not execute subsequent middleware after the error is thrown', function (testDone) {
|
229 | const middlewareExecution = []
|
230 |
|
231 | const testMiddlewareA = function (context, next, done) {
|
232 | middlewareExecution.push('A')
|
233 | next(done)
|
234 | }
|
235 |
|
236 | const testMiddlewareB = function (context, next, done) {
|
237 | middlewareExecution.push('B')
|
238 | throw new Error()
|
239 | }
|
240 |
|
241 | const testMiddlewareC = function (context, next, done) {
|
242 | middlewareExecution.push('C')
|
243 | next(done)
|
244 | }
|
245 |
|
246 | this.middleware.register(testMiddlewareA)
|
247 | this.middleware.register(testMiddlewareB)
|
248 | this.middleware.register(testMiddlewareC)
|
249 |
|
250 | const middlewareFinished = sinon.spy()
|
251 | const middlewareFailed = () => {
|
252 | expect(middlewareFinished).to.not.have.been.called
|
253 | expect(middlewareExecution).to.deep.equal(['A', 'B'])
|
254 | testDone()
|
255 | }
|
256 |
|
257 | this.middleware.execute(
|
258 | {},
|
259 | middlewareFinished,
|
260 | middlewareFailed
|
261 | )
|
262 | })
|
263 |
|
264 | it('emits an error event', function (testDone) {
|
265 | const testResponse = {}
|
266 | const theError = new Error()
|
267 |
|
268 | const testMiddleware = function (context, next, done) {
|
269 | throw theError
|
270 | }
|
271 |
|
272 | this.middleware.register(testMiddleware)
|
273 |
|
274 | this.robot.emit = sinon.spy(function (name, err, response) {
|
275 | expect(name).to.equal('error')
|
276 | expect(err).to.equal(theError)
|
277 | expect(response).to.equal(testResponse)
|
278 | })
|
279 |
|
280 | const middlewareFinished = sinon.spy()
|
281 | const middlewareFailed = () => {
|
282 | expect(this.robot.emit).to.have.been.called
|
283 | testDone()
|
284 | }
|
285 |
|
286 | this.middleware.execute(
|
287 | { response: testResponse },
|
288 | middlewareFinished,
|
289 | middlewareFailed
|
290 | )
|
291 | })
|
292 |
|
293 | it('unwinds the middleware stack (calling all done functions)', function (testDone) {
|
294 | let extraDoneFunc = null
|
295 |
|
296 | const testMiddlewareA = function (context, next, done) {
|
297 |
|
298 | extraDoneFunc = sinon.spy(done)
|
299 | next(extraDoneFunc)
|
300 | }
|
301 |
|
302 | const testMiddlewareB = function (context, next, done) {
|
303 | throw new Error()
|
304 | }
|
305 |
|
306 | this.middleware.register(testMiddlewareA)
|
307 | this.middleware.register(testMiddlewareB)
|
308 |
|
309 | const middlewareFinished = sinon.spy()
|
310 | const middlewareFailed = function () {
|
311 |
|
312 | expect(middlewareFinished).to.not.have.been.called
|
313 |
|
314 | expect(extraDoneFunc).to.have.been.called
|
315 | testDone()
|
316 | }
|
317 |
|
318 | this.middleware.execute(
|
319 | {},
|
320 | middlewareFinished,
|
321 | middlewareFailed
|
322 | )
|
323 | })
|
324 | })
|
325 | })
|
326 |
|
327 | describe('#register', function () {
|
328 | it('adds to the list of middleware', function () {
|
329 | const testMiddleware = function (context, next, done) {}
|
330 |
|
331 | this.middleware.register(testMiddleware)
|
332 |
|
333 | expect(this.middleware.stack).to.include(testMiddleware)
|
334 | })
|
335 |
|
336 | it('validates the arity of middleware', function () {
|
337 | const testMiddleware = function (context, next, done, extra) {}
|
338 |
|
339 | expect(() => this.middleware.register(testMiddleware)).to.throw(/Incorrect number of arguments/)
|
340 | })
|
341 | })
|
342 | })
|
343 |
|
344 |
|
345 |
|
346 |
|
347 | describe('Public Middleware APIs', function () {
|
348 | beforeEach(function () {
|
349 | mockery.enable({
|
350 | warnOnReplace: false,
|
351 | warnOnUnregistered: false
|
352 | })
|
353 | mockery.registerMock('hubot-mock-adapter', require('./fixtures/mock-adapter'))
|
354 | process.env.EXPRESS_PORT = 0
|
355 | this.robot = new Robot(null, 'mock-adapter', true, 'TestHubot')
|
356 | this.robot.run
|
357 |
|
358 |
|
359 | this.robot.on('error', function (name, err, response) {
|
360 | if (__guard__(err != null ? err.constructor : undefined, x => x.name) === 'AssertionError') {
|
361 | process.nextTick(function () {
|
362 | throw err
|
363 | })
|
364 | }
|
365 | })
|
366 |
|
367 | this.user = this.robot.brain.userForId('1', {
|
368 | name: 'hubottester',
|
369 | room: '#mocha'
|
370 | })
|
371 |
|
372 |
|
373 | this.middleware = sinon.spy((context, next, done) => next(done))
|
374 |
|
375 | this.testMessage = new TextMessage(this.user, 'message123')
|
376 | this.robot.hear(/^message123$/, function (response) {})
|
377 | this.testListener = this.robot.listeners[0]
|
378 | })
|
379 |
|
380 | afterEach(function () {
|
381 | mockery.disable()
|
382 | this.robot.shutdown()
|
383 | })
|
384 |
|
385 | describe('listener middleware context', function () {
|
386 | beforeEach(function () {
|
387 | this.robot.listenerMiddleware((context, next, done) => {
|
388 | this.middleware(context, next, done)
|
389 | })
|
390 | })
|
391 |
|
392 | describe('listener', function () {
|
393 | it('is the listener object that matched', function (testDone) {
|
394 | this.robot.receive(this.testMessage, () => {
|
395 | expect(this.middleware).to.have.been.calledWithMatch(
|
396 | sinon.match.has('listener',
|
397 | sinon.match.same(this.testListener)),
|
398 | sinon.match.any,
|
399 | sinon.match.any
|
400 | )
|
401 | testDone()
|
402 | })
|
403 | })
|
404 |
|
405 | it('has options.id (metadata)', function (testDone) {
|
406 | this.robot.receive(this.testMessage, () => {
|
407 | expect(this.middleware).to.have.been.calledWithMatch(
|
408 | sinon.match.has('listener',
|
409 | sinon.match.has('options',
|
410 | sinon.match.has('id'))),
|
411 | sinon.match.any,
|
412 | sinon.match.any
|
413 | )
|
414 | testDone()
|
415 | })
|
416 | })
|
417 | })
|
418 |
|
419 | describe('response', () =>
|
420 | it('is a Response that wraps the message', function (testDone) {
|
421 | this.robot.receive(this.testMessage, () => {
|
422 | expect(this.middleware).to.have.been.calledWithMatch(
|
423 | sinon.match.has('response',
|
424 | sinon.match.instanceOf(Response).and(
|
425 | sinon.match.has('message',
|
426 | sinon.match.same(this.testMessage)))),
|
427 | sinon.match.any,
|
428 | sinon.match.any
|
429 | )
|
430 | testDone()
|
431 | })
|
432 | })
|
433 | )
|
434 | })
|
435 |
|
436 | describe('receive middleware context', function () {
|
437 | beforeEach(function () {
|
438 | this.robot.receiveMiddleware((context, next, done) => {
|
439 | this.middleware(context, next, done)
|
440 | })
|
441 | })
|
442 |
|
443 | describe('response', () =>
|
444 | it('is a match-less Response object', function (testDone) {
|
445 | this.robot.receive(this.testMessage, () => {
|
446 | expect(this.middleware).to.have.been.calledWithMatch(
|
447 | sinon.match.has('response',
|
448 | sinon.match.instanceOf(Response).and(
|
449 | sinon.match.has('message',
|
450 | sinon.match.same(this.testMessage)))),
|
451 | sinon.match.any,
|
452 | sinon.match.any
|
453 | )
|
454 | testDone()
|
455 | })
|
456 | })
|
457 | )
|
458 | })
|
459 |
|
460 | describe('next', function () {
|
461 | beforeEach(function () {
|
462 | this.robot.listenerMiddleware((context, next, done) => {
|
463 | this.middleware(context, next, done)
|
464 | })
|
465 | })
|
466 |
|
467 | it('is a function with arity one', function (testDone) {
|
468 | this.robot.receive(this.testMessage, () => {
|
469 | expect(this.middleware).to.have.been.calledWithMatch(
|
470 | sinon.match.any,
|
471 | sinon.match.func.and(
|
472 | sinon.match.has('length',
|
473 | sinon.match(1))),
|
474 | sinon.match.any
|
475 | )
|
476 | testDone()
|
477 | })
|
478 | })
|
479 | })
|
480 |
|
481 | describe('done', function () {
|
482 | beforeEach(function () {
|
483 | this.robot.listenerMiddleware((context, next, done) => {
|
484 | this.middleware(context, next, done)
|
485 | })
|
486 | })
|
487 |
|
488 | it('is a function with arity zero', function (testDone) {
|
489 | this.robot.receive(this.testMessage, () => {
|
490 | expect(this.middleware).to.have.been.calledWithMatch(
|
491 | sinon.match.any,
|
492 | sinon.match.any,
|
493 | sinon.match.func.and(
|
494 | sinon.match.has('length',
|
495 | sinon.match(0)))
|
496 | )
|
497 | testDone()
|
498 | })
|
499 | })
|
500 | })
|
501 | })
|
502 | })
|
503 |
|
504 | function __guard__ (value, transform) {
|
505 | (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined
|
506 | }
|