1 | describe('Copacetic', () => {
|
2 | const expect = require('chai').expect
|
3 | const sinon = require('sinon')
|
4 | const nock = require('nock')
|
5 | const noop = require('node-noop').noop
|
6 |
|
7 | let Copacetic
|
8 | let dependencyLevel
|
9 |
|
10 | before(() => {
|
11 | const Dependency = require('../lib/dependency')
|
12 | const Injector = require('../lib/util/injector')
|
13 | const CodependencyMock = require('./mocks/codependency')
|
14 |
|
15 | dependencyLevel = require('../lib/dependency-level')
|
16 | Copacetic = require('../lib/copacetic')(
|
17 | Dependency(Injector(CodependencyMock({
|
18 | 'node-fetch': require('node-fetch')
|
19 | })))
|
20 | )
|
21 | })
|
22 |
|
23 | it('should export a function', () => {
|
24 | expect(Copacetic).to.be.a('function')
|
25 | })
|
26 |
|
27 | it('should return an instance of Copacetic', () => {
|
28 | expect(Copacetic()).to.be.a('object')
|
29 | })
|
30 |
|
31 | it('should be in event emitter mode by default', () => {
|
32 | expect(Copacetic().eventEmitterMode).to.equal(true)
|
33 | })
|
34 |
|
35 | describe('isHealthy', () => {
|
36 | it('should return false if a hard dependency is unhealthy', (done) => {
|
37 | const copacetic = Copacetic()
|
38 | copacetic.registerDependency({
|
39 | name: 'My-Dependency',
|
40 | url: 'http://example.com',
|
41 | level: dependencyLevel.HARD
|
42 | })
|
43 |
|
44 | nock('http://example.com')
|
45 | .get('/')
|
46 | .reply(400)
|
47 |
|
48 | return copacetic
|
49 | .check({ name: 'My-Dependency' })
|
50 | .on('unhealthy', (healthInfo) => {
|
51 | expect(copacetic.isHealthy).to.equal(false)
|
52 | done()
|
53 | })
|
54 | })
|
55 |
|
56 | it('should return true if a soft dependency is unhealthy', (done) => {
|
57 | const copacetic = Copacetic()
|
58 | copacetic.registerDependency({
|
59 | name: 'My-Dependency',
|
60 | url: 'http://example.com',
|
61 | level: dependencyLevel.SOFT
|
62 | })
|
63 |
|
64 | nock('http://example.com')
|
65 | .get('/')
|
66 | .reply(400)
|
67 |
|
68 | return copacetic
|
69 | .check({ name: 'My-Dependency' })
|
70 | .on('unhealthy', () => {
|
71 | expect(copacetic.isHealthy).to.equal(true)
|
72 | done()
|
73 | })
|
74 | })
|
75 |
|
76 | it('should return true if all dependencies are healthy', (done) => {
|
77 | const copacetic = Copacetic()
|
78 | copacetic.registerDependency({
|
79 | name: 'My-Dependency',
|
80 | url: 'http://example.com',
|
81 | level: dependencyLevel.SOFT
|
82 | })
|
83 |
|
84 | nock('http://example.com')
|
85 | .get('/')
|
86 | .reply(200)
|
87 |
|
88 | return copacetic
|
89 | .check({ name: 'My-Dependency' })
|
90 | .on('healthy', () => {
|
91 | expect(copacetic.isHealthy).to.equal(true)
|
92 | done()
|
93 | })
|
94 | })
|
95 | })
|
96 |
|
97 | describe('getCopaceticInfo', () => {
|
98 | it('should return the health info for all registered dependencies', () => {
|
99 | const copacetic = Copacetic()
|
100 | copacetic.registerDependency({
|
101 | name: 'My-Dependency',
|
102 | url: 'http://example.com'
|
103 | })
|
104 |
|
105 | expect(copacetic.healthInfo).to.deep.equal(
|
106 | [
|
107 | {
|
108 | name: 'My-Dependency',
|
109 | healthy: true,
|
110 | level: 'SOFT',
|
111 | lastChecked: undefined
|
112 | }
|
113 | ]
|
114 | )
|
115 | })
|
116 | })
|
117 |
|
118 | describe('healthReport', () => {
|
119 | it('should return the full health report', () => {
|
120 | const copacetic = Copacetic('my-service')
|
121 | copacetic.registerDependency({
|
122 | name: 'My-Dependency',
|
123 | url: 'http://example.com'
|
124 | })
|
125 |
|
126 | expect(copacetic.healthReport).to.deep.equal(
|
127 | {
|
128 | name: 'my-service',
|
129 | isHealthy: true,
|
130 | dependencies: [
|
131 | {
|
132 | name: 'My-Dependency',
|
133 | healthy: true,
|
134 | level: 'SOFT',
|
135 | lastChecked: undefined
|
136 | }
|
137 | ]
|
138 | }
|
139 | )
|
140 | })
|
141 | })
|
142 |
|
143 | describe('getDependency()', () => {
|
144 | let copacetic
|
145 |
|
146 | it('should look up a dependency given a dependency name', () => {
|
147 | copacetic = Copacetic()
|
148 | copacetic.registerDependency({
|
149 | name: 'My-Dependency',
|
150 | url: 'http://example.com'
|
151 | })
|
152 |
|
153 | expect(copacetic.getDependency('My-Dependency').name).to.equal('My-Dependency')
|
154 | })
|
155 |
|
156 | it('should look up a dependency given a dependency object', () => {
|
157 | expect(
|
158 | copacetic.getDependency(copacetic.getDependency('My-Dependency')).name
|
159 | ).to.equal('My-Dependency')
|
160 | })
|
161 | })
|
162 |
|
163 | describe('isDependencyRegistered', () => {
|
164 | let copacetic
|
165 |
|
166 | it('should return true if a dependency exists', () => {
|
167 | copacetic = Copacetic()
|
168 | copacetic.registerDependency({
|
169 | name: 'My-Dependency',
|
170 | url: 'http://example.com'
|
171 | })
|
172 |
|
173 | expect(copacetic.isDependencyRegistered('My-Dependency')).to.equal(true)
|
174 | })
|
175 |
|
176 | it('should return false if a dependency does not exist', () => {
|
177 | expect(copacetic.isDependencyRegistered('Test-Dependency')).to.equal(false)
|
178 | })
|
179 | })
|
180 |
|
181 | describe('registerDependency()', () => {
|
182 | let copacetic
|
183 |
|
184 | it('should register a dependency if it does not exist', () => {
|
185 | copacetic = Copacetic()
|
186 | copacetic.registerDependency({
|
187 | name: 'My-Dependency',
|
188 | url: 'http://example.com'
|
189 | })
|
190 |
|
191 | expect(copacetic.getDependency('My-Dependency').name).to.equal('My-Dependency')
|
192 | })
|
193 |
|
194 | it('should throw an error if a dependency exists', () => {
|
195 | expect(() => copacetic.registerDependency({
|
196 | name: 'My-Dependency',
|
197 | url: 'http://example.com'
|
198 | })).to.throw(Error)
|
199 | })
|
200 | })
|
201 |
|
202 | describe('deregister()', () => {
|
203 | let copacetic
|
204 |
|
205 | it('should deregister a dependency if it does exist', () => {
|
206 | copacetic = Copacetic()
|
207 |
|
208 | copacetic.registerDependency({
|
209 | name: 'My-Dependency',
|
210 | url: 'http://example.com'
|
211 | })
|
212 |
|
213 | expect(copacetic.dependencyNames.indexOf('My-Dependency')).not.to.equal(-1)
|
214 | expect(() => copacetic.deregisterDependency('My-Dependency')).not.to.throw(Error)
|
215 | expect(copacetic.dependencyNames.indexOf('My-Dependency')).to.equal(-1)
|
216 | })
|
217 |
|
218 | it('should throw an error if the dependency does not exist', () => {
|
219 | expect(() => copacetic.deregisterDependency('My-Dependencyyy')).to.throw(Error)
|
220 | })
|
221 | })
|
222 |
|
223 | describe('checkAll()', () => {
|
224 | let copacetic
|
225 |
|
226 | beforeEach(() => {
|
227 | copacetic = Copacetic()
|
228 | copacetic.registerDependency({
|
229 | name: 'My-Dependency',
|
230 | url: 'http://example.com'
|
231 | })
|
232 | copacetic.registerDependency({
|
233 | name: 'My-Other-Dependency',
|
234 | url: 'http://other-example.com'
|
235 | })
|
236 |
|
237 | nock('http://example.com')
|
238 | .get('/')
|
239 | .reply(200)
|
240 | nock('http://other-example.com')
|
241 | .get('/')
|
242 | .reply(200)
|
243 | })
|
244 |
|
245 | it('should check the health of all registered dependencies', (done) => {
|
246 | copacetic
|
247 | .checkAll()
|
248 | .on('health', (dependencies) => {
|
249 | expect(dependencies).to.deep.equal([
|
250 | {
|
251 | name: 'My-Dependency',
|
252 | healthy: true,
|
253 | level: 'SOFT',
|
254 | lastChecked: dependencies[0].lastChecked
|
255 | },
|
256 | {
|
257 | name: 'My-Other-Dependency',
|
258 | healthy: true,
|
259 | level: 'SOFT',
|
260 | lastChecked: dependencies[1].lastChecked
|
261 | }
|
262 | ])
|
263 | done()
|
264 | })
|
265 | })
|
266 |
|
267 | it('should return a promise when not in eventEmitterMode', () => {
|
268 | copacetic.eventEmitterMode = false
|
269 | return copacetic
|
270 | .checkAll()
|
271 | .then((dependencies) => {
|
272 | expect(dependencies).to.deep.equal([
|
273 | {
|
274 | name: 'My-Dependency',
|
275 | healthy: true,
|
276 | level: 'SOFT',
|
277 | lastChecked: dependencies[0].lastChecked
|
278 | },
|
279 | {
|
280 | name: 'My-Other-Dependency',
|
281 | healthy: true,
|
282 | level: 'SOFT',
|
283 | lastChecked: dependencies[1].lastChecked
|
284 | }
|
285 | ])
|
286 | })
|
287 | })
|
288 | })
|
289 |
|
290 | describe('check()', () => {
|
291 | describe('when checking one dependency', () => {
|
292 | let copacetic
|
293 |
|
294 | it('should emit an "healthy" event when checking a single healthy dependency', (done) => {
|
295 | copacetic = Copacetic()
|
296 | copacetic.registerDependency({
|
297 | name: 'My-Dependency',
|
298 | url: 'http://example.com'
|
299 | })
|
300 |
|
301 | nock('http://example.com')
|
302 | .get('/')
|
303 | .reply(200)
|
304 |
|
305 | copacetic
|
306 | .check({ name: 'My-Dependency' })
|
307 | .on('healthy', (dependency) => {
|
308 | expect(dependency).to.deep.equal({
|
309 | name: 'My-Dependency',
|
310 | healthy: true,
|
311 | level: 'SOFT',
|
312 | lastChecked: dependency.lastChecked
|
313 | })
|
314 | done()
|
315 | })
|
316 | })
|
317 | it('should emit an "unhealthy" event when checking a single unhealthy dependency', (done) => {
|
318 | nock('http://example.com')
|
319 | .get('/')
|
320 | .reply(400)
|
321 |
|
322 | copacetic
|
323 | .check({ name: 'My-Dependency' })
|
324 | .on('unhealthy', (dependency) => {
|
325 | expect(dependency).to.deep.equal({
|
326 | name: 'My-Dependency',
|
327 | healthy: false,
|
328 | level: 'SOFT',
|
329 | lastChecked: dependency.lastChecked
|
330 | })
|
331 | done()
|
332 | })
|
333 | })
|
334 |
|
335 | it('should return a promise when not in eventEmitterMode', () => {
|
336 | nock('http://example.com')
|
337 | .get('/')
|
338 | .reply(200)
|
339 |
|
340 | copacetic.eventEmitterMode = false
|
341 |
|
342 | return copacetic
|
343 | .check({ name: 'My-Dependency' })
|
344 | .then((dependency) => {
|
345 | expect(dependency).to.deep.equal({
|
346 | name: 'My-Dependency',
|
347 | healthy: true,
|
348 | level: 'SOFT',
|
349 | lastChecked: dependency.lastChecked
|
350 | })
|
351 | })
|
352 | })
|
353 | })
|
354 |
|
355 | describe('when checking multiple dependencies', () => {
|
356 | it('should emit a "health" event when checking dependencies', (done) => {
|
357 | let copacetic = Copacetic()
|
358 | copacetic.registerDependency({
|
359 | name: 'My-Dependency',
|
360 | url: 'http://example.com'
|
361 | })
|
362 | .registerDependency({
|
363 | name: 'My-Other-Dependency',
|
364 | url: 'http://dankdependency.com'
|
365 | })
|
366 |
|
367 | nock('http://example.com')
|
368 | .get('/')
|
369 | .reply(200)
|
370 | nock('http://dankdependency.com')
|
371 | .get('/')
|
372 | .reply(400)
|
373 |
|
374 | copacetic
|
375 | .check({
|
376 | dependencies: [
|
377 | { name: 'My-Dependency' },
|
378 | { name: 'My-Other-Dependency' }
|
379 | ],
|
380 | parallel: false
|
381 | })
|
382 | .on('health', (healthSummary) => {
|
383 | expect(healthSummary).to.deep.equal([
|
384 | {
|
385 | name: 'My-Dependency',
|
386 | healthy: true,
|
387 | level: 'SOFT',
|
388 | lastChecked: healthSummary[0].lastChecked
|
389 | },
|
390 | {
|
391 | name: 'My-Other-Dependency',
|
392 | healthy: false,
|
393 | level: 'SOFT',
|
394 | lastChecked: healthSummary[1].lastChecked
|
395 | }
|
396 | ])
|
397 | done()
|
398 | })
|
399 | })
|
400 | })
|
401 | })
|
402 |
|
403 | describe('waitFor', () => {
|
404 | it('should call check with unlimited retries', () => {
|
405 | const copacetic = Copacetic()
|
406 | copacetic.registerDependency({
|
407 | name: 'My-Dependency',
|
408 | url: 'http://example.com'
|
409 | })
|
410 |
|
411 | const checkSpy = sinon.stub(copacetic, 'check').callsFake(noop)
|
412 |
|
413 | copacetic.waitFor({ name: 'My-Dependency' })
|
414 | copacetic.waitFor({
|
415 | dependencies: [ { name: 'My-Dependency' } ]
|
416 | })
|
417 |
|
418 | expect(checkSpy.getCall(0).args[0]).to.deep.equal({
|
419 | name: 'My-Dependency', retries: 0
|
420 | })
|
421 |
|
422 | expect(checkSpy.getCall(1).args[0]).to.deep.equal({
|
423 | dependencies: [ { name: 'My-Dependency', retries: 0, maxDelay: 0 } ]
|
424 | })
|
425 | })
|
426 | })
|
427 |
|
428 | describe('poll()', () => {
|
429 | it('should emit a "health" event, describing the status of dependencies', (done) => {
|
430 | const copacetic = Copacetic()
|
431 | copacetic.registerDependency({
|
432 | name: 'My-Dependency',
|
433 | url: 'http://example.com'
|
434 | })
|
435 | copacetic.registerDependency({
|
436 | name: 'My-Other-Dependency',
|
437 | url: 'http://dankdependency.com'
|
438 | })
|
439 |
|
440 | nock('http://example.com')
|
441 | .get('/')
|
442 | .reply(200)
|
443 | nock('http://dankdependency.com')
|
444 | .get('/')
|
445 | .reply(400)
|
446 |
|
447 | copacetic
|
448 | .poll({
|
449 | dependencies: [
|
450 | { name: 'My-Dependency' },
|
451 | { name: 'My-Other-Dependency' }
|
452 | ]
|
453 | })
|
454 | .on('health', (healthSummary, stop) => {
|
455 | expect(healthSummary).to.deep.equal([
|
456 | {
|
457 | name: 'My-Dependency',
|
458 | healthy: true,
|
459 | level: 'SOFT',
|
460 | lastChecked: healthSummary[0].lastChecked
|
461 | },
|
462 | {
|
463 | name: 'My-Other-Dependency',
|
464 | healthy: false,
|
465 | level: 'SOFT',
|
466 | lastChecked: healthSummary[1].lastChecked
|
467 | }
|
468 | ])
|
469 |
|
470 | stop()
|
471 | done()
|
472 | })
|
473 | })
|
474 |
|
475 | it('should poll a single dependency', (done) => {
|
476 | const copacetic = Copacetic()
|
477 | copacetic.registerDependency({
|
478 | name: 'My-Dependency',
|
479 | url: 'http://example.com'
|
480 | })
|
481 | nock('http://example.com')
|
482 | .get('/')
|
483 | .reply(200)
|
484 |
|
485 | return copacetic
|
486 | .poll({ name: 'My-Dependency' })
|
487 | .on('health', (healthSummary, stop) => {
|
488 | expect(healthSummary).to.deep.equal({
|
489 | name: 'My-Dependency',
|
490 | healthy: true,
|
491 | level: 'SOFT',
|
492 | lastChecked: healthSummary.lastChecked
|
493 | })
|
494 |
|
495 | stop()
|
496 | done()
|
497 | })
|
498 | })
|
499 | })
|
500 |
|
501 | describe('stop()', () => {
|
502 | it('should stop polling dependencies', () => {
|
503 | const copacetic = Copacetic()
|
504 | copacetic.registerDependency({
|
505 | name: 'My-Dependency',
|
506 | url: 'http://example.com'
|
507 | })
|
508 |
|
509 | nock('http://example.com')
|
510 | .get('/')
|
511 | .reply(200)
|
512 |
|
513 | copacetic.pollAll()
|
514 | expect(copacetic.isPolling).to.equal(true)
|
515 | copacetic.stop()
|
516 | expect(copacetic.isPolling).to.equal(false)
|
517 | })
|
518 | })
|
519 | })
|