WebsocketLoadBalancerTests.coffee 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. SandboxedModule = require('sandboxed-module')
  2. sinon = require('sinon')
  3. require('chai').should()
  4. modulePath = require('path').join __dirname, '../../../app/js/WebsocketLoadBalancer'
  5. describe "WebsocketLoadBalancer", ->
  6. beforeEach ->
  7. @rclient = {}
  8. @RoomEvents = {on: sinon.stub()}
  9. @WebsocketLoadBalancer = SandboxedModule.require modulePath, requires:
  10. "./RedisClientManager":
  11. createClientList: () => []
  12. "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
  13. "./SafeJsonParse": @SafeJsonParse =
  14. parse: (data, cb) => cb null, JSON.parse(data)
  15. "./EventLogger": {checkEventOrder: sinon.stub()}
  16. "./HealthCheckManager": {check: sinon.stub()}
  17. "./RoomManager" : @RoomManager = {eventSource: sinon.stub().returns @RoomEvents}
  18. "./ChannelManager": @ChannelManager = {publish: sinon.stub()}
  19. "./ConnectedUsersManager": @ConnectedUsersManager = {refreshClient: sinon.stub()}
  20. "./Utils": @Utils = {
  21. getClientAttributes: sinon.spy(
  22. (client, _attrs, callback) ->
  23. callback(null, {is_restricted_user: !!client.__isRestricted})
  24. )
  25. }
  26. @io = {}
  27. @WebsocketLoadBalancer.rclientPubList = [{publish: sinon.stub()}]
  28. @WebsocketLoadBalancer.rclientSubList = [{
  29. subscribe: sinon.stub()
  30. on: sinon.stub()
  31. }]
  32. @room_id = "room-id"
  33. @message = "otUpdateApplied"
  34. @payload = ["argument one", 42]
  35. describe "emitToRoom", ->
  36. beforeEach ->
  37. @WebsocketLoadBalancer.emitToRoom(@room_id, @message, @payload...)
  38. it "should publish the message to redis", ->
  39. @ChannelManager.publish
  40. .calledWith(@WebsocketLoadBalancer.rclientPubList[0], "editor-events", @room_id, JSON.stringify(
  41. room_id: @room_id,
  42. message: @message
  43. payload: @payload
  44. ))
  45. .should.equal true
  46. describe "emitToAll", ->
  47. beforeEach ->
  48. @WebsocketLoadBalancer.emitToRoom = sinon.stub()
  49. @WebsocketLoadBalancer.emitToAll @message, @payload...
  50. it "should emit to the room 'all'", ->
  51. @WebsocketLoadBalancer.emitToRoom
  52. .calledWith("all", @message, @payload...)
  53. .should.equal true
  54. describe "listenForEditorEvents", ->
  55. beforeEach ->
  56. @WebsocketLoadBalancer._processEditorEvent = sinon.stub()
  57. @WebsocketLoadBalancer.listenForEditorEvents()
  58. it "should subscribe to the editor-events channel", ->
  59. @WebsocketLoadBalancer.rclientSubList[0].subscribe
  60. .calledWith("editor-events")
  61. .should.equal true
  62. it "should process the events with _processEditorEvent", ->
  63. @WebsocketLoadBalancer.rclientSubList[0].on
  64. .calledWith("message", sinon.match.func)
  65. .should.equal true
  66. describe "_processEditorEvent", ->
  67. describe "with bad JSON", ->
  68. beforeEach ->
  69. @isRestrictedUser = false
  70. @SafeJsonParse.parse = sinon.stub().callsArgWith 1, new Error("oops")
  71. @WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", "blah")
  72. it "should log an error", ->
  73. @logger.error.called.should.equal true
  74. describe "with a designated room", ->
  75. beforeEach ->
  76. @io.sockets =
  77. clients: sinon.stub().returns([
  78. {id: 'client-id-1', emit: @emit1 = sinon.stub()}
  79. {id: 'client-id-2', emit: @emit2 = sinon.stub()}
  80. {id: 'client-id-1', emit: @emit3 = sinon.stub()} # duplicate client
  81. ])
  82. data = JSON.stringify
  83. room_id: @room_id
  84. message: @message
  85. payload: @payload
  86. @WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
  87. it "should send the message to all (unique) clients in the room", ->
  88. @io.sockets.clients
  89. .calledWith(@room_id)
  90. .should.equal true
  91. @emit1.calledWith(@message, @payload...).should.equal true
  92. @emit2.calledWith(@message, @payload...).should.equal true
  93. @emit3.called.should.equal false # duplicate client should be ignored
  94. describe "with a designated room, and restricted clients, not restricted message", ->
  95. beforeEach ->
  96. @io.sockets =
  97. clients: sinon.stub().returns([
  98. {id: 'client-id-1', emit: @emit1 = sinon.stub()}
  99. {id: 'client-id-2', emit: @emit2 = sinon.stub()}
  100. {id: 'client-id-1', emit: @emit3 = sinon.stub()} # duplicate client
  101. {id: 'client-id-4', emit: @emit4 = sinon.stub(), __isRestricted: true}
  102. ])
  103. data = JSON.stringify
  104. room_id: @room_id
  105. message: @message
  106. payload: @payload
  107. @WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
  108. it "should send the message to all (unique) clients in the room", ->
  109. @io.sockets.clients
  110. .calledWith(@room_id)
  111. .should.equal true
  112. @emit1.calledWith(@message, @payload...).should.equal true
  113. @emit2.calledWith(@message, @payload...).should.equal true
  114. @emit3.called.should.equal false # duplicate client should be ignored
  115. @emit4.called.should.equal true # restricted client, but should be called
  116. describe "with a designated room, and restricted clients, restricted message", ->
  117. beforeEach ->
  118. @io.sockets =
  119. clients: sinon.stub().returns([
  120. {id: 'client-id-1', emit: @emit1 = sinon.stub()}
  121. {id: 'client-id-2', emit: @emit2 = sinon.stub()}
  122. {id: 'client-id-1', emit: @emit3 = sinon.stub()} # duplicate client
  123. {id: 'client-id-4', emit: @emit4 = sinon.stub(), __isRestricted: true}
  124. ])
  125. data = JSON.stringify
  126. room_id: @room_id
  127. message: @restrictedMessage = 'new-comment'
  128. payload: @payload
  129. @WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
  130. it "should send the message to all (unique) clients in the room, who are not restricted", ->
  131. @io.sockets.clients
  132. .calledWith(@room_id)
  133. .should.equal true
  134. @emit1.calledWith(@restrictedMessage, @payload...).should.equal true
  135. @emit2.calledWith(@restrictedMessage, @payload...).should.equal true
  136. @emit3.called.should.equal false # duplicate client should be ignored
  137. @emit4.called.should.equal false # restricted client, should not be called
  138. describe "when emitting to all", ->
  139. beforeEach ->
  140. @io.sockets =
  141. emit: @emit = sinon.stub()
  142. data = JSON.stringify
  143. room_id: "all"
  144. message: @message
  145. payload: @payload
  146. @WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
  147. it "should send the message to all clients", ->
  148. @emit.calledWith(@message, @payload...).should.equal true