|
|
@@ -1,5 +1,6 @@
|
|
|
chai = require('chai')
|
|
|
should = chai.should()
|
|
|
+expect = chai.expect
|
|
|
sinon = require("sinon")
|
|
|
modulePath = "../../../app/js/ChannelManager.js"
|
|
|
SandboxedModule = require('sandboxed-module')
|
|
|
@@ -11,34 +12,82 @@ describe 'ChannelManager', ->
|
|
|
@ChannelManager = SandboxedModule.require modulePath, requires:
|
|
|
"settings-sharelatex": @settings = {}
|
|
|
"metrics-sharelatex": @metrics = {inc: sinon.stub(), summary: sinon.stub()}
|
|
|
- "logger-sharelatex": @logger = { log: sinon.stub(), warn: sinon.stub(), error: sinon.stub() }
|
|
|
-
|
|
|
+
|
|
|
describe "subscribe", ->
|
|
|
|
|
|
describe "when there is no existing subscription for this redis client", ->
|
|
|
- beforeEach ->
|
|
|
- @rclient.subscribe = sinon.stub()
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.subscribe = sinon.stub().resolves()
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ setTimeout done
|
|
|
|
|
|
it "should subscribe to the redis channel", ->
|
|
|
@rclient.subscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|
|
|
|
|
|
describe "when there is an existing subscription for this redis client", ->
|
|
|
- beforeEach ->
|
|
|
- @rclient.subscribe = sinon.stub()
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.subscribe = sinon.stub().resolves()
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
- @rclient.subscribe = sinon.stub() # discard the original stub
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ setTimeout done
|
|
|
|
|
|
- it "should not subscribe to the redis channel", ->
|
|
|
- @rclient.subscribe.called.should.equal false
|
|
|
+ it "should subscribe to the redis channel again", ->
|
|
|
+ @rclient.subscribe.callCount.should.equal 2
|
|
|
+
|
|
|
+ describe "when subscribe errors", ->
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.subscribe = sinon.stub()
|
|
|
+ .onFirstCall().rejects(new Error("some redis error"))
|
|
|
+ .onSecondCall().resolves()
|
|
|
+ p = @ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ p.then () ->
|
|
|
+ done(new Error('should not subscribe but fail'))
|
|
|
+ .catch (err) =>
|
|
|
+ err.message.should.equal "some redis error"
|
|
|
+ @ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef").should.equal false
|
|
|
+ @ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ # subscribe is wrapped in Promise, delay other assertions
|
|
|
+ setTimeout done
|
|
|
+ return null
|
|
|
+
|
|
|
+ it "should have recorded the error", ->
|
|
|
+ expect(@metrics.inc.calledWithExactly("subscribe.failed.applied-ops")).to.equal(true)
|
|
|
+
|
|
|
+ it "should subscribe again", ->
|
|
|
+ @rclient.subscribe.callCount.should.equal 2
|
|
|
+
|
|
|
+ it "should cleanup", ->
|
|
|
+ @ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef").should.equal false
|
|
|
+
|
|
|
+ describe "when subscribe errors and the clientChannelMap entry was replaced", ->
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.subscribe = sinon.stub()
|
|
|
+ .onFirstCall().rejects(new Error("some redis error"))
|
|
|
+ .onSecondCall().resolves()
|
|
|
+ @first = @ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ # ignore error
|
|
|
+ @first.catch((()->))
|
|
|
+ expect(@ChannelManager.getClientMapEntry(@rclient).get("applied-ops:1234567890abcdef")).to.equal @first
|
|
|
+
|
|
|
+ @rclient.unsubscribe = sinon.stub().resolves()
|
|
|
+ @ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ @second = @ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ # should get replaced immediately
|
|
|
+ expect(@ChannelManager.getClientMapEntry(@rclient).get("applied-ops:1234567890abcdef")).to.equal @second
|
|
|
+
|
|
|
+ # let the first subscribe error -> unsubscribe -> subscribe
|
|
|
+ setTimeout done
|
|
|
+
|
|
|
+ it "should cleanup the second subscribePromise", ->
|
|
|
+ expect(@ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef")).to.equal false
|
|
|
|
|
|
describe "when there is an existing subscription for another redis client but not this one", ->
|
|
|
- beforeEach ->
|
|
|
- @other_rclient.subscribe = sinon.stub()
|
|
|
+ beforeEach (done) ->
|
|
|
+ @other_rclient.subscribe = sinon.stub().resolves()
|
|
|
@ChannelManager.subscribe @other_rclient, "applied-ops", "1234567890abcdef"
|
|
|
- @rclient.subscribe = sinon.stub() # discard the original stub
|
|
|
+ @rclient.subscribe = sinon.stub().resolves() # discard the original stub
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ setTimeout done
|
|
|
|
|
|
it "should subscribe to the redis channel on this redis client", ->
|
|
|
@rclient.subscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|
|
|
@@ -46,30 +95,82 @@ describe 'ChannelManager', ->
|
|
|
describe "unsubscribe", ->
|
|
|
|
|
|
describe "when there is no existing subscription for this redis client", ->
|
|
|
- beforeEach ->
|
|
|
- @rclient.unsubscribe = sinon.stub()
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.unsubscribe = sinon.stub().resolves()
|
|
|
@ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ setTimeout done
|
|
|
|
|
|
- it "should not unsubscribe from the redis channel", ->
|
|
|
- @rclient.unsubscribe.called.should.equal false
|
|
|
+ it "should unsubscribe from the redis channel", ->
|
|
|
+ @rclient.unsubscribe.called.should.equal true
|
|
|
|
|
|
|
|
|
describe "when there is an existing subscription for this another redis client but not this one", ->
|
|
|
- beforeEach ->
|
|
|
- @other_rclient.subscribe = sinon.stub()
|
|
|
- @rclient.unsubscribe = sinon.stub()
|
|
|
+ beforeEach (done) ->
|
|
|
+ @other_rclient.subscribe = sinon.stub().resolves()
|
|
|
+ @rclient.unsubscribe = sinon.stub().resolves()
|
|
|
@ChannelManager.subscribe @other_rclient, "applied-ops", "1234567890abcdef"
|
|
|
@ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ setTimeout done
|
|
|
|
|
|
- it "should not unsubscribe from the redis channel on this client", ->
|
|
|
- @rclient.unsubscribe.called.should.equal false
|
|
|
+ it "should still unsubscribe from the redis channel on this client", ->
|
|
|
+ @rclient.unsubscribe.called.should.equal true
|
|
|
+
|
|
|
+ describe "when unsubscribe errors and completes", ->
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.subscribe = sinon.stub().resolves()
|
|
|
+ @ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ @rclient.unsubscribe = sinon.stub().rejects(new Error("some redis error"))
|
|
|
+ @ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ setTimeout done
|
|
|
+ return null
|
|
|
+
|
|
|
+ it "should have cleaned up", ->
|
|
|
+ @ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef").should.equal false
|
|
|
+
|
|
|
+ it "should not error out when subscribing again", (done) ->
|
|
|
+ p = @ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ p.then () ->
|
|
|
+ done()
|
|
|
+ .catch done
|
|
|
+ return null
|
|
|
+
|
|
|
+ describe "when unsubscribe errors and another client subscribes at the same time", ->
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.subscribe = sinon.stub().resolves()
|
|
|
+ @ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ rejectSubscribe = undefined
|
|
|
+ @rclient.unsubscribe = () ->
|
|
|
+ return new Promise (resolve, reject) ->
|
|
|
+ rejectSubscribe = reject
|
|
|
+ @ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+
|
|
|
+ setTimeout () =>
|
|
|
+ # delay, actualUnsubscribe should not see the new subscribe request
|
|
|
+ @ChannelManager.subscribe(@rclient, "applied-ops", "1234567890abcdef")
|
|
|
+ .then () ->
|
|
|
+ setTimeout done
|
|
|
+ .catch done
|
|
|
+ setTimeout ->
|
|
|
+ # delay, rejectSubscribe is not defined immediately
|
|
|
+ rejectSubscribe(new Error("redis error"))
|
|
|
+ return null
|
|
|
+
|
|
|
+ it "should have recorded the error", ->
|
|
|
+ expect(@metrics.inc.calledWithExactly("unsubscribe.failed.applied-ops")).to.equal(true)
|
|
|
+
|
|
|
+ it "should have subscribed", ->
|
|
|
+ @rclient.subscribe.called.should.equal true
|
|
|
+
|
|
|
+ it "should have discarded the finished Promise", ->
|
|
|
+ @ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef").should.equal false
|
|
|
|
|
|
describe "when there is an existing subscription for this redis client", ->
|
|
|
- beforeEach ->
|
|
|
- @rclient.subscribe = sinon.stub()
|
|
|
- @rclient.unsubscribe = sinon.stub()
|
|
|
+ beforeEach (done) ->
|
|
|
+ @rclient.subscribe = sinon.stub().resolves()
|
|
|
+ @rclient.unsubscribe = sinon.stub().resolves()
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
@ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
+ setTimeout done
|
|
|
|
|
|
it "should unsubscribe from the redis channel", ->
|
|
|
@rclient.unsubscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|