2019-07-19 06:58:40 -04:00
|
|
|
chai = require('chai')
|
|
|
|
should = chai.should()
|
2020-05-15 05:43:11 -04:00
|
|
|
expect = chai.expect
|
2019-07-19 06:58:40 -04:00
|
|
|
sinon = require("sinon")
|
|
|
|
modulePath = "../../../app/js/ChannelManager.js"
|
|
|
|
SandboxedModule = require('sandboxed-module')
|
|
|
|
|
|
|
|
describe 'ChannelManager', ->
|
|
|
|
beforeEach ->
|
2019-07-22 06:23:43 -04:00
|
|
|
@rclient = {}
|
|
|
|
@other_rclient = {}
|
2019-07-19 06:58:40 -04:00
|
|
|
@ChannelManager = SandboxedModule.require modulePath, requires:
|
|
|
|
"settings-sharelatex": @settings = {}
|
2020-03-30 05:31:44 -04:00
|
|
|
"metrics-sharelatex": @metrics = {inc: sinon.stub(), summary: sinon.stub()}
|
2020-06-05 06:38:09 -04:00
|
|
|
"logger-sharelatex": @logger = { log: sinon.stub(), warn: sinon.stub(), error: sinon.stub() }
|
2020-05-15 05:43:11 -04:00
|
|
|
|
2019-07-19 06:58:40 -04:00
|
|
|
describe "subscribe", ->
|
|
|
|
|
2019-07-22 06:23:43 -04:00
|
|
|
describe "when there is no existing subscription for this redis client", ->
|
2020-05-15 05:43:11 -04:00
|
|
|
beforeEach (done) ->
|
|
|
|
@rclient.subscribe = sinon.stub().resolves()
|
2019-07-22 06:23:43 -04:00
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
2020-05-15 05:43:11 -04:00
|
|
|
setTimeout done
|
2019-07-22 06:23:43 -04:00
|
|
|
|
|
|
|
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", ->
|
2020-05-15 05:43:11 -04:00
|
|
|
beforeEach (done) ->
|
|
|
|
@rclient.subscribe = sinon.stub().resolves()
|
2019-07-22 06:23:43 -04:00
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
2020-05-15 05:43:11 -04:00
|
|
|
setTimeout done
|
2019-07-22 06:23:43 -04:00
|
|
|
|
2020-05-15 05:43:11 -04:00
|
|
|
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
|
2019-07-22 06:23:43 -04:00
|
|
|
|
|
|
|
describe "when there is an existing subscription for another redis client but not this one", ->
|
2020-05-15 05:43:11 -04:00
|
|
|
beforeEach (done) ->
|
|
|
|
@other_rclient.subscribe = sinon.stub().resolves()
|
2019-07-22 06:23:43 -04:00
|
|
|
@ChannelManager.subscribe @other_rclient, "applied-ops", "1234567890abcdef"
|
2020-05-15 05:43:11 -04:00
|
|
|
@rclient.subscribe = sinon.stub().resolves() # discard the original stub
|
2019-07-22 06:23:43 -04:00
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
2020-05-15 05:43:11 -04:00
|
|
|
setTimeout done
|
2019-07-22 06:23:43 -04:00
|
|
|
|
|
|
|
it "should subscribe to the redis channel on this redis client", ->
|
|
|
|
@rclient.subscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|
2019-07-19 06:58:40 -04:00
|
|
|
|
|
|
|
describe "unsubscribe", ->
|
|
|
|
|
2019-07-22 06:23:43 -04:00
|
|
|
describe "when there is no existing subscription for this redis client", ->
|
2020-05-15 05:43:11 -04:00
|
|
|
beforeEach (done) ->
|
|
|
|
@rclient.unsubscribe = sinon.stub().resolves()
|
2019-07-22 06:23:43 -04:00
|
|
|
@ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
2020-05-15 05:43:11 -04:00
|
|
|
setTimeout done
|
2019-07-22 06:23:43 -04:00
|
|
|
|
2020-05-15 05:43:11 -04:00
|
|
|
it "should unsubscribe from the redis channel", ->
|
|
|
|
@rclient.unsubscribe.called.should.equal true
|
2019-07-22 06:23:43 -04:00
|
|
|
|
|
|
|
|
|
|
|
describe "when there is an existing subscription for this another redis client but not this one", ->
|
2020-05-15 05:43:11 -04:00
|
|
|
beforeEach (done) ->
|
|
|
|
@other_rclient.subscribe = sinon.stub().resolves()
|
|
|
|
@rclient.unsubscribe = sinon.stub().resolves()
|
2019-07-22 06:23:43 -04:00
|
|
|
@ChannelManager.subscribe @other_rclient, "applied-ops", "1234567890abcdef"
|
|
|
|
@ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
2020-05-15 05:43:11 -04:00
|
|
|
setTimeout done
|
2019-07-19 06:58:40 -04:00
|
|
|
|
2020-05-15 05:43:11 -04:00
|
|
|
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
|
2019-07-22 06:23:43 -04:00
|
|
|
|
|
|
|
describe "when there is an existing subscription for this redis client", ->
|
2020-05-15 05:43:11 -04:00
|
|
|
beforeEach (done) ->
|
|
|
|
@rclient.subscribe = sinon.stub().resolves()
|
|
|
|
@rclient.unsubscribe = sinon.stub().resolves()
|
2019-07-22 06:23:43 -04:00
|
|
|
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
|
|
|
@ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
2020-05-15 05:43:11 -04:00
|
|
|
setTimeout done
|
2019-07-22 06:23:43 -04:00
|
|
|
|
|
|
|
it "should unsubscribe from the redis channel", ->
|
|
|
|
@rclient.unsubscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|
2019-07-19 06:58:40 -04:00
|
|
|
|
|
|
|
describe "publish", ->
|
|
|
|
|
|
|
|
describe "when the channel is 'all'", ->
|
2019-07-22 06:23:43 -04:00
|
|
|
beforeEach ->
|
|
|
|
@rclient.publish = sinon.stub()
|
|
|
|
@ChannelManager.publish @rclient, "applied-ops", "all", "random-message"
|
|
|
|
|
|
|
|
it "should publish on the base channel", ->
|
|
|
|
@rclient.publish.calledWithExactly("applied-ops", "random-message").should.equal true
|
2019-07-19 06:58:40 -04:00
|
|
|
|
|
|
|
describe "when the channel has an specific id", ->
|
2019-07-22 06:23:43 -04:00
|
|
|
|
|
|
|
describe "when the individual channel setting is false", ->
|
|
|
|
beforeEach ->
|
|
|
|
@rclient.publish = sinon.stub()
|
|
|
|
@settings.publishOnIndividualChannels = false
|
|
|
|
@ChannelManager.publish @rclient, "applied-ops", "1234567890abcdef", "random-message"
|
|
|
|
|
|
|
|
it "should publish on the per-id channel", ->
|
|
|
|
@rclient.publish.calledWithExactly("applied-ops", "random-message").should.equal true
|
|
|
|
@rclient.publish.calledOnce.should.equal true
|
|
|
|
|
|
|
|
describe "when the individual channel setting is true", ->
|
|
|
|
beforeEach ->
|
|
|
|
@rclient.publish = sinon.stub()
|
|
|
|
@settings.publishOnIndividualChannels = true
|
|
|
|
@ChannelManager.publish @rclient, "applied-ops", "1234567890abcdef", "random-message"
|
|
|
|
|
|
|
|
it "should publish on the per-id channel", ->
|
|
|
|
@rclient.publish.calledWithExactly("applied-ops:1234567890abcdef", "random-message").should.equal true
|
|
|
|
@rclient.publish.calledOnce.should.equal true
|
|
|
|
|
2020-03-30 05:31:44 -04:00
|
|
|
describe "metrics", ->
|
|
|
|
beforeEach ->
|
|
|
|
@rclient.publish = sinon.stub()
|
|
|
|
@ChannelManager.publish @rclient, "applied-ops", "all", "random-message"
|
|
|
|
|
|
|
|
it "should track the payload size", ->
|
|
|
|
@metrics.summary.calledWithExactly(
|
|
|
|
"redis.publish.applied-ops",
|
|
|
|
"random-message".length
|
|
|
|
).should.equal true
|