1 | should = require 'should'
|
2 | _ = require 'lodash'
|
3 | logger = require 'torch'
|
4 | {focus} = require 'qi'
|
5 | {join} = require 'path'
|
6 |
|
7 | bus = require '../lib/bus'
|
8 | core = require '../lib/core'
|
9 |
|
10 | mockRetriever = require './helpers/mockRetriever'
|
11 |
|
12 | describe 'core.request', ->
|
13 | beforeEach (done) ->
|
14 | core.reset =>
|
15 |
|
16 | core.init {timeout: 20}, mockRetriever()
|
17 | @moduleName = 'server'
|
18 | @serviceName = 'start'
|
19 | @channel = "#{@moduleName}.#{@serviceName}"
|
20 | @data =
|
21 | x: 2
|
22 | y: 'hello'
|
23 |
|
24 | done()
|
25 |
|
26 | it 'should receive exactly one valid response', (done) ->
|
27 | core.respond @channel, (message, done) ->
|
28 | done null, message
|
29 |
|
30 | core.request @channel, @data, (err, message) =>
|
31 | should.not.exist err
|
32 |
|
33 | should.exist message
|
34 | message.should.eql @data
|
35 |
|
36 | done()
|
37 |
|
38 | it 'should pass an error to the callback', (done) ->
|
39 | testError = new Error 'testError'
|
40 | core.respond @channel, (args, fin) ->
|
41 | fin testError
|
42 |
|
43 | core.request @channel, @data, (err, message) =>
|
44 | should.exist err
|
45 | should.exist message
|
46 | message.should.eql {}
|
47 |
|
48 | err.should.eql testError
|
49 |
|
50 | done()
|
51 |
|
52 | it 'should invoke the success callback exactly once, then discard all', (done) ->
|
53 | core.respond @channel, (message, finished) =>
|
54 | finished()
|
55 |
|
56 | test = (err, data) ->
|
57 | bus.publish
|
58 | channel: @channel
|
59 | topic: replyTo.topic.err
|
60 | data: new Error 'should not see'
|
61 | done()
|
62 |
|
63 | replyTo = core.request @channel, @data, test
|
64 |
|
65 | it 'should return a timeout error when it times out', (done) ->
|
66 | {RequestTimeoutError} = require '../lib/errorTypes'
|
67 | core.respond @channel, ->
|
68 | core.request @channel, @data, (err, result) =>
|
69 | should.exist err
|
70 | err.message.should.eql "Request timed out after 20ms on channel: '#{@channel}'"
|
71 | err.should.be.instanceOf RequestTimeoutError
|
72 | should.exist result
|
73 | result.should.eql {}
|
74 | done()
|
75 |
|
76 | it 'should return immediately if there are no listeners', (done) ->
|
77 | core.request @channel, @data, (err, result) =>
|
78 | should.exist err
|
79 | expectedMsg = "No responders for request: '#{@channel}'"
|
80 | err.message.should.eql expectedMsg
|
81 |
|
82 | should.exist result
|
83 | result.should.eql {}
|
84 | done()
|
85 |
|
86 | it 'should return an "ambiguous" error when there are multiple responders', (done) ->
|
87 | core.respond @channel, ->
|
88 | core.respond @channel, ->
|
89 | core.request @channel, @data, (err, result) =>
|
90 | should.exist err
|
91 | err.message.should.eql "Ambiguous: 2 responders for request: '#{@channel}'"
|
92 | should.exist result
|
93 | result.should.eql {}
|
94 | done()
|
95 |
|
96 |
|
97 | describe 'core.response', ->
|
98 | beforeEach (done) ->
|
99 | core.reset =>
|
100 | core.init {timeout: 20}, mockRetriever()
|
101 | @channel = 'testChannel'
|
102 | @data =
|
103 | x: 2
|
104 | y: 'hello'
|
105 | done()
|
106 |
|
107 | it 'should send exactly one valid response', (done) ->
|
108 | echo = (message, next) -> next null, message
|
109 | core.respond @channel, echo
|
110 |
|
111 | bus.subscribe
|
112 | channel: @channel
|
113 | topic: "success.123"
|
114 | callback: (message) =>
|
115 | should.exist message
|
116 | message.should.eql @data
|
117 | done()
|
118 |
|
119 | bus.publish
|
120 | channel: @channel
|
121 | topic: "request.123"
|
122 | data: @data
|
123 | replyTo:
|
124 | channel: @channel
|
125 | topic:
|
126 | success: "success.123"
|
127 |
|
128 |
|
129 | describe 'core.delegate', ->
|
130 | beforeEach (done) ->
|
131 | core.reset =>
|
132 | core.init {timeout: 20}, mockRetriever()
|
133 | done()
|
134 |
|
135 | it 'should should return if there are no responders', (done) ->
|
136 | channel = 'testChannel'
|
137 |
|
138 | core.delegate channel, {}, (err, results) ->
|
139 | should.not.exist err
|
140 | done()
|
141 |
|
142 | it 'responders on another channel should not interfere', (done) ->
|
143 | channel = 'testChannel'
|
144 |
|
145 | core.respond 'fooChannel', (message, next) ->
|
146 | next null, {helloFrom: 'fooChannel'}
|
147 |
|
148 | core.delegate channel, {}, (err, results) ->
|
149 | should.not.exist err
|
150 | done()
|
151 |
|
152 | it 'should should receive multiple responses', (done) ->
|
153 | channel = 'testChannel'
|
154 |
|
155 | sub = core.respond channel, (message, next) ->
|
156 | next null, {helloFrom: 'responderA'}
|
157 |
|
158 | res1 = sub.responderId
|
159 |
|
160 | sub = core.respond channel, (message, next) ->
|
161 | next null, {helloFrom: 'responderB'}
|
162 |
|
163 | res2 = sub.responderId
|
164 |
|
165 | core.delegate channel, {}, (err, results) ->
|
166 | should.not.exist err
|
167 | should.exist results
|
168 |
|
169 | results[res1].should.eql { helloFrom: 'responderA' }
|
170 | results[res2].should.eql { helloFrom: 'responderB' }
|
171 | done()
|
172 |
|
173 | it 'should return an err when a responder returns one', (done) ->
|
174 | channel = 'testChannel'
|
175 |
|
176 | testResponse = {message: 'this works'}
|
177 | core.respond channel, (message, next) ->
|
178 | next null, testResponse
|
179 |
|
180 | testError = new Error 'Expect this error'
|
181 | core.respond channel, (message, next) ->
|
182 | next testError, {}
|
183 |
|
184 | core.delegate channel, {}, (err, results) ->
|
185 | should.exist err
|
186 | expectedMsg = "Received errors from channel '#{channel}':\nError: #{testError.message}"
|
187 | err.message.should.startWith expectedMsg
|
188 |
|
189 | should.exist err.errors, 'expected errors'
|
190 | subErrors = _.values err.errors
|
191 | should.exist subErrors, 'expected subErrors'
|
192 | [subErr] = subErrors
|
193 | should.exist subErr, 'expected subErr'
|
194 | subErr.should.eql testError
|
195 |
|
196 | should.exist results, 'expected results'
|
197 | values = _.values results
|
198 | should.exist values, 'expected values'
|
199 | [result] = values
|
200 | should.exist result, 'expected result'
|
201 | result.should.eql testResponse
|
202 |
|
203 | done()
|
204 |
|
205 | it 'should return a timeout err when an implied request times out', (done) ->
|
206 | channel = 'testChannel'
|
207 |
|
208 | wontTimeOutMsg = {message: "I won't time out"}
|
209 | sub = core.respond channel, (message, next) ->
|
210 | next null, wontTimeOutMsg
|
211 |
|
212 | successId = sub.responderId
|
213 |
|
214 |
|
215 | sub = core.respond channel, (message, next) ->
|
216 |
|
217 |
|
218 | errId = sub.responderId
|
219 |
|
220 | core.delegate channel, {}, (err, results) ->
|
221 | should.exist err
|
222 |
|
223 | expectedMsg = "Received errors from channel '#{channel}':\nAxiomError/RequestTimeoutError: Responder with id '#{errId}' timed out after 20ms on channel: '#{channel}'"
|
224 | err.message.should.startWith expectedMsg
|
225 |
|
226 | should.exist err.errors
|
227 |
|
228 | subErr = err.errors[errId]
|
229 | should.exist subErr, 'expected subErr'
|
230 |
|
231 | errMsg = "Responder with id '#{errId}' timed out after 20ms on channel: '#{channel}'"
|
232 | subErr.message.should.eql errMsg
|
233 |
|
234 | should.exist results
|
235 | result = results[successId]
|
236 | should.exist result
|
237 | result.should.eql wontTimeOutMsg
|
238 |
|
239 | done()
|
240 |
|
241 | it 'should collect args', (done) ->
|
242 |
|
243 |
|
244 | pitch = (args, fin) ->
|
245 | fin null, {foo: 1}
|
246 | pitch.extension = 'sandbox'
|
247 |
|
248 |
|
249 | responderId = core.respond 'pitch', pitch
|
250 |
|
251 |
|
252 | core.delegate 'pitch', {input: 2}, (err, result) ->
|
253 | should.not.exist err
|
254 | should.exist result?.sandbox, 'expected responder data'
|
255 | should.exist result.__delegation_result, 'expected __delegation_result'
|
256 | result.__delegation_result.should.eql true
|
257 | should.exist result.__input, 'expected __input'
|
258 | result.__input.should.eql {input: 2}
|
259 | result.sandbox.should.eql {input: 2, foo: 1}
|
260 | done()
|
261 |
|
262 | it 'should collect errors', (done) ->
|
263 |
|
264 |
|
265 | error = (args, fin) ->
|
266 | fin(new Error 'oops')
|
267 | error.extension = 'sandbox'
|
268 |
|
269 |
|
270 | responderId = core.respond 'error', error
|
271 |
|
272 |
|
273 | core.delegate 'error', {something: 1}, (err, result) ->
|
274 | should.exist err?.errors?.sandbox, 'expected err for responder'
|
275 | should.exist result, 'expected result'
|
276 | result.should.eql
|
277 | __delegation_result: true
|
278 | __input: {something: 1}
|
279 | done()
|
280 |
|
281 | it 'should distribute args', (done) ->
|
282 |
|
283 |
|
284 | responderId = core.load 'sandbox',
|
285 | services:
|
286 | pitch: (args, fin) ->
|
287 | args.should.eql {bar: 2, foo: 1}
|
288 | fin null, args
|
289 |
|
290 | args =
|
291 | __delegation_result: true
|
292 | __input: {bar: 2}
|
293 | sandbox: {foo: 1}
|
294 | other: {value: "shouldn't see"}
|
295 |
|
296 |
|
297 | core.delegate 'sandbox.pitch', args, (err, result) ->
|
298 | should.not.exist err
|
299 | should.exist result?.sandbox, 'expected responder data'
|
300 | result.sandbox.should.eql {bar: 2, foo: 1}
|
301 | done()
|
302 |
|
303 | it 'should use input args', (done) ->
|
304 |
|
305 |
|
306 | responderId = core.load 'sandbox',
|
307 | services:
|
308 | pitch: (args, fin) ->
|
309 | args.should.eql {bar: 2}
|
310 | fin null, args
|
311 |
|
312 | args =
|
313 | __delegation_result: true
|
314 | __input: {bar: 2}
|
315 | other: {value: "shouldn't see"}
|
316 |
|
317 |
|
318 | core.delegate 'sandbox.pitch', args, (err, result) ->
|
319 | should.not.exist err
|
320 | should.exist result?.sandbox, 'expected responder data'
|
321 | result.sandbox.should.eql {bar: 2}
|
322 | done()
|
323 |
|
324 | describe 'core.listen', ->
|
325 | beforeEach (done) ->
|
326 | core.reset =>
|
327 | core.init {timeout: 20}, mockRetriever()
|
328 | @channelA = 'testChannelA'
|
329 | @channelB = 'testChannelB'
|
330 | @dataA =
|
331 | x: 2
|
332 | y: 'hello'
|
333 | @dataB =
|
334 | x: 111
|
335 | y: 'goodbye'
|
336 | @topicA = 'info.A'
|
337 | @topicB = 'info.B'
|
338 |
|
339 | done()
|
340 |
|
341 | it 'should listen with a standard-signature callback', (done) ->
|
342 | core.listen @channelA, @topicA, (err, result) =>
|
343 | should.not.exist err
|
344 | should.exist result
|
345 | {data} = result
|
346 | should.exist data
|
347 | data.should.eql @dataA
|
348 | done()
|
349 |
|
350 | bus.publish
|
351 | channel: @channelA
|
352 | data: @dataA
|
353 | topic: @topicA
|
354 |
|
355 | it 'should listen to two topics on one channel', (done) ->
|
356 | cb = focus (err, results) =>
|
357 | should.not.exist err
|
358 |
|
359 | should.exist results
|
360 |
|
361 | [resultA, resultB] = results
|
362 | should.exist resultA
|
363 | should.exist resultB
|
364 |
|
365 | dataA = resultA.data
|
366 | should.exist dataA
|
367 | dataA.should.eql @dataA
|
368 |
|
369 | dataB = resultB.data
|
370 | should.exist dataB
|
371 | dataB.should.eql @dataB
|
372 |
|
373 | done()
|
374 |
|
375 | core.listen @channelA, @topicA, cb()
|
376 |
|
377 | core.listen @channelA, @topicB, cb()
|
378 |
|
379 | bus.publish
|
380 | channel: @channelA
|
381 | data: @dataA
|
382 | topic: @topicA
|
383 |
|
384 | bus.publish
|
385 | channel: @channelA
|
386 | data: @dataB
|
387 | topic: @topicB
|
388 |
|
389 | it 'should listen to two topics on two channels', (done) ->
|
390 | cb = focus (err, results) =>
|
391 | should.not.exist err
|
392 |
|
393 | should.exist results
|
394 |
|
395 | [resultA, resultB] = results
|
396 | should.exist resultA
|
397 | should.exist resultB
|
398 |
|
399 | dataA = resultA.data
|
400 | should.exist dataA
|
401 | dataA.should.eql @dataA
|
402 |
|
403 | dataB = resultB.data
|
404 | should.exist dataB
|
405 | dataB.should.eql @dataB
|
406 |
|
407 | done()
|
408 |
|
409 | core.listen @channelA, @topicA, cb()
|
410 |
|
411 | core.listen @channelB, @topicB, cb()
|
412 |
|
413 | bus.publish
|
414 | channel: @channelA
|
415 | data: @dataA
|
416 | topic: @topicA
|
417 |
|
418 | bus.publish
|
419 | channel: @channelB
|
420 | data: @dataB
|
421 | topic: @topicB
|
422 |
|
423 | it 'should listen to any topic on one channel', (done) ->
|
424 | core.listen @channelA, '#', (err, result) =>
|
425 | should.not.exist err
|
426 | should.exist result
|
427 |
|
428 | {data} = result
|
429 | should.exist data
|
430 | data.should.eql @dataA
|
431 |
|
432 | done()
|
433 |
|
434 | bus.publish
|
435 | channel: @channelA
|
436 | data: @dataA
|
437 | topic: @topicA
|