mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 06:03:42 -05:00
decaffeinate: Convert AuthorizationManagerTests.coffee and 13 other files to JS
This commit is contained in:
parent
e5d07bd3af
commit
2ca620e7a0
14 changed files with 2987 additions and 2231 deletions
|
@ -1,166 +1,216 @@
|
|||
chai = require "chai"
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
sinon = require("sinon")
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
path = require "path"
|
||||
modulePath = '../../../app/js/AuthorizationManager'
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const chai = require("chai");
|
||||
chai.should();
|
||||
const {
|
||||
expect
|
||||
} = chai;
|
||||
const sinon = require("sinon");
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const path = require("path");
|
||||
const modulePath = '../../../app/js/AuthorizationManager';
|
||||
|
||||
describe 'AuthorizationManager', ->
|
||||
beforeEach ->
|
||||
@client =
|
||||
ol_context: {}
|
||||
describe('AuthorizationManager', function() {
|
||||
beforeEach(function() {
|
||||
this.client =
|
||||
{ol_context: {}};
|
||||
|
||||
@AuthorizationManager = SandboxedModule.require modulePath, requires: {}
|
||||
return this.AuthorizationManager = SandboxedModule.require(modulePath, {requires: {}});});
|
||||
|
||||
describe "assertClientCanViewProject", ->
|
||||
it "should allow the readOnly privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "readOnly"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
describe("assertClientCanViewProject", function() {
|
||||
it("should allow the readOnly privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "readOnly";
|
||||
return this.AuthorizationManager.assertClientCanViewProject(this.client, function(error) {
|
||||
expect(error).to.be.null;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should allow the readAndWrite privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "readAndWrite"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
it("should allow the readAndWrite privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "readAndWrite";
|
||||
return this.AuthorizationManager.assertClientCanViewProject(this.client, function(error) {
|
||||
expect(error).to.be.null;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should allow the owner privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "owner"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
it("should allow the owner privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "owner";
|
||||
return this.AuthorizationManager.assertClientCanViewProject(this.client, function(error) {
|
||||
expect(error).to.be.null;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should return an error with any other privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "unknown"
|
||||
@AuthorizationManager.assertClientCanViewProject @client, (error) ->
|
||||
error.message.should.equal "not authorized"
|
||||
done()
|
||||
return it("should return an error with any other privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "unknown";
|
||||
return this.AuthorizationManager.assertClientCanViewProject(this.client, function(error) {
|
||||
error.message.should.equal("not authorized");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "assertClientCanEditProject", ->
|
||||
it "should not allow the readOnly privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "readOnly"
|
||||
@AuthorizationManager.assertClientCanEditProject @client, (error) ->
|
||||
error.message.should.equal "not authorized"
|
||||
done()
|
||||
describe("assertClientCanEditProject", function() {
|
||||
it("should not allow the readOnly privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "readOnly";
|
||||
return this.AuthorizationManager.assertClientCanEditProject(this.client, function(error) {
|
||||
error.message.should.equal("not authorized");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should allow the readAndWrite privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "readAndWrite"
|
||||
@AuthorizationManager.assertClientCanEditProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
it("should allow the readAndWrite privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "readAndWrite";
|
||||
return this.AuthorizationManager.assertClientCanEditProject(this.client, function(error) {
|
||||
expect(error).to.be.null;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should allow the owner privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "owner"
|
||||
@AuthorizationManager.assertClientCanEditProject @client, (error) ->
|
||||
expect(error).to.be.null
|
||||
done()
|
||||
it("should allow the owner privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "owner";
|
||||
return this.AuthorizationManager.assertClientCanEditProject(this.client, function(error) {
|
||||
expect(error).to.be.null;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should return an error with any other privilegeLevel", (done) ->
|
||||
@client.ol_context.privilege_level = "unknown"
|
||||
@AuthorizationManager.assertClientCanEditProject @client, (error) ->
|
||||
error.message.should.equal "not authorized"
|
||||
done()
|
||||
return it("should return an error with any other privilegeLevel", function(done) {
|
||||
this.client.ol_context.privilege_level = "unknown";
|
||||
return this.AuthorizationManager.assertClientCanEditProject(this.client, function(error) {
|
||||
error.message.should.equal("not authorized");
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
# check doc access for project
|
||||
// check doc access for project
|
||||
|
||||
describe "assertClientCanViewProjectAndDoc", ->
|
||||
beforeEach () ->
|
||||
@doc_id = "12345"
|
||||
@callback = sinon.stub()
|
||||
@client.ol_context = {}
|
||||
describe("assertClientCanViewProjectAndDoc", function() {
|
||||
beforeEach(function() {
|
||||
this.doc_id = "12345";
|
||||
this.callback = sinon.stub();
|
||||
return this.client.ol_context = {};});
|
||||
|
||||
describe "when not authorised at the project level", ->
|
||||
beforeEach () ->
|
||||
@client.ol_context.privilege_level = "unknown"
|
||||
describe("when not authorised at the project level", function() {
|
||||
beforeEach(function() {
|
||||
return this.client.ol_context.privilege_level = "unknown";
|
||||
});
|
||||
|
||||
it "should not allow access", () ->
|
||||
@AuthorizationManager.assertClientCanViewProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
it("should not allow access", function() {
|
||||
return this.AuthorizationManager.assertClientCanViewProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
});
|
||||
|
||||
describe "even when authorised at the doc level", ->
|
||||
beforeEach (done) ->
|
||||
@AuthorizationManager.addAccessToDoc @client, @doc_id, done
|
||||
return describe("even when authorised at the doc level", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.AuthorizationManager.addAccessToDoc(this.client, this.doc_id, done);
|
||||
});
|
||||
|
||||
it "should not allow access", () ->
|
||||
@AuthorizationManager.assertClientCanViewProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
return it("should not allow access", function() {
|
||||
return this.AuthorizationManager.assertClientCanViewProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "when authorised at the project level", ->
|
||||
beforeEach () ->
|
||||
@client.ol_context.privilege_level = "readOnly"
|
||||
return describe("when authorised at the project level", function() {
|
||||
beforeEach(function() {
|
||||
return this.client.ol_context.privilege_level = "readOnly";
|
||||
});
|
||||
|
||||
describe "and not authorised at the document level", ->
|
||||
it "should not allow access", () ->
|
||||
@AuthorizationManager.assertClientCanViewProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
describe("and not authorised at the document level", () => it("should not allow access", function() {
|
||||
return this.AuthorizationManager.assertClientCanViewProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
}));
|
||||
|
||||
describe "and authorised at the document level", ->
|
||||
beforeEach (done) ->
|
||||
@AuthorizationManager.addAccessToDoc @client, @doc_id, done
|
||||
describe("and authorised at the document level", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.AuthorizationManager.addAccessToDoc(this.client, this.doc_id, done);
|
||||
});
|
||||
|
||||
it "should allow access", () ->
|
||||
@AuthorizationManager.assertClientCanViewProjectAndDoc @client, @doc_id, @callback
|
||||
@callback
|
||||
return it("should allow access", function() {
|
||||
this.AuthorizationManager.assertClientCanViewProjectAndDoc(this.client, this.doc_id, this.callback);
|
||||
return this.callback
|
||||
.calledWith(null)
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when document authorisation is added and then removed", ->
|
||||
beforeEach (done) ->
|
||||
@AuthorizationManager.addAccessToDoc @client, @doc_id, () =>
|
||||
@AuthorizationManager.removeAccessToDoc @client, @doc_id, done
|
||||
return describe("when document authorisation is added and then removed", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.AuthorizationManager.addAccessToDoc(this.client, this.doc_id, () => {
|
||||
return this.AuthorizationManager.removeAccessToDoc(this.client, this.doc_id, done);
|
||||
});
|
||||
});
|
||||
|
||||
it "should deny access", () ->
|
||||
@AuthorizationManager.assertClientCanViewProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
return it("should deny access", function() {
|
||||
return this.AuthorizationManager.assertClientCanViewProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "assertClientCanEditProjectAndDoc", ->
|
||||
beforeEach () ->
|
||||
@doc_id = "12345"
|
||||
@callback = sinon.stub()
|
||||
@client.ol_context = {}
|
||||
return describe("assertClientCanEditProjectAndDoc", function() {
|
||||
beforeEach(function() {
|
||||
this.doc_id = "12345";
|
||||
this.callback = sinon.stub();
|
||||
return this.client.ol_context = {};});
|
||||
|
||||
describe "when not authorised at the project level", ->
|
||||
beforeEach () ->
|
||||
@client.ol_context.privilege_level = "readOnly"
|
||||
describe("when not authorised at the project level", function() {
|
||||
beforeEach(function() {
|
||||
return this.client.ol_context.privilege_level = "readOnly";
|
||||
});
|
||||
|
||||
it "should not allow access", () ->
|
||||
@AuthorizationManager.assertClientCanEditProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
it("should not allow access", function() {
|
||||
return this.AuthorizationManager.assertClientCanEditProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
});
|
||||
|
||||
describe "even when authorised at the doc level", ->
|
||||
beforeEach (done) ->
|
||||
@AuthorizationManager.addAccessToDoc @client, @doc_id, done
|
||||
return describe("even when authorised at the doc level", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.AuthorizationManager.addAccessToDoc(this.client, this.doc_id, done);
|
||||
});
|
||||
|
||||
it "should not allow access", () ->
|
||||
@AuthorizationManager.assertClientCanEditProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
return it("should not allow access", function() {
|
||||
return this.AuthorizationManager.assertClientCanEditProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "when authorised at the project level", ->
|
||||
beforeEach () ->
|
||||
@client.ol_context.privilege_level = "readAndWrite"
|
||||
return describe("when authorised at the project level", function() {
|
||||
beforeEach(function() {
|
||||
return this.client.ol_context.privilege_level = "readAndWrite";
|
||||
});
|
||||
|
||||
describe "and not authorised at the document level", ->
|
||||
it "should not allow access", () ->
|
||||
@AuthorizationManager.assertClientCanEditProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
describe("and not authorised at the document level", () => it("should not allow access", function() {
|
||||
return this.AuthorizationManager.assertClientCanEditProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
}));
|
||||
|
||||
describe "and authorised at the document level", ->
|
||||
beforeEach (done) ->
|
||||
@AuthorizationManager.addAccessToDoc @client, @doc_id, done
|
||||
describe("and authorised at the document level", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.AuthorizationManager.addAccessToDoc(this.client, this.doc_id, done);
|
||||
});
|
||||
|
||||
it "should allow access", () ->
|
||||
@AuthorizationManager.assertClientCanEditProjectAndDoc @client, @doc_id, @callback
|
||||
@callback
|
||||
return it("should allow access", function() {
|
||||
this.AuthorizationManager.assertClientCanEditProjectAndDoc(this.client, this.doc_id, this.callback);
|
||||
return this.callback
|
||||
.calledWith(null)
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when document authorisation is added and then removed", ->
|
||||
beforeEach (done) ->
|
||||
@AuthorizationManager.addAccessToDoc @client, @doc_id, () =>
|
||||
@AuthorizationManager.removeAccessToDoc @client, @doc_id, done
|
||||
return describe("when document authorisation is added and then removed", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.AuthorizationManager.addAccessToDoc(this.client, this.doc_id, () => {
|
||||
return this.AuthorizationManager.removeAccessToDoc(this.client, this.doc_id, done);
|
||||
});
|
||||
});
|
||||
|
||||
it "should deny access", () ->
|
||||
@AuthorizationManager.assertClientCanEditProjectAndDoc @client, @doc_id, (err) ->
|
||||
err.message.should.equal "not authorized"
|
||||
return it("should deny access", function() {
|
||||
return this.AuthorizationManager.assertClientCanEditProjectAndDoc(this.client, this.doc_id, err => err.message.should.equal("not authorized"));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,220 +1,274 @@
|
|||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../app/js/ChannelManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const chai = require('chai');
|
||||
const should = chai.should();
|
||||
const {
|
||||
expect
|
||||
} = chai;
|
||||
const sinon = require("sinon");
|
||||
const modulePath = "../../../app/js/ChannelManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
|
||||
describe 'ChannelManager', ->
|
||||
beforeEach ->
|
||||
@rclient = {}
|
||||
@other_rclient = {}
|
||||
@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('ChannelManager', function() {
|
||||
beforeEach(function() {
|
||||
this.rclient = {};
|
||||
this.other_rclient = {};
|
||||
return this.ChannelManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"settings-sharelatex": (this.settings = {}),
|
||||
"metrics-sharelatex": (this.metrics = {inc: sinon.stub(), summary: sinon.stub()}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), warn: sinon.stub(), error: sinon.stub() })
|
||||
}
|
||||
});});
|
||||
|
||||
describe "subscribe", ->
|
||||
describe("subscribe", function() {
|
||||
|
||||
describe "when there is no existing subscription for this redis client", ->
|
||||
beforeEach (done) ->
|
||||
@rclient.subscribe = sinon.stub().resolves()
|
||||
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
||||
setTimeout done
|
||||
describe("when there is no existing subscription for this redis client", function() {
|
||||
beforeEach(function(done) {
|
||||
this.rclient.subscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
return setTimeout(done);
|
||||
});
|
||||
|
||||
it "should subscribe to the redis channel", ->
|
||||
@rclient.subscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|
||||
return it("should subscribe to the redis channel", function() {
|
||||
return this.rclient.subscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when there is an existing subscription for this redis client", ->
|
||||
beforeEach (done) ->
|
||||
@rclient.subscribe = sinon.stub().resolves()
|
||||
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
||||
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
||||
setTimeout done
|
||||
describe("when there is an existing subscription for this redis client", function() {
|
||||
beforeEach(function(done) {
|
||||
this.rclient.subscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
return setTimeout(done);
|
||||
});
|
||||
|
||||
it "should subscribe to the redis channel again", ->
|
||||
@rclient.subscribe.callCount.should.equal 2
|
||||
return it("should subscribe to the redis channel again", function() {
|
||||
return this.rclient.subscribe.callCount.should.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when subscribe errors", ->
|
||||
beforeEach (done) ->
|
||||
@rclient.subscribe = sinon.stub()
|
||||
describe("when subscribe errors", function() {
|
||||
beforeEach(function(done) {
|
||||
this.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
|
||||
.onSecondCall().resolves();
|
||||
const p = this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
p.then(() => done(new Error('should not subscribe but fail'))).catch(err => {
|
||||
err.message.should.equal("some redis error");
|
||||
this.ChannelManager.getClientMapEntry(this.rclient).has("applied-ops:1234567890abcdef").should.equal(false);
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
// subscribe is wrapped in Promise, delay other assertions
|
||||
return setTimeout(done);
|
||||
});
|
||||
return null;
|
||||
});
|
||||
|
||||
it "should have recorded the error", ->
|
||||
expect(@metrics.inc.calledWithExactly("subscribe.failed.applied-ops")).to.equal(true)
|
||||
it("should have recorded the error", function() {
|
||||
return expect(this.metrics.inc.calledWithExactly("subscribe.failed.applied-ops")).to.equal(true);
|
||||
});
|
||||
|
||||
it "should subscribe again", ->
|
||||
@rclient.subscribe.callCount.should.equal 2
|
||||
it("should subscribe again", function() {
|
||||
return this.rclient.subscribe.callCount.should.equal(2);
|
||||
});
|
||||
|
||||
it "should cleanup", ->
|
||||
@ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef").should.equal false
|
||||
return it("should cleanup", function() {
|
||||
return this.ChannelManager.getClientMapEntry(this.rclient).has("applied-ops:1234567890abcdef").should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when subscribe errors and the clientChannelMap entry was replaced", ->
|
||||
beforeEach (done) ->
|
||||
@rclient.subscribe = sinon.stub()
|
||||
describe("when subscribe errors and the clientChannelMap entry was replaced", function() {
|
||||
beforeEach(function(done) {
|
||||
this.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
|
||||
.onSecondCall().resolves();
|
||||
this.first = this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
// ignore error
|
||||
this.first.catch((function(){}));
|
||||
expect(this.ChannelManager.getClientMapEntry(this.rclient).get("applied-ops:1234567890abcdef")).to.equal(this.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
|
||||
this.rclient.unsubscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.unsubscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
this.second = this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
// should get replaced immediately
|
||||
expect(this.ChannelManager.getClientMapEntry(this.rclient).get("applied-ops:1234567890abcdef")).to.equal(this.second);
|
||||
|
||||
# let the first subscribe error -> unsubscribe -> subscribe
|
||||
setTimeout done
|
||||
// let the first subscribe error -> unsubscribe -> subscribe
|
||||
return setTimeout(done);
|
||||
});
|
||||
|
||||
it "should cleanup the second subscribePromise", ->
|
||||
expect(@ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef")).to.equal false
|
||||
return it("should cleanup the second subscribePromise", function() {
|
||||
return expect(this.ChannelManager.getClientMapEntry(this.rclient).has("applied-ops:1234567890abcdef")).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when there is an existing subscription for another redis client but not this one", ->
|
||||
beforeEach (done) ->
|
||||
@other_rclient.subscribe = sinon.stub().resolves()
|
||||
@ChannelManager.subscribe @other_rclient, "applied-ops", "1234567890abcdef"
|
||||
@rclient.subscribe = sinon.stub().resolves() # discard the original stub
|
||||
@ChannelManager.subscribe @rclient, "applied-ops", "1234567890abcdef"
|
||||
setTimeout done
|
||||
return describe("when there is an existing subscription for another redis client but not this one", function() {
|
||||
beforeEach(function(done) {
|
||||
this.other_rclient.subscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.subscribe(this.other_rclient, "applied-ops", "1234567890abcdef");
|
||||
this.rclient.subscribe = sinon.stub().resolves(); // discard the original stub
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
return setTimeout(done);
|
||||
});
|
||||
|
||||
it "should subscribe to the redis channel on this redis client", ->
|
||||
@rclient.subscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|
||||
return it("should subscribe to the redis channel on this redis client", function() {
|
||||
return this.rclient.subscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "unsubscribe", ->
|
||||
describe("unsubscribe", function() {
|
||||
|
||||
describe "when there is no existing subscription for this redis client", ->
|
||||
beforeEach (done) ->
|
||||
@rclient.unsubscribe = sinon.stub().resolves()
|
||||
@ChannelManager.unsubscribe @rclient, "applied-ops", "1234567890abcdef"
|
||||
setTimeout done
|
||||
describe("when there is no existing subscription for this redis client", function() {
|
||||
beforeEach(function(done) {
|
||||
this.rclient.unsubscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.unsubscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
return setTimeout(done);
|
||||
});
|
||||
|
||||
it "should unsubscribe from the redis channel", ->
|
||||
@rclient.unsubscribe.called.should.equal true
|
||||
return it("should unsubscribe from the redis channel", function() {
|
||||
return this.rclient.unsubscribe.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "when there is an existing subscription for this another redis client but not this one", ->
|
||||
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
|
||||
describe("when there is an existing subscription for this another redis client but not this one", function() {
|
||||
beforeEach(function(done) {
|
||||
this.other_rclient.subscribe = sinon.stub().resolves();
|
||||
this.rclient.unsubscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.subscribe(this.other_rclient, "applied-ops", "1234567890abcdef");
|
||||
this.ChannelManager.unsubscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
return setTimeout(done);
|
||||
});
|
||||
|
||||
it "should still unsubscribe from the redis channel on this client", ->
|
||||
@rclient.unsubscribe.called.should.equal true
|
||||
return it("should still unsubscribe from the redis channel on this client", function() {
|
||||
return this.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
|
||||
describe("when unsubscribe errors and completes", function() {
|
||||
beforeEach(function(done) {
|
||||
this.rclient.subscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
this.rclient.unsubscribe = sinon.stub().rejects(new Error("some redis error"));
|
||||
this.ChannelManager.unsubscribe(this.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 have cleaned up", function() {
|
||||
return this.ChannelManager.getClientMapEntry(this.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
|
||||
return it("should not error out when subscribing again", function(done) {
|
||||
const p = this.ChannelManager.subscribe(this.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"
|
||||
describe("when unsubscribe errors and another client subscribes at the same time", function() {
|
||||
beforeEach(function(done) {
|
||||
this.rclient.subscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
let rejectSubscribe = undefined;
|
||||
this.rclient.unsubscribe = () => new Promise((resolve, reject) => rejectSubscribe = reject);
|
||||
this.ChannelManager.unsubscribe(this.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
|
||||
setTimeout(() => {
|
||||
// delay, actualUnsubscribe should not see the new subscribe request
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef")
|
||||
.then(() => setTimeout(done)).catch(done);
|
||||
return 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 recorded the error", function() {
|
||||
return expect(this.metrics.inc.calledWithExactly("unsubscribe.failed.applied-ops")).to.equal(true);
|
||||
});
|
||||
|
||||
it "should have subscribed", ->
|
||||
@rclient.subscribe.called.should.equal true
|
||||
it("should have subscribed", function() {
|
||||
return this.rclient.subscribe.called.should.equal(true);
|
||||
});
|
||||
|
||||
it "should have discarded the finished Promise", ->
|
||||
@ChannelManager.getClientMapEntry(@rclient).has("applied-ops:1234567890abcdef").should.equal false
|
||||
return it("should have discarded the finished Promise", function() {
|
||||
return this.ChannelManager.getClientMapEntry(this.rclient).has("applied-ops:1234567890abcdef").should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when there is an existing subscription for this redis client", ->
|
||||
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
|
||||
return describe("when there is an existing subscription for this redis client", function() {
|
||||
beforeEach(function(done) {
|
||||
this.rclient.subscribe = sinon.stub().resolves();
|
||||
this.rclient.unsubscribe = sinon.stub().resolves();
|
||||
this.ChannelManager.subscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
this.ChannelManager.unsubscribe(this.rclient, "applied-ops", "1234567890abcdef");
|
||||
return setTimeout(done);
|
||||
});
|
||||
|
||||
it "should unsubscribe from the redis channel", ->
|
||||
@rclient.unsubscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal true
|
||||
return it("should unsubscribe from the redis channel", function() {
|
||||
return this.rclient.unsubscribe.calledWithExactly("applied-ops:1234567890abcdef").should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "publish", ->
|
||||
return describe("publish", function() {
|
||||
|
||||
describe "when the channel is 'all'", ->
|
||||
beforeEach ->
|
||||
@rclient.publish = sinon.stub()
|
||||
@ChannelManager.publish @rclient, "applied-ops", "all", "random-message"
|
||||
describe("when the channel is 'all'", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.publish = sinon.stub();
|
||||
return this.ChannelManager.publish(this.rclient, "applied-ops", "all", "random-message");
|
||||
});
|
||||
|
||||
it "should publish on the base channel", ->
|
||||
@rclient.publish.calledWithExactly("applied-ops", "random-message").should.equal true
|
||||
return it("should publish on the base channel", function() {
|
||||
return this.rclient.publish.calledWithExactly("applied-ops", "random-message").should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when the channel has an specific id", ->
|
||||
describe("when the channel has an specific id", function() {
|
||||
|
||||
describe "when the individual channel setting is false", ->
|
||||
beforeEach ->
|
||||
@rclient.publish = sinon.stub()
|
||||
@settings.publishOnIndividualChannels = false
|
||||
@ChannelManager.publish @rclient, "applied-ops", "1234567890abcdef", "random-message"
|
||||
describe("when the individual channel setting is false", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.publish = sinon.stub();
|
||||
this.settings.publishOnIndividualChannels = false;
|
||||
return this.ChannelManager.publish(this.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
|
||||
return it("should publish on the per-id channel", function() {
|
||||
this.rclient.publish.calledWithExactly("applied-ops", "random-message").should.equal(true);
|
||||
return this.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"
|
||||
return describe("when the individual channel setting is true", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.publish = sinon.stub();
|
||||
this.settings.publishOnIndividualChannels = true;
|
||||
return this.ChannelManager.publish(this.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
|
||||
return it("should publish on the per-id channel", function() {
|
||||
this.rclient.publish.calledWithExactly("applied-ops:1234567890abcdef", "random-message").should.equal(true);
|
||||
return this.rclient.publish.calledOnce.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "metrics", ->
|
||||
beforeEach ->
|
||||
@rclient.publish = sinon.stub()
|
||||
@ChannelManager.publish @rclient, "applied-ops", "all", "random-message"
|
||||
return describe("metrics", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.publish = sinon.stub();
|
||||
return this.ChannelManager.publish(this.rclient, "applied-ops", "all", "random-message");
|
||||
});
|
||||
|
||||
it "should track the payload size", ->
|
||||
@metrics.summary.calledWithExactly(
|
||||
return it("should track the payload size", function() {
|
||||
return this.metrics.summary.calledWithExactly(
|
||||
"redis.publish.applied-ops",
|
||||
"random-message".length
|
||||
).should.equal true
|
||||
).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,164 +1,221 @@
|
|||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
|
||||
should = require('chai').should()
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
path = require('path')
|
||||
sinon = require('sinon')
|
||||
modulePath = path.join __dirname, "../../../app/js/ConnectedUsersManager"
|
||||
expect = require("chai").expect
|
||||
tk = require("timekeeper")
|
||||
const should = require('chai').should();
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const sinon = require('sinon');
|
||||
const modulePath = path.join(__dirname, "../../../app/js/ConnectedUsersManager");
|
||||
const {
|
||||
expect
|
||||
} = require("chai");
|
||||
const tk = require("timekeeper");
|
||||
|
||||
|
||||
describe "ConnectedUsersManager", ->
|
||||
describe("ConnectedUsersManager", function() {
|
||||
|
||||
beforeEach ->
|
||||
beforeEach(function() {
|
||||
|
||||
@settings =
|
||||
redis:
|
||||
realtime:
|
||||
key_schema:
|
||||
clientsInProject: ({project_id}) -> "clients_in_project:#{project_id}"
|
||||
connectedUser: ({project_id, client_id})-> "connected_user:#{project_id}:#{client_id}"
|
||||
@rClient =
|
||||
auth:->
|
||||
setex:sinon.stub()
|
||||
sadd:sinon.stub()
|
||||
get: sinon.stub()
|
||||
srem:sinon.stub()
|
||||
del:sinon.stub()
|
||||
smembers:sinon.stub()
|
||||
expire:sinon.stub()
|
||||
hset:sinon.stub()
|
||||
hgetall:sinon.stub()
|
||||
exec:sinon.stub()
|
||||
multi: => return @rClient
|
||||
tk.freeze(new Date())
|
||||
|
||||
@ConnectedUsersManager = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex":@settings
|
||||
"logger-sharelatex": log:->
|
||||
"redis-sharelatex": createClient:=>
|
||||
return @rClient
|
||||
@client_id = "32132132"
|
||||
@project_id = "dskjh2u21321"
|
||||
@user = {
|
||||
_id: "user-id-123"
|
||||
first_name: "Joe"
|
||||
last_name: "Bloggs"
|
||||
email: "joe@example.com"
|
||||
this.settings = {
|
||||
redis: {
|
||||
realtime: {
|
||||
key_schema: {
|
||||
clientsInProject({project_id}) { return `clients_in_project:${project_id}`; },
|
||||
connectedUser({project_id, client_id}){ return `connected_user:${project_id}:${client_id}`; }
|
||||
}
|
||||
@cursorData = { row: 12, column: 9, doc_id: '53c3b8c85fee64000023dc6e' }
|
||||
}
|
||||
}
|
||||
};
|
||||
this.rClient = {
|
||||
auth() {},
|
||||
setex:sinon.stub(),
|
||||
sadd:sinon.stub(),
|
||||
get: sinon.stub(),
|
||||
srem:sinon.stub(),
|
||||
del:sinon.stub(),
|
||||
smembers:sinon.stub(),
|
||||
expire:sinon.stub(),
|
||||
hset:sinon.stub(),
|
||||
hgetall:sinon.stub(),
|
||||
exec:sinon.stub(),
|
||||
multi: () => { return this.rClient; }
|
||||
};
|
||||
tk.freeze(new Date());
|
||||
|
||||
afterEach ->
|
||||
tk.reset()
|
||||
this.ConnectedUsersManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"settings-sharelatex":this.settings,
|
||||
"logger-sharelatex": { log() {}
|
||||
},
|
||||
"redis-sharelatex": { createClient:() => {
|
||||
return this.rClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
this.client_id = "32132132";
|
||||
this.project_id = "dskjh2u21321";
|
||||
this.user = {
|
||||
_id: "user-id-123",
|
||||
first_name: "Joe",
|
||||
last_name: "Bloggs",
|
||||
email: "joe@example.com"
|
||||
};
|
||||
return this.cursorData = { row: 12, column: 9, doc_id: '53c3b8c85fee64000023dc6e' };});
|
||||
|
||||
describe "updateUserPosition", ->
|
||||
beforeEach ->
|
||||
@rClient.exec.callsArgWith(0)
|
||||
afterEach(() => tk.reset());
|
||||
|
||||
it "should set a key with the date and give it a ttl", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "last_updated_at", Date.now()).should.equal true
|
||||
done()
|
||||
describe("updateUserPosition", function() {
|
||||
beforeEach(function() {
|
||||
return this.rClient.exec.callsArgWith(0);
|
||||
});
|
||||
|
||||
it "should set a key with the user_id", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "user_id", @user._id).should.equal true
|
||||
done()
|
||||
it("should set a key with the date and give it a ttl", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.hset.calledWith(`connected_user:${this.project_id}:${this.client_id}`, "last_updated_at", Date.now()).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should set a key with the first_name", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "first_name", @user.first_name).should.equal true
|
||||
done()
|
||||
it("should set a key with the user_id", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.hset.calledWith(`connected_user:${this.project_id}:${this.client_id}`, "user_id", this.user._id).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should set a key with the last_name", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "last_name", @user.last_name).should.equal true
|
||||
done()
|
||||
it("should set a key with the first_name", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.hset.calledWith(`connected_user:${this.project_id}:${this.client_id}`, "first_name", this.user.first_name).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should set a key with the email", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "email", @user.email).should.equal true
|
||||
done()
|
||||
it("should set a key with the last_name", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.hset.calledWith(`connected_user:${this.project_id}:${this.client_id}`, "last_name", this.user.last_name).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should push the client_id on to the project list", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.sadd.calledWith("clients_in_project:#{@project_id}", @client_id).should.equal true
|
||||
done()
|
||||
it("should set a key with the email", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.hset.calledWith(`connected_user:${this.project_id}:${this.client_id}`, "email", this.user.email).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should add a ttl to the project set so it stays clean", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.expire.calledWith("clients_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true
|
||||
done()
|
||||
it("should push the client_id on to the project list", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.sadd.calledWith(`clients_in_project:${this.project_id}`, this.client_id).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should add a ttl to the connected user so it stays clean", (done) ->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, null, (err)=>
|
||||
@rClient.expire.calledWith("connected_user:#{@project_id}:#{@client_id}", 60 * 15).should.equal true
|
||||
done()
|
||||
it("should add a ttl to the project set so it stays clean", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.expire.calledWith(`clients_in_project:${this.project_id}`, 24 * 4 * 60 * 60).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should set the cursor position when provided", (done)->
|
||||
@ConnectedUsersManager.updateUserPosition @project_id, @client_id, @user, @cursorData, (err)=>
|
||||
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "cursorData", JSON.stringify(@cursorData)).should.equal true
|
||||
done()
|
||||
it("should add a ttl to the connected user so it stays clean", function(done) {
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, null, err=> {
|
||||
this.rClient.expire.calledWith(`connected_user:${this.project_id}:${this.client_id}`, 60 * 15).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
describe "markUserAsDisconnected", ->
|
||||
beforeEach ->
|
||||
@rClient.exec.callsArgWith(0)
|
||||
return it("should set the cursor position when provided", function(done){
|
||||
return this.ConnectedUsersManager.updateUserPosition(this.project_id, this.client_id, this.user, this.cursorData, err=> {
|
||||
this.rClient.hset.calledWith(`connected_user:${this.project_id}:${this.client_id}`, "cursorData", JSON.stringify(this.cursorData)).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it "should remove the user from the set", (done)->
|
||||
@ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
|
||||
@rClient.srem.calledWith("clients_in_project:#{@project_id}", @client_id).should.equal true
|
||||
done()
|
||||
describe("markUserAsDisconnected", function() {
|
||||
beforeEach(function() {
|
||||
return this.rClient.exec.callsArgWith(0);
|
||||
});
|
||||
|
||||
it "should delete the connected_user string", (done)->
|
||||
@ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
|
||||
@rClient.del.calledWith("connected_user:#{@project_id}:#{@client_id}").should.equal true
|
||||
done()
|
||||
it("should remove the user from the set", function(done){
|
||||
return this.ConnectedUsersManager.markUserAsDisconnected(this.project_id, this.client_id, err=> {
|
||||
this.rClient.srem.calledWith(`clients_in_project:${this.project_id}`, this.client_id).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should add a ttl to the connected user set so it stays clean", (done)->
|
||||
@ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
|
||||
@rClient.expire.calledWith("clients_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true
|
||||
done()
|
||||
it("should delete the connected_user string", function(done){
|
||||
return this.ConnectedUsersManager.markUserAsDisconnected(this.project_id, this.client_id, err=> {
|
||||
this.rClient.del.calledWith(`connected_user:${this.project_id}:${this.client_id}`).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
describe "_getConnectedUser", ->
|
||||
return it("should add a ttl to the connected user set so it stays clean", function(done){
|
||||
return this.ConnectedUsersManager.markUserAsDisconnected(this.project_id, this.client_id, err=> {
|
||||
this.rClient.expire.calledWith(`clients_in_project:${this.project_id}`, 24 * 4 * 60 * 60).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it "should return a connected user if there is a user object", (done)->
|
||||
cursorData = JSON.stringify(cursorData:{row:1})
|
||||
@rClient.hgetall.callsArgWith(1, null, {connected_at:new Date(), user_id: @user._id, last_updated_at: "#{Date.now()}", cursorData})
|
||||
@ConnectedUsersManager._getConnectedUser @project_id, @client_id, (err, result)=>
|
||||
result.connected.should.equal true
|
||||
result.client_id.should.equal @client_id
|
||||
done()
|
||||
describe("_getConnectedUser", function() {
|
||||
|
||||
it "should return a not connected user if there is no object", (done)->
|
||||
@rClient.hgetall.callsArgWith(1, null, null)
|
||||
@ConnectedUsersManager._getConnectedUser @project_id, @client_id, (err, result)=>
|
||||
result.connected.should.equal false
|
||||
result.client_id.should.equal @client_id
|
||||
done()
|
||||
it("should return a connected user if there is a user object", function(done){
|
||||
const cursorData = JSON.stringify({cursorData:{row:1}});
|
||||
this.rClient.hgetall.callsArgWith(1, null, {connected_at:new Date(), user_id: this.user._id, last_updated_at: `${Date.now()}`, cursorData});
|
||||
return this.ConnectedUsersManager._getConnectedUser(this.project_id, this.client_id, (err, result)=> {
|
||||
result.connected.should.equal(true);
|
||||
result.client_id.should.equal(this.client_id);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should return a not connected user if there is an empty object", (done)->
|
||||
@rClient.hgetall.callsArgWith(1, null, {})
|
||||
@ConnectedUsersManager._getConnectedUser @project_id, @client_id, (err, result)=>
|
||||
result.connected.should.equal false
|
||||
result.client_id.should.equal @client_id
|
||||
done()
|
||||
it("should return a not connected user if there is no object", function(done){
|
||||
this.rClient.hgetall.callsArgWith(1, null, null);
|
||||
return this.ConnectedUsersManager._getConnectedUser(this.project_id, this.client_id, (err, result)=> {
|
||||
result.connected.should.equal(false);
|
||||
result.client_id.should.equal(this.client_id);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
describe "getConnectedUsers", ->
|
||||
return it("should return a not connected user if there is an empty object", function(done){
|
||||
this.rClient.hgetall.callsArgWith(1, null, {});
|
||||
return this.ConnectedUsersManager._getConnectedUser(this.project_id, this.client_id, (err, result)=> {
|
||||
result.connected.should.equal(false);
|
||||
result.client_id.should.equal(this.client_id);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach ->
|
||||
@users = ["1234", "5678", "9123", "8234"]
|
||||
@rClient.smembers.callsArgWith(1, null, @users)
|
||||
@ConnectedUsersManager._getConnectedUser = sinon.stub()
|
||||
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[0]).callsArgWith(2, null, {connected:true, client_age: 2, client_id:@users[0]})
|
||||
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[1]).callsArgWith(2, null, {connected:false, client_age: 1, client_id:@users[1]})
|
||||
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[2]).callsArgWith(2, null, {connected:true, client_age: 3, client_id:@users[2]})
|
||||
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[3]).callsArgWith(2, null, {connected:true, client_age: 11, client_id:@users[3]}) # connected but old
|
||||
return describe("getConnectedUsers", function() {
|
||||
|
||||
it "should only return the users in the list which are still in redis and recently updated", (done)->
|
||||
@ConnectedUsersManager.getConnectedUsers @project_id, (err, users)=>
|
||||
users.length.should.equal 2
|
||||
users[0].should.deep.equal {client_id:@users[0], client_age: 2, connected:true}
|
||||
users[1].should.deep.equal {client_id:@users[2], client_age: 3, connected:true}
|
||||
done()
|
||||
beforeEach(function() {
|
||||
this.users = ["1234", "5678", "9123", "8234"];
|
||||
this.rClient.smembers.callsArgWith(1, null, this.users);
|
||||
this.ConnectedUsersManager._getConnectedUser = sinon.stub();
|
||||
this.ConnectedUsersManager._getConnectedUser.withArgs(this.project_id, this.users[0]).callsArgWith(2, null, {connected:true, client_age: 2, client_id:this.users[0]});
|
||||
this.ConnectedUsersManager._getConnectedUser.withArgs(this.project_id, this.users[1]).callsArgWith(2, null, {connected:false, client_age: 1, client_id:this.users[1]});
|
||||
this.ConnectedUsersManager._getConnectedUser.withArgs(this.project_id, this.users[2]).callsArgWith(2, null, {connected:true, client_age: 3, client_id:this.users[2]});
|
||||
return this.ConnectedUsersManager._getConnectedUser.withArgs(this.project_id, this.users[3]).callsArgWith(2, null, {connected:true, client_age: 11, client_id:this.users[3]});
|
||||
}); // connected but old
|
||||
|
||||
return it("should only return the users in the list which are still in redis and recently updated", function(done){
|
||||
return this.ConnectedUsersManager.getConnectedUsers(this.project_id, (err, users)=> {
|
||||
users.length.should.equal(2);
|
||||
users[0].should.deep.equal({client_id:this.users[0], client_age: 2, connected:true});
|
||||
users[1].should.deep.equal({client_id:this.users[2], client_age: 3, connected:true});
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,153 +1,203 @@
|
|||
SandboxedModule = require('sandboxed-module')
|
||||
sinon = require('sinon')
|
||||
require('chai').should()
|
||||
modulePath = require('path').join __dirname, '../../../app/js/DocumentUpdaterController'
|
||||
MockClient = require "./helpers/MockClient"
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon');
|
||||
require('chai').should();
|
||||
const modulePath = require('path').join(__dirname, '../../../app/js/DocumentUpdaterController');
|
||||
const MockClient = require("./helpers/MockClient");
|
||||
|
||||
describe "DocumentUpdaterController", ->
|
||||
beforeEach ->
|
||||
@project_id = "project-id-123"
|
||||
@doc_id = "doc-id-123"
|
||||
@callback = sinon.stub()
|
||||
@io = { "mock": "socket.io" }
|
||||
@rclient = []
|
||||
@RoomEvents = { on: sinon.stub() }
|
||||
@EditorUpdatesController = SandboxedModule.require modulePath, requires:
|
||||
"logger-sharelatex": @logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() }
|
||||
"settings-sharelatex": @settings =
|
||||
redis:
|
||||
documentupdater:
|
||||
key_schema:
|
||||
pendingUpdates: ({doc_id}) -> "PendingUpdates:#{doc_id}"
|
||||
describe("DocumentUpdaterController", function() {
|
||||
beforeEach(function() {
|
||||
this.project_id = "project-id-123";
|
||||
this.doc_id = "doc-id-123";
|
||||
this.callback = sinon.stub();
|
||||
this.io = { "mock": "socket.io" };
|
||||
this.rclient = [];
|
||||
this.RoomEvents = { on: sinon.stub() };
|
||||
return this.EditorUpdatesController = SandboxedModule.require(modulePath, { requires: {
|
||||
"logger-sharelatex": (this.logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() }),
|
||||
"settings-sharelatex": (this.settings = {
|
||||
redis: {
|
||||
documentupdater: {
|
||||
key_schema: {
|
||||
pendingUpdates({doc_id}) { return `PendingUpdates:${doc_id}`; }
|
||||
}
|
||||
},
|
||||
pubsub: null
|
||||
"redis-sharelatex" : @redis =
|
||||
createClient: (name) =>
|
||||
@rclient.push(rclientStub = {name:name})
|
||||
return rclientStub
|
||||
"./SafeJsonParse": @SafeJsonParse =
|
||||
parse: (data, cb) => cb null, JSON.parse(data)
|
||||
"./EventLogger": @EventLogger = {checkEventOrder: sinon.stub()}
|
||||
"./HealthCheckManager": {check: sinon.stub()}
|
||||
"metrics-sharelatex": @metrics = {inc: sinon.stub()}
|
||||
"./RoomManager" : @RoomManager = { eventSource: sinon.stub().returns @RoomEvents}
|
||||
"./ChannelManager": @ChannelManager = {}
|
||||
}
|
||||
}),
|
||||
"redis-sharelatex" : (this.redis = {
|
||||
createClient: name => {
|
||||
let rclientStub;
|
||||
this.rclient.push(rclientStub = {name});
|
||||
return rclientStub;
|
||||
}
|
||||
}),
|
||||
"./SafeJsonParse": (this.SafeJsonParse =
|
||||
{parse: (data, cb) => cb(null, JSON.parse(data))}),
|
||||
"./EventLogger": (this.EventLogger = {checkEventOrder: sinon.stub()}),
|
||||
"./HealthCheckManager": {check: sinon.stub()},
|
||||
"metrics-sharelatex": (this.metrics = {inc: sinon.stub()}),
|
||||
"./RoomManager" : (this.RoomManager = { eventSource: sinon.stub().returns(this.RoomEvents)}),
|
||||
"./ChannelManager": (this.ChannelManager = {})
|
||||
}
|
||||
});});
|
||||
|
||||
describe "listenForUpdatesFromDocumentUpdater", ->
|
||||
beforeEach ->
|
||||
@rclient.length = 0 # clear any existing clients
|
||||
@EditorUpdatesController.rclientList = [@redis.createClient("first"), @redis.createClient("second")]
|
||||
@rclient[0].subscribe = sinon.stub()
|
||||
@rclient[0].on = sinon.stub()
|
||||
@rclient[1].subscribe = sinon.stub()
|
||||
@rclient[1].on = sinon.stub()
|
||||
@EditorUpdatesController.listenForUpdatesFromDocumentUpdater()
|
||||
describe("listenForUpdatesFromDocumentUpdater", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.length = 0; // clear any existing clients
|
||||
this.EditorUpdatesController.rclientList = [this.redis.createClient("first"), this.redis.createClient("second")];
|
||||
this.rclient[0].subscribe = sinon.stub();
|
||||
this.rclient[0].on = sinon.stub();
|
||||
this.rclient[1].subscribe = sinon.stub();
|
||||
this.rclient[1].on = sinon.stub();
|
||||
return this.EditorUpdatesController.listenForUpdatesFromDocumentUpdater();
|
||||
});
|
||||
|
||||
it "should subscribe to the doc-updater stream", ->
|
||||
@rclient[0].subscribe.calledWith("applied-ops").should.equal true
|
||||
it("should subscribe to the doc-updater stream", function() {
|
||||
return this.rclient[0].subscribe.calledWith("applied-ops").should.equal(true);
|
||||
});
|
||||
|
||||
it "should register a callback to handle updates", ->
|
||||
@rclient[0].on.calledWith("message").should.equal true
|
||||
it("should register a callback to handle updates", function() {
|
||||
return this.rclient[0].on.calledWith("message").should.equal(true);
|
||||
});
|
||||
|
||||
it "should subscribe to any additional doc-updater stream", ->
|
||||
@rclient[1].subscribe.calledWith("applied-ops").should.equal true
|
||||
@rclient[1].on.calledWith("message").should.equal true
|
||||
return it("should subscribe to any additional doc-updater stream", function() {
|
||||
this.rclient[1].subscribe.calledWith("applied-ops").should.equal(true);
|
||||
return this.rclient[1].on.calledWith("message").should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "_processMessageFromDocumentUpdater", ->
|
||||
describe "with bad JSON", ->
|
||||
beforeEach ->
|
||||
@SafeJsonParse.parse = sinon.stub().callsArgWith 1, new Error("oops")
|
||||
@EditorUpdatesController._processMessageFromDocumentUpdater @io, "applied-ops", "blah"
|
||||
describe("_processMessageFromDocumentUpdater", function() {
|
||||
describe("with bad JSON", function() {
|
||||
beforeEach(function() {
|
||||
this.SafeJsonParse.parse = sinon.stub().callsArgWith(1, new Error("oops"));
|
||||
return this.EditorUpdatesController._processMessageFromDocumentUpdater(this.io, "applied-ops", "blah");
|
||||
});
|
||||
|
||||
it "should log an error", ->
|
||||
@logger.error.called.should.equal true
|
||||
return it("should log an error", function() {
|
||||
return this.logger.error.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with update", ->
|
||||
beforeEach ->
|
||||
@message =
|
||||
doc_id: @doc_id
|
||||
describe("with update", function() {
|
||||
beforeEach(function() {
|
||||
this.message = {
|
||||
doc_id: this.doc_id,
|
||||
op: {t: "foo", p: 12}
|
||||
@EditorUpdatesController._applyUpdateFromDocumentUpdater = sinon.stub()
|
||||
@EditorUpdatesController._processMessageFromDocumentUpdater @io, "applied-ops", JSON.stringify(@message)
|
||||
};
|
||||
this.EditorUpdatesController._applyUpdateFromDocumentUpdater = sinon.stub();
|
||||
return this.EditorUpdatesController._processMessageFromDocumentUpdater(this.io, "applied-ops", JSON.stringify(this.message));
|
||||
});
|
||||
|
||||
it "should apply the update", ->
|
||||
@EditorUpdatesController._applyUpdateFromDocumentUpdater
|
||||
.calledWith(@io, @doc_id, @message.op)
|
||||
.should.equal true
|
||||
return it("should apply the update", function() {
|
||||
return this.EditorUpdatesController._applyUpdateFromDocumentUpdater
|
||||
.calledWith(this.io, this.doc_id, this.message.op)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with error", ->
|
||||
beforeEach ->
|
||||
@message =
|
||||
doc_id: @doc_id
|
||||
return describe("with error", function() {
|
||||
beforeEach(function() {
|
||||
this.message = {
|
||||
doc_id: this.doc_id,
|
||||
error: "Something went wrong"
|
||||
@EditorUpdatesController._processErrorFromDocumentUpdater = sinon.stub()
|
||||
@EditorUpdatesController._processMessageFromDocumentUpdater @io, "applied-ops", JSON.stringify(@message)
|
||||
};
|
||||
this.EditorUpdatesController._processErrorFromDocumentUpdater = sinon.stub();
|
||||
return this.EditorUpdatesController._processMessageFromDocumentUpdater(this.io, "applied-ops", JSON.stringify(this.message));
|
||||
});
|
||||
|
||||
it "should process the error", ->
|
||||
@EditorUpdatesController._processErrorFromDocumentUpdater
|
||||
.calledWith(@io, @doc_id, @message.error)
|
||||
.should.equal true
|
||||
return it("should process the error", function() {
|
||||
return this.EditorUpdatesController._processErrorFromDocumentUpdater
|
||||
.calledWith(this.io, this.doc_id, this.message.error)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "_applyUpdateFromDocumentUpdater", ->
|
||||
beforeEach ->
|
||||
@sourceClient = new MockClient()
|
||||
@otherClients = [new MockClient(), new MockClient()]
|
||||
@update =
|
||||
op: [ t: "foo", p: 12 ]
|
||||
meta: source: @sourceClient.publicId
|
||||
v: @version = 42
|
||||
doc: @doc_id
|
||||
@io.sockets =
|
||||
clients: sinon.stub().returns([@sourceClient, @otherClients..., @sourceClient]) # include a duplicate client
|
||||
describe("_applyUpdateFromDocumentUpdater", function() {
|
||||
beforeEach(function() {
|
||||
this.sourceClient = new MockClient();
|
||||
this.otherClients = [new MockClient(), new MockClient()];
|
||||
this.update = {
|
||||
op: [ {t: "foo", p: 12} ],
|
||||
meta: { source: this.sourceClient.publicId
|
||||
},
|
||||
v: (this.version = 42),
|
||||
doc: this.doc_id
|
||||
};
|
||||
return this.io.sockets =
|
||||
{clients: sinon.stub().returns([this.sourceClient, ...Array.from(this.otherClients), this.sourceClient])};
|
||||
}); // include a duplicate client
|
||||
|
||||
describe "normally", ->
|
||||
beforeEach ->
|
||||
@EditorUpdatesController._applyUpdateFromDocumentUpdater @io, @doc_id, @update
|
||||
describe("normally", function() {
|
||||
beforeEach(function() {
|
||||
return this.EditorUpdatesController._applyUpdateFromDocumentUpdater(this.io, this.doc_id, this.update);
|
||||
});
|
||||
|
||||
it "should send a version bump to the source client", ->
|
||||
@sourceClient.emit
|
||||
.calledWith("otUpdateApplied", v: @version, doc: @doc_id)
|
||||
.should.equal true
|
||||
@sourceClient.emit.calledOnce.should.equal true
|
||||
it("should send a version bump to the source client", function() {
|
||||
this.sourceClient.emit
|
||||
.calledWith("otUpdateApplied", {v: this.version, doc: this.doc_id})
|
||||
.should.equal(true);
|
||||
return this.sourceClient.emit.calledOnce.should.equal(true);
|
||||
});
|
||||
|
||||
it "should get the clients connected to the document", ->
|
||||
@io.sockets.clients
|
||||
.calledWith(@doc_id)
|
||||
.should.equal true
|
||||
it("should get the clients connected to the document", function() {
|
||||
return this.io.sockets.clients
|
||||
.calledWith(this.doc_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it "should send the full update to the other clients", ->
|
||||
for client in @otherClients
|
||||
return it("should send the full update to the other clients", function() {
|
||||
return Array.from(this.otherClients).map((client) =>
|
||||
client.emit
|
||||
.calledWith("otUpdateApplied", @update)
|
||||
.should.equal true
|
||||
.calledWith("otUpdateApplied", this.update)
|
||||
.should.equal(true));
|
||||
});
|
||||
});
|
||||
|
||||
describe "with a duplicate op", ->
|
||||
beforeEach ->
|
||||
@update.dup = true
|
||||
@EditorUpdatesController._applyUpdateFromDocumentUpdater @io, @doc_id, @update
|
||||
return describe("with a duplicate op", function() {
|
||||
beforeEach(function() {
|
||||
this.update.dup = true;
|
||||
return this.EditorUpdatesController._applyUpdateFromDocumentUpdater(this.io, this.doc_id, this.update);
|
||||
});
|
||||
|
||||
it "should send a version bump to the source client as usual", ->
|
||||
@sourceClient.emit
|
||||
.calledWith("otUpdateApplied", v: @version, doc: @doc_id)
|
||||
.should.equal true
|
||||
it("should send a version bump to the source client as usual", function() {
|
||||
return this.sourceClient.emit
|
||||
.calledWith("otUpdateApplied", {v: this.version, doc: this.doc_id})
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it "should not send anything to the other clients (they've already had the op)", ->
|
||||
for client in @otherClients
|
||||
return it("should not send anything to the other clients (they've already had the op)", function() {
|
||||
return Array.from(this.otherClients).map((client) =>
|
||||
client.emit
|
||||
.calledWith("otUpdateApplied")
|
||||
.should.equal false
|
||||
.should.equal(false));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe "_processErrorFromDocumentUpdater", ->
|
||||
beforeEach ->
|
||||
@clients = [new MockClient(), new MockClient()]
|
||||
@io.sockets =
|
||||
clients: sinon.stub().returns(@clients)
|
||||
@EditorUpdatesController._processErrorFromDocumentUpdater @io, @doc_id, "Something went wrong"
|
||||
return describe("_processErrorFromDocumentUpdater", function() {
|
||||
beforeEach(function() {
|
||||
this.clients = [new MockClient(), new MockClient()];
|
||||
this.io.sockets =
|
||||
{clients: sinon.stub().returns(this.clients)};
|
||||
return this.EditorUpdatesController._processErrorFromDocumentUpdater(this.io, this.doc_id, "Something went wrong");
|
||||
});
|
||||
|
||||
it "should log a warning", ->
|
||||
@logger.warn.called.should.equal true
|
||||
it("should log a warning", function() {
|
||||
return this.logger.warn.called.should.equal(true);
|
||||
});
|
||||
|
||||
it "should disconnect all clients in that document", ->
|
||||
@io.sockets.clients.calledWith(@doc_id).should.equal true
|
||||
for client in @clients
|
||||
client.disconnect.called.should.equal true
|
||||
return it("should disconnect all clients in that document", function() {
|
||||
this.io.sockets.clients.calledWith(this.doc_id).should.equal(true);
|
||||
return Array.from(this.clients).map((client) =>
|
||||
client.disconnect.called.should.equal(true));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,193 +1,260 @@
|
|||
require('chai').should()
|
||||
sinon = require("sinon")
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
path = require "path"
|
||||
modulePath = '../../../app/js/DocumentUpdaterManager'
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
require('chai').should();
|
||||
const sinon = require("sinon");
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const path = require("path");
|
||||
const modulePath = '../../../app/js/DocumentUpdaterManager';
|
||||
|
||||
describe 'DocumentUpdaterManager', ->
|
||||
beforeEach ->
|
||||
@project_id = "project-id-923"
|
||||
@doc_id = "doc-id-394"
|
||||
@lines = ["one", "two", "three"]
|
||||
@version = 42
|
||||
@settings =
|
||||
apis: documentupdater: url: "http://doc-updater.example.com"
|
||||
redis: documentupdater:
|
||||
key_schema:
|
||||
pendingUpdates: ({doc_id}) -> "PendingUpdates:#{doc_id}"
|
||||
describe('DocumentUpdaterManager', function() {
|
||||
beforeEach(function() {
|
||||
let Timer;
|
||||
this.project_id = "project-id-923";
|
||||
this.doc_id = "doc-id-394";
|
||||
this.lines = ["one", "two", "three"];
|
||||
this.version = 42;
|
||||
this.settings = {
|
||||
apis: { documentupdater: {url: "http://doc-updater.example.com"}
|
||||
},
|
||||
redis: { documentupdater: {
|
||||
key_schema: {
|
||||
pendingUpdates({doc_id}) { return `PendingUpdates:${doc_id}`; }
|
||||
}
|
||||
}
|
||||
},
|
||||
maxUpdateSize: 7 * 1024 * 1024
|
||||
@rclient = {auth:->}
|
||||
};
|
||||
this.rclient = {auth() {}};
|
||||
|
||||
@DocumentUpdaterManager = SandboxedModule.require modulePath,
|
||||
requires:
|
||||
'settings-sharelatex':@settings
|
||||
'logger-sharelatex': @logger = {log: sinon.stub(), error: sinon.stub(), warn: sinon.stub()}
|
||||
'request': @request = {}
|
||||
'redis-sharelatex' : createClient: () => @rclient
|
||||
'metrics-sharelatex': @Metrics =
|
||||
summary: sinon.stub()
|
||||
Timer: class Timer
|
||||
done: () ->
|
||||
globals:
|
||||
JSON: @JSON = Object.create(JSON) # avoid modifying JSON object directly
|
||||
return this.DocumentUpdaterManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'settings-sharelatex':this.settings,
|
||||
'logger-sharelatex': (this.logger = {log: sinon.stub(), error: sinon.stub(), warn: sinon.stub()}),
|
||||
'request': (this.request = {}),
|
||||
'redis-sharelatex' : { createClient: () => this.rclient
|
||||
},
|
||||
'metrics-sharelatex': (this.Metrics = {
|
||||
summary: sinon.stub(),
|
||||
Timer: (Timer = class Timer {
|
||||
done() {}
|
||||
})
|
||||
})
|
||||
},
|
||||
globals: {
|
||||
JSON: (this.JSON = Object.create(JSON))
|
||||
}
|
||||
}
|
||||
);
|
||||
}); // avoid modifying JSON object directly
|
||||
|
||||
describe "getDocument", ->
|
||||
beforeEach ->
|
||||
@callback = sinon.stub()
|
||||
describe("getDocument", function() {
|
||||
beforeEach(function() {
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
|
||||
describe "successfully", ->
|
||||
beforeEach ->
|
||||
@body = JSON.stringify
|
||||
lines: @lines
|
||||
version: @version
|
||||
ops: @ops = ["mock-op-1", "mock-op-2"]
|
||||
ranges: @ranges = {"mock": "ranges"}
|
||||
@fromVersion = 2
|
||||
@request.get = sinon.stub().callsArgWith(1, null, {statusCode: 200}, @body)
|
||||
@DocumentUpdaterManager.getDocument @project_id, @doc_id, @fromVersion, @callback
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
this.body = JSON.stringify({
|
||||
lines: this.lines,
|
||||
version: this.version,
|
||||
ops: (this.ops = ["mock-op-1", "mock-op-2"]),
|
||||
ranges: (this.ranges = {"mock": "ranges"})});
|
||||
this.fromVersion = 2;
|
||||
this.request.get = sinon.stub().callsArgWith(1, null, {statusCode: 200}, this.body);
|
||||
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
||||
});
|
||||
|
||||
it 'should get the document from the document updater', ->
|
||||
url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}/doc/#{@doc_id}?fromVersion=#{@fromVersion}"
|
||||
@request.get.calledWith(url).should.equal true
|
||||
it('should get the document from the document updater', function() {
|
||||
const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}/doc/${this.doc_id}?fromVersion=${this.fromVersion}`;
|
||||
return this.request.get.calledWith(url).should.equal(true);
|
||||
});
|
||||
|
||||
it "should call the callback with the lines, version, ranges and ops", ->
|
||||
@callback.calledWith(null, @lines, @version, @ranges, @ops).should.equal true
|
||||
return it("should call the callback with the lines, version, ranges and ops", function() {
|
||||
return this.callback.calledWith(null, this.lines, this.version, this.ranges, this.ops).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when the document updater API returns an error", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, @error = new Error("something went wrong"), null, null)
|
||||
@DocumentUpdaterManager.getDocument @project_id, @doc_id, @fromVersion, @callback
|
||||
describe("when the document updater API returns an error", function() {
|
||||
beforeEach(function() {
|
||||
this.request.get = sinon.stub().callsArgWith(1, (this.error = new Error("something went wrong")), null, null);
|
||||
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
||||
});
|
||||
|
||||
it "should return an error to the callback", ->
|
||||
@callback.calledWith(@error).should.equal true
|
||||
return it("should return an error to the callback", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
[404, 422].forEach (statusCode) ->
|
||||
describe "when the document updater returns a #{statusCode} status code", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, null, { statusCode }, "")
|
||||
@DocumentUpdaterManager.getDocument @project_id, @doc_id, @fromVersion, @callback
|
||||
[404, 422].forEach(statusCode => describe(`when the document updater returns a ${statusCode} status code`, function() {
|
||||
beforeEach(function() {
|
||||
this.request.get = sinon.stub().callsArgWith(1, null, { statusCode }, "");
|
||||
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
||||
});
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.called.should.equal(true)
|
||||
err = @callback.getCall(0).args[0]
|
||||
err.should.have.property('statusCode', statusCode)
|
||||
err.should.have.property('message', "doc updater could not load requested ops")
|
||||
@logger.error.called.should.equal(false)
|
||||
@logger.warn.called.should.equal(true)
|
||||
return it("should return the callback with an error", function() {
|
||||
this.callback.called.should.equal(true);
|
||||
const err = this.callback.getCall(0).args[0];
|
||||
err.should.have.property('statusCode', statusCode);
|
||||
err.should.have.property('message', "doc updater could not load requested ops");
|
||||
this.logger.error.called.should.equal(false);
|
||||
return this.logger.warn.called.should.equal(true);
|
||||
});
|
||||
}));
|
||||
|
||||
describe "when the document updater returns a failure error code", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "")
|
||||
@DocumentUpdaterManager.getDocument @project_id, @doc_id, @fromVersion, @callback
|
||||
return describe("when the document updater returns a failure error code", function() {
|
||||
beforeEach(function() {
|
||||
this.request.get = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "");
|
||||
return this.DocumentUpdaterManager.getDocument(this.project_id, this.doc_id, this.fromVersion, this.callback);
|
||||
});
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.called.should.equal(true)
|
||||
err = @callback.getCall(0).args[0]
|
||||
err.should.have.property('statusCode', 500)
|
||||
err.should.have.property('message', "doc updater returned a non-success status code: 500")
|
||||
@logger.error.called.should.equal(true)
|
||||
return it("should return the callback with an error", function() {
|
||||
this.callback.called.should.equal(true);
|
||||
const err = this.callback.getCall(0).args[0];
|
||||
err.should.have.property('statusCode', 500);
|
||||
err.should.have.property('message', "doc updater returned a non-success status code: 500");
|
||||
return this.logger.error.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'flushProjectToMongoAndDelete', ->
|
||||
beforeEach ->
|
||||
@callback = sinon.stub()
|
||||
describe('flushProjectToMongoAndDelete', function() {
|
||||
beforeEach(function() {
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
|
||||
describe "successfully", ->
|
||||
beforeEach ->
|
||||
@request.del = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "")
|
||||
@DocumentUpdaterManager.flushProjectToMongoAndDelete @project_id, @callback
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
this.request.del = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "");
|
||||
return this.DocumentUpdaterManager.flushProjectToMongoAndDelete(this.project_id, this.callback);
|
||||
});
|
||||
|
||||
it 'should delete the project from the document updater', ->
|
||||
url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}?background=true"
|
||||
@request.del.calledWith(url).should.equal true
|
||||
it('should delete the project from the document updater', function() {
|
||||
const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}?background=true`;
|
||||
return this.request.del.calledWith(url).should.equal(true);
|
||||
});
|
||||
|
||||
it "should call the callback with no error", ->
|
||||
@callback.calledWith(null).should.equal true
|
||||
return it("should call the callback with no error", function() {
|
||||
return this.callback.calledWith(null).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when the document updater API returns an error", ->
|
||||
beforeEach ->
|
||||
@request.del = sinon.stub().callsArgWith(1, @error = new Error("something went wrong"), null, null)
|
||||
@DocumentUpdaterManager.flushProjectToMongoAndDelete @project_id, @callback
|
||||
describe("when the document updater API returns an error", function() {
|
||||
beforeEach(function() {
|
||||
this.request.del = sinon.stub().callsArgWith(1, (this.error = new Error("something went wrong")), null, null);
|
||||
return this.DocumentUpdaterManager.flushProjectToMongoAndDelete(this.project_id, this.callback);
|
||||
});
|
||||
|
||||
it "should return an error to the callback", ->
|
||||
@callback.calledWith(@error).should.equal true
|
||||
return it("should return an error to the callback", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when the document updater returns a failure error code", ->
|
||||
beforeEach ->
|
||||
@request.del = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "")
|
||||
@DocumentUpdaterManager.flushProjectToMongoAndDelete @project_id, @callback
|
||||
return describe("when the document updater returns a failure error code", function() {
|
||||
beforeEach(function() {
|
||||
this.request.del = sinon.stub().callsArgWith(1, null, { statusCode: 500 }, "");
|
||||
return this.DocumentUpdaterManager.flushProjectToMongoAndDelete(this.project_id, this.callback);
|
||||
});
|
||||
|
||||
it "should return the callback with an error", ->
|
||||
@callback.called.should.equal(true)
|
||||
err = @callback.getCall(0).args[0]
|
||||
err.should.have.property('statusCode', 500)
|
||||
err.should.have.property('message', "document updater returned a failure status code: 500")
|
||||
return it("should return the callback with an error", function() {
|
||||
this.callback.called.should.equal(true);
|
||||
const err = this.callback.getCall(0).args[0];
|
||||
err.should.have.property('statusCode', 500);
|
||||
return err.should.have.property('message', "document updater returned a failure status code: 500");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'queueChange', ->
|
||||
beforeEach ->
|
||||
@change = {
|
||||
return describe('queueChange', function() {
|
||||
beforeEach(function() {
|
||||
this.change = {
|
||||
"doc":"1234567890",
|
||||
"op":["d":"test", "p":345]
|
||||
"op":[{"d":"test", "p":345}],
|
||||
"v": 789
|
||||
}
|
||||
@rclient.rpush = sinon.stub().yields()
|
||||
@callback = sinon.stub()
|
||||
};
|
||||
this.rclient.rpush = sinon.stub().yields();
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
|
||||
describe "successfully", ->
|
||||
beforeEach ->
|
||||
@DocumentUpdaterManager.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
||||
});
|
||||
|
||||
it "should push the change", ->
|
||||
@rclient.rpush
|
||||
.calledWith("PendingUpdates:#{@doc_id}", JSON.stringify(@change))
|
||||
.should.equal true
|
||||
it("should push the change", function() {
|
||||
return this.rclient.rpush
|
||||
.calledWith(`PendingUpdates:${this.doc_id}`, JSON.stringify(this.change))
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it "should notify the doc updater of the change via the pending-updates-list queue", ->
|
||||
@rclient.rpush
|
||||
.calledWith("pending-updates-list", "#{@project_id}:#{@doc_id}")
|
||||
.should.equal true
|
||||
return it("should notify the doc updater of the change via the pending-updates-list queue", function() {
|
||||
return this.rclient.rpush
|
||||
.calledWith("pending-updates-list", `${this.project_id}:${this.doc_id}`)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with error talking to redis during rpush", ->
|
||||
beforeEach ->
|
||||
@rclient.rpush = sinon.stub().yields(new Error("something went wrong"))
|
||||
@DocumentUpdaterManager.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
describe("with error talking to redis during rpush", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.rpush = sinon.stub().yields(new Error("something went wrong"));
|
||||
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
||||
});
|
||||
|
||||
it "should return an error", ->
|
||||
@callback.calledWithExactly(sinon.match(Error)).should.equal true
|
||||
return it("should return an error", function() {
|
||||
return this.callback.calledWithExactly(sinon.match(Error)).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with null byte corruption", ->
|
||||
beforeEach ->
|
||||
@JSON.stringify = () -> return '["bad bytes! \u0000 <- here"]'
|
||||
@DocumentUpdaterManager.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
describe("with null byte corruption", function() {
|
||||
beforeEach(function() {
|
||||
this.JSON.stringify = () => '["bad bytes! \u0000 <- here"]';
|
||||
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
||||
});
|
||||
|
||||
it "should return an error", ->
|
||||
@callback.calledWithExactly(sinon.match(Error)).should.equal true
|
||||
it("should return an error", function() {
|
||||
return this.callback.calledWithExactly(sinon.match(Error)).should.equal(true);
|
||||
});
|
||||
|
||||
it "should not push the change onto the pending-updates-list queue", ->
|
||||
@rclient.rpush.called.should.equal false
|
||||
return it("should not push the change onto the pending-updates-list queue", function() {
|
||||
return this.rclient.rpush.called.should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when the update is too large", ->
|
||||
beforeEach ->
|
||||
@change = {op: {p: 12,t: "update is too large".repeat(1024 * 400)}}
|
||||
@DocumentUpdaterManager.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
describe("when the update is too large", function() {
|
||||
beforeEach(function() {
|
||||
this.change = {op: {p: 12,t: "update is too large".repeat(1024 * 400)}};
|
||||
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
||||
});
|
||||
|
||||
it "should return an error", ->
|
||||
@callback.calledWithExactly(sinon.match(Error)).should.equal true
|
||||
it("should return an error", function() {
|
||||
return this.callback.calledWithExactly(sinon.match(Error)).should.equal(true);
|
||||
});
|
||||
|
||||
it "should add the size to the error", ->
|
||||
@callback.args[0][0].updateSize.should.equal 7782422
|
||||
it("should add the size to the error", function() {
|
||||
return this.callback.args[0][0].updateSize.should.equal(7782422);
|
||||
});
|
||||
|
||||
it "should not push the change onto the pending-updates-list queue", ->
|
||||
@rclient.rpush.called.should.equal false
|
||||
return it("should not push the change onto the pending-updates-list queue", function() {
|
||||
return this.rclient.rpush.called.should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with invalid keys", ->
|
||||
beforeEach ->
|
||||
@change = {
|
||||
"op":["d":"test", "p":345]
|
||||
"version": 789 # not a valid key
|
||||
}
|
||||
@DocumentUpdaterManager.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
return describe("with invalid keys", function() {
|
||||
beforeEach(function() {
|
||||
this.change = {
|
||||
"op":[{"d":"test", "p":345}],
|
||||
"version": 789 // not a valid key
|
||||
};
|
||||
return this.DocumentUpdaterManager.queueChange(this.project_id, this.doc_id, this.change, this.callback);
|
||||
});
|
||||
|
||||
it "should remove the invalid keys from the change", ->
|
||||
@rclient.rpush
|
||||
.calledWith("PendingUpdates:#{@doc_id}", JSON.stringify({op:@change.op}))
|
||||
.should.equal true
|
||||
return it("should remove the invalid keys from the change", function() {
|
||||
return this.rclient.rpush
|
||||
.calledWith(`PendingUpdates:${this.doc_id}`, JSON.stringify({op:this.change.op}))
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,81 +1,113 @@
|
|||
should = require('chai').should()
|
||||
sinon = require "sinon"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
path = require "path"
|
||||
modulePath = path.join __dirname, "../../../app/js/DrainManager"
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const should = require('chai').should();
|
||||
const sinon = require("sinon");
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const path = require("path");
|
||||
const modulePath = path.join(__dirname, "../../../app/js/DrainManager");
|
||||
|
||||
describe "DrainManager", ->
|
||||
beforeEach ->
|
||||
@DrainManager = SandboxedModule.require modulePath, requires:
|
||||
"logger-sharelatex": @logger = log: sinon.stub()
|
||||
@io =
|
||||
sockets:
|
||||
describe("DrainManager", function() {
|
||||
beforeEach(function() {
|
||||
this.DrainManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"logger-sharelatex": (this.logger = {log: sinon.stub()})
|
||||
}
|
||||
}
|
||||
);
|
||||
return this.io = {
|
||||
sockets: {
|
||||
clients: sinon.stub()
|
||||
|
||||
describe "startDrainTimeWindow", ->
|
||||
beforeEach ->
|
||||
@clients = []
|
||||
for i in [0..5399]
|
||||
@clients[i] = {
|
||||
id: i
|
||||
emit: sinon.stub()
|
||||
}
|
||||
@io.sockets.clients.returns @clients
|
||||
@DrainManager.startDrain = sinon.stub()
|
||||
};
|
||||
});
|
||||
|
||||
it "should set a drain rate fast enough", (done)->
|
||||
@DrainManager.startDrainTimeWindow(@io, 9)
|
||||
@DrainManager.startDrain.calledWith(@io, 10).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
describe "reconnectNClients", ->
|
||||
beforeEach ->
|
||||
@clients = []
|
||||
for i in [0..9]
|
||||
@clients[i] = {
|
||||
id: i
|
||||
describe("startDrainTimeWindow", function() {
|
||||
beforeEach(function() {
|
||||
this.clients = [];
|
||||
for (let i = 0; i <= 5399; i++) {
|
||||
this.clients[i] = {
|
||||
id: i,
|
||||
emit: sinon.stub()
|
||||
};
|
||||
}
|
||||
@io.sockets.clients.returns @clients
|
||||
this.io.sockets.clients.returns(this.clients);
|
||||
return this.DrainManager.startDrain = sinon.stub();
|
||||
});
|
||||
|
||||
describe "after first pass", ->
|
||||
beforeEach ->
|
||||
@DrainManager.reconnectNClients(@io, 3)
|
||||
return it("should set a drain rate fast enough", function(done){
|
||||
this.DrainManager.startDrainTimeWindow(this.io, 9);
|
||||
this.DrainManager.startDrain.calledWith(this.io, 10).should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should reconnect the first 3 clients", ->
|
||||
for i in [0..2]
|
||||
@clients[i].emit.calledWith("reconnectGracefully").should.equal true
|
||||
|
||||
it "should not reconnect any more clients", ->
|
||||
for i in [3..9]
|
||||
@clients[i].emit.calledWith("reconnectGracefully").should.equal false
|
||||
return describe("reconnectNClients", function() {
|
||||
beforeEach(function() {
|
||||
this.clients = [];
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
this.clients[i] = {
|
||||
id: i,
|
||||
emit: sinon.stub()
|
||||
};
|
||||
}
|
||||
return this.io.sockets.clients.returns(this.clients);
|
||||
});
|
||||
|
||||
describe "after second pass", ->
|
||||
beforeEach ->
|
||||
@DrainManager.reconnectNClients(@io, 3)
|
||||
return describe("after first pass", function() {
|
||||
beforeEach(function() {
|
||||
return this.DrainManager.reconnectNClients(this.io, 3);
|
||||
});
|
||||
|
||||
it "should reconnect the next 3 clients", ->
|
||||
for i in [3..5]
|
||||
@clients[i].emit.calledWith("reconnectGracefully").should.equal true
|
||||
it("should reconnect the first 3 clients", function() {
|
||||
return [0, 1, 2].map((i) =>
|
||||
this.clients[i].emit.calledWith("reconnectGracefully").should.equal(true));
|
||||
});
|
||||
|
||||
it "should not reconnect any more clients", ->
|
||||
for i in [6..9]
|
||||
@clients[i].emit.calledWith("reconnectGracefully").should.equal false
|
||||
it("should not reconnect any more clients", function() {
|
||||
return [3, 4, 5, 6, 7, 8, 9].map((i) =>
|
||||
this.clients[i].emit.calledWith("reconnectGracefully").should.equal(false));
|
||||
});
|
||||
|
||||
it "should not reconnect the first 3 clients again", ->
|
||||
for i in [0..2]
|
||||
@clients[i].emit.calledOnce.should.equal true
|
||||
return describe("after second pass", function() {
|
||||
beforeEach(function() {
|
||||
return this.DrainManager.reconnectNClients(this.io, 3);
|
||||
});
|
||||
|
||||
describe "after final pass", ->
|
||||
beforeEach ->
|
||||
@DrainManager.reconnectNClients(@io, 100)
|
||||
it("should reconnect the next 3 clients", function() {
|
||||
return [3, 4, 5].map((i) =>
|
||||
this.clients[i].emit.calledWith("reconnectGracefully").should.equal(true));
|
||||
});
|
||||
|
||||
it "should not reconnect the first 6 clients again", ->
|
||||
for i in [0..5]
|
||||
@clients[i].emit.calledOnce.should.equal true
|
||||
it("should not reconnect any more clients", function() {
|
||||
return [6, 7, 8, 9].map((i) =>
|
||||
this.clients[i].emit.calledWith("reconnectGracefully").should.equal(false));
|
||||
});
|
||||
|
||||
it "should log out that it reached the end", ->
|
||||
@logger.log
|
||||
it("should not reconnect the first 3 clients again", function() {
|
||||
return [0, 1, 2].map((i) =>
|
||||
this.clients[i].emit.calledOnce.should.equal(true));
|
||||
});
|
||||
|
||||
return describe("after final pass", function() {
|
||||
beforeEach(function() {
|
||||
return this.DrainManager.reconnectNClients(this.io, 100);
|
||||
});
|
||||
|
||||
it("should not reconnect the first 6 clients again", function() {
|
||||
return [0, 1, 2, 3, 4, 5].map((i) =>
|
||||
this.clients[i].emit.calledOnce.should.equal(true));
|
||||
});
|
||||
|
||||
return it("should log out that it reached the end", function() {
|
||||
return this.logger.log
|
||||
.calledWith("All clients have been told to reconnectGracefully")
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,76 +1,101 @@
|
|||
require('chai').should()
|
||||
expect = require("chai").expect
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
modulePath = '../../../app/js/EventLogger'
|
||||
sinon = require("sinon")
|
||||
tk = require "timekeeper"
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
require('chai').should();
|
||||
const {
|
||||
expect
|
||||
} = require("chai");
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const modulePath = '../../../app/js/EventLogger';
|
||||
const sinon = require("sinon");
|
||||
const tk = require("timekeeper");
|
||||
|
||||
describe 'EventLogger', ->
|
||||
beforeEach ->
|
||||
@start = Date.now()
|
||||
tk.freeze(new Date(@start))
|
||||
@EventLogger = SandboxedModule.require modulePath, requires:
|
||||
"logger-sharelatex": @logger = {error: sinon.stub(), warn: sinon.stub()}
|
||||
"metrics-sharelatex": @metrics = {inc: sinon.stub()}
|
||||
@channel = "applied-ops"
|
||||
@id_1 = "random-hostname:abc-1"
|
||||
@message_1 = "message-1"
|
||||
@id_2 = "random-hostname:abc-2"
|
||||
@message_2 = "message-2"
|
||||
describe('EventLogger', function() {
|
||||
beforeEach(function() {
|
||||
this.start = Date.now();
|
||||
tk.freeze(new Date(this.start));
|
||||
this.EventLogger = SandboxedModule.require(modulePath, { requires: {
|
||||
"logger-sharelatex": (this.logger = {error: sinon.stub(), warn: sinon.stub()}),
|
||||
"metrics-sharelatex": (this.metrics = {inc: sinon.stub()})
|
||||
}
|
||||
});
|
||||
this.channel = "applied-ops";
|
||||
this.id_1 = "random-hostname:abc-1";
|
||||
this.message_1 = "message-1";
|
||||
this.id_2 = "random-hostname:abc-2";
|
||||
return this.message_2 = "message-2";
|
||||
});
|
||||
|
||||
afterEach ->
|
||||
tk.reset()
|
||||
afterEach(() => tk.reset());
|
||||
|
||||
describe 'checkEventOrder', ->
|
||||
return describe('checkEventOrder', function() {
|
||||
|
||||
describe 'when the events are in order', ->
|
||||
beforeEach ->
|
||||
@EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
@status = @EventLogger.checkEventOrder(@channel, @id_2, @message_2)
|
||||
describe('when the events are in order', function() {
|
||||
beforeEach(function() {
|
||||
this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
return this.status = this.EventLogger.checkEventOrder(this.channel, this.id_2, this.message_2);
|
||||
});
|
||||
|
||||
it 'should accept events in order', ->
|
||||
expect(@status).to.be.undefined
|
||||
it('should accept events in order', function() {
|
||||
return expect(this.status).to.be.undefined;
|
||||
});
|
||||
|
||||
it 'should increment the valid event metric', ->
|
||||
@metrics.inc.calledWith("event.#{@channel}.valid", 1)
|
||||
.should.equal.true
|
||||
return it('should increment the valid event metric', function() {
|
||||
return this.metrics.inc.calledWith(`event.${this.channel}.valid`, 1)
|
||||
.should.equal.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe 'when there is a duplicate events', ->
|
||||
beforeEach ->
|
||||
@EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
@status = @EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
describe('when there is a duplicate events', function() {
|
||||
beforeEach(function() {
|
||||
this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
return this.status = this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
});
|
||||
|
||||
it 'should return "duplicate" for the same event', ->
|
||||
expect(@status).to.equal "duplicate"
|
||||
it('should return "duplicate" for the same event', function() {
|
||||
return expect(this.status).to.equal("duplicate");
|
||||
});
|
||||
|
||||
it 'should increment the duplicate event metric', ->
|
||||
@metrics.inc.calledWith("event.#{@channel}.duplicate", 1)
|
||||
.should.equal.true
|
||||
return it('should increment the duplicate event metric', function() {
|
||||
return this.metrics.inc.calledWith(`event.${this.channel}.duplicate`, 1)
|
||||
.should.equal.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe 'when there are out of order events', ->
|
||||
beforeEach ->
|
||||
@EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
@EventLogger.checkEventOrder(@channel, @id_2, @message_2)
|
||||
@status = @EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
describe('when there are out of order events', function() {
|
||||
beforeEach(function() {
|
||||
this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
this.EventLogger.checkEventOrder(this.channel, this.id_2, this.message_2);
|
||||
return this.status = this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
});
|
||||
|
||||
it 'should return "out-of-order" for the event', ->
|
||||
expect(@status).to.equal "out-of-order"
|
||||
it('should return "out-of-order" for the event', function() {
|
||||
return expect(this.status).to.equal("out-of-order");
|
||||
});
|
||||
|
||||
it 'should increment the out-of-order event metric', ->
|
||||
@metrics.inc.calledWith("event.#{@channel}.out-of-order", 1)
|
||||
.should.equal.true
|
||||
return it('should increment the out-of-order event metric', function() {
|
||||
return this.metrics.inc.calledWith(`event.${this.channel}.out-of-order`, 1)
|
||||
.should.equal.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe 'after MAX_STALE_TIME_IN_MS', ->
|
||||
it 'should flush old entries', ->
|
||||
@EventLogger.MAX_EVENTS_BEFORE_CLEAN = 10
|
||||
@EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
for i in [1..8]
|
||||
status = @EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
expect(status).to.equal "duplicate"
|
||||
# the next event should flush the old entries aboce
|
||||
@EventLogger.MAX_STALE_TIME_IN_MS=1000
|
||||
tk.freeze(new Date(@start + 5 * 1000))
|
||||
# because we flushed the entries this should not be a duplicate
|
||||
@EventLogger.checkEventOrder(@channel, 'other-1', @message_2)
|
||||
status = @EventLogger.checkEventOrder(@channel, @id_1, @message_1)
|
||||
expect(status).to.be.undefined
|
||||
return describe('after MAX_STALE_TIME_IN_MS', () => it('should flush old entries', function() {
|
||||
let status;
|
||||
this.EventLogger.MAX_EVENTS_BEFORE_CLEAN = 10;
|
||||
this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
status = this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
expect(status).to.equal("duplicate");
|
||||
}
|
||||
// the next event should flush the old entries aboce
|
||||
this.EventLogger.MAX_STALE_TIME_IN_MS=1000;
|
||||
tk.freeze(new Date(this.start + (5 * 1000)));
|
||||
// because we flushed the entries this should not be a duplicate
|
||||
this.EventLogger.checkEventOrder(this.channel, 'other-1', this.message_2);
|
||||
status = this.EventLogger.checkEventOrder(this.channel, this.id_1, this.message_1);
|
||||
return expect(status).to.be.undefined;
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -1,288 +1,359 @@
|
|||
chai = require('chai')
|
||||
expect = chai.expect
|
||||
should = chai.should()
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../app/js/RoomManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const chai = require('chai');
|
||||
const {
|
||||
expect
|
||||
} = chai;
|
||||
const should = chai.should();
|
||||
const sinon = require("sinon");
|
||||
const modulePath = "../../../app/js/RoomManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
|
||||
describe 'RoomManager', ->
|
||||
beforeEach ->
|
||||
@project_id = "project-id-123"
|
||||
@doc_id = "doc-id-456"
|
||||
@other_doc_id = "doc-id-789"
|
||||
@client = {namespace: {name: ''}, id: "first-client"}
|
||||
@RoomManager = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex": @settings = {}
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), warn: sinon.stub(), error: sinon.stub() }
|
||||
"metrics-sharelatex": @metrics = { gauge: sinon.stub() }
|
||||
@RoomManager._clientsInRoom = sinon.stub()
|
||||
@RoomManager._clientAlreadyInRoom = sinon.stub()
|
||||
@RoomEvents = @RoomManager.eventSource()
|
||||
sinon.spy(@RoomEvents, 'emit')
|
||||
sinon.spy(@RoomEvents, 'once')
|
||||
describe('RoomManager', function() {
|
||||
beforeEach(function() {
|
||||
this.project_id = "project-id-123";
|
||||
this.doc_id = "doc-id-456";
|
||||
this.other_doc_id = "doc-id-789";
|
||||
this.client = {namespace: {name: ''}, id: "first-client"};
|
||||
this.RoomManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"settings-sharelatex": (this.settings = {}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), warn: sinon.stub(), error: sinon.stub() }),
|
||||
"metrics-sharelatex": (this.metrics = { gauge: sinon.stub() })
|
||||
}
|
||||
});
|
||||
this.RoomManager._clientsInRoom = sinon.stub();
|
||||
this.RoomManager._clientAlreadyInRoom = sinon.stub();
|
||||
this.RoomEvents = this.RoomManager.eventSource();
|
||||
sinon.spy(this.RoomEvents, 'emit');
|
||||
return sinon.spy(this.RoomEvents, 'once');
|
||||
});
|
||||
|
||||
describe "emitOnCompletion", ->
|
||||
describe "when a subscribe errors", ->
|
||||
afterEach () ->
|
||||
process.removeListener("unhandledRejection", @onUnhandled)
|
||||
describe("emitOnCompletion", () => describe("when a subscribe errors", function() {
|
||||
afterEach(function() {
|
||||
return process.removeListener("unhandledRejection", this.onUnhandled);
|
||||
});
|
||||
|
||||
beforeEach (done) ->
|
||||
@onUnhandled = (error) =>
|
||||
@unhandledError = error
|
||||
done(new Error("unhandledRejection: #{error.message}"))
|
||||
process.on("unhandledRejection", @onUnhandled)
|
||||
beforeEach(function(done) {
|
||||
this.onUnhandled = error => {
|
||||
this.unhandledError = error;
|
||||
return done(new Error(`unhandledRejection: ${error.message}`));
|
||||
};
|
||||
process.on("unhandledRejection", this.onUnhandled);
|
||||
|
||||
reject = undefined
|
||||
subscribePromise = new Promise((_, r) -> reject = r)
|
||||
promises = [subscribePromise]
|
||||
eventName = "project-subscribed-123"
|
||||
@RoomEvents.once eventName, () ->
|
||||
setTimeout(done, 100)
|
||||
@RoomManager.emitOnCompletion(promises, eventName)
|
||||
setTimeout(() -> reject(new Error("subscribe failed")))
|
||||
let reject = undefined;
|
||||
const subscribePromise = new Promise((_, r) => reject = r);
|
||||
const promises = [subscribePromise];
|
||||
const eventName = "project-subscribed-123";
|
||||
this.RoomEvents.once(eventName, () => setTimeout(done, 100));
|
||||
this.RoomManager.emitOnCompletion(promises, eventName);
|
||||
return setTimeout(() => reject(new Error("subscribe failed")));
|
||||
});
|
||||
|
||||
it "should keep going", () ->
|
||||
expect(@unhandledError).to.not.exist
|
||||
return it("should keep going", function() {
|
||||
return expect(this.unhandledError).to.not.exist;
|
||||
});
|
||||
}));
|
||||
|
||||
describe "joinProject", ->
|
||||
describe("joinProject", function() {
|
||||
|
||||
describe "when the project room is empty", ->
|
||||
describe("when the project room is empty", function() {
|
||||
|
||||
beforeEach (done) ->
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @project_id)
|
||||
.onFirstCall().returns(0)
|
||||
@client.join = sinon.stub()
|
||||
@callback = sinon.stub()
|
||||
@RoomEvents.on 'project-active', (id) =>
|
||||
setTimeout () =>
|
||||
@RoomEvents.emit "project-subscribed-#{id}"
|
||||
, 100
|
||||
@RoomManager.joinProject @client, @project_id, (err) =>
|
||||
@callback(err)
|
||||
done()
|
||||
beforeEach(function(done) {
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.project_id)
|
||||
.onFirstCall().returns(0);
|
||||
this.client.join = sinon.stub();
|
||||
this.callback = sinon.stub();
|
||||
this.RoomEvents.on('project-active', id => {
|
||||
return setTimeout(() => {
|
||||
return this.RoomEvents.emit(`project-subscribed-${id}`);
|
||||
}
|
||||
, 100);
|
||||
});
|
||||
return this.RoomManager.joinProject(this.client, this.project_id, err => {
|
||||
this.callback(err);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should emit a 'project-active' event with the id", ->
|
||||
@RoomEvents.emit.calledWithExactly('project-active', @project_id).should.equal true
|
||||
it("should emit a 'project-active' event with the id", function() {
|
||||
return this.RoomEvents.emit.calledWithExactly('project-active', this.project_id).should.equal(true);
|
||||
});
|
||||
|
||||
it "should listen for the 'project-subscribed-id' event", ->
|
||||
@RoomEvents.once.calledWith("project-subscribed-#{@project_id}").should.equal true
|
||||
it("should listen for the 'project-subscribed-id' event", function() {
|
||||
return this.RoomEvents.once.calledWith(`project-subscribed-${this.project_id}`).should.equal(true);
|
||||
});
|
||||
|
||||
it "should join the room using the id", ->
|
||||
@client.join.calledWithExactly(@project_id).should.equal true
|
||||
return it("should join the room using the id", function() {
|
||||
return this.client.join.calledWithExactly(this.project_id).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when there are other clients in the project room", ->
|
||||
return describe("when there are other clients in the project room", function() {
|
||||
|
||||
beforeEach ->
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @project_id)
|
||||
beforeEach(function() {
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.project_id)
|
||||
.onFirstCall().returns(123)
|
||||
.onSecondCall().returns(124)
|
||||
@client.join = sinon.stub()
|
||||
@RoomManager.joinProject @client, @project_id
|
||||
.onSecondCall().returns(124);
|
||||
this.client.join = sinon.stub();
|
||||
return this.RoomManager.joinProject(this.client, this.project_id);
|
||||
});
|
||||
|
||||
it "should join the room using the id", ->
|
||||
@client.join.called.should.equal true
|
||||
it("should join the room using the id", function() {
|
||||
return this.client.join.called.should.equal(true);
|
||||
});
|
||||
|
||||
it "should not emit any events", ->
|
||||
@RoomEvents.emit.called.should.equal false
|
||||
return it("should not emit any events", function() {
|
||||
return this.RoomEvents.emit.called.should.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "joinDoc", ->
|
||||
describe("joinDoc", function() {
|
||||
|
||||
describe "when the doc room is empty", ->
|
||||
describe("when the doc room is empty", function() {
|
||||
|
||||
beforeEach (done) ->
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.onFirstCall().returns(0)
|
||||
@client.join = sinon.stub()
|
||||
@callback = sinon.stub()
|
||||
@RoomEvents.on 'doc-active', (id) =>
|
||||
setTimeout () =>
|
||||
@RoomEvents.emit "doc-subscribed-#{id}"
|
||||
, 100
|
||||
@RoomManager.joinDoc @client, @doc_id, (err) =>
|
||||
@callback(err)
|
||||
done()
|
||||
beforeEach(function(done) {
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.onFirstCall().returns(0);
|
||||
this.client.join = sinon.stub();
|
||||
this.callback = sinon.stub();
|
||||
this.RoomEvents.on('doc-active', id => {
|
||||
return setTimeout(() => {
|
||||
return this.RoomEvents.emit(`doc-subscribed-${id}`);
|
||||
}
|
||||
, 100);
|
||||
});
|
||||
return this.RoomManager.joinDoc(this.client, this.doc_id, err => {
|
||||
this.callback(err);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should emit a 'doc-active' event with the id", ->
|
||||
@RoomEvents.emit.calledWithExactly('doc-active', @doc_id).should.equal true
|
||||
it("should emit a 'doc-active' event with the id", function() {
|
||||
return this.RoomEvents.emit.calledWithExactly('doc-active', this.doc_id).should.equal(true);
|
||||
});
|
||||
|
||||
it "should listen for the 'doc-subscribed-id' event", ->
|
||||
@RoomEvents.once.calledWith("doc-subscribed-#{@doc_id}").should.equal true
|
||||
it("should listen for the 'doc-subscribed-id' event", function() {
|
||||
return this.RoomEvents.once.calledWith(`doc-subscribed-${this.doc_id}`).should.equal(true);
|
||||
});
|
||||
|
||||
it "should join the room using the id", ->
|
||||
@client.join.calledWithExactly(@doc_id).should.equal true
|
||||
return it("should join the room using the id", function() {
|
||||
return this.client.join.calledWithExactly(this.doc_id).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when there are other clients in the doc room", ->
|
||||
return describe("when there are other clients in the doc room", function() {
|
||||
|
||||
beforeEach ->
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
beforeEach(function() {
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.onFirstCall().returns(123)
|
||||
.onSecondCall().returns(124)
|
||||
@client.join = sinon.stub()
|
||||
@RoomManager.joinDoc @client, @doc_id
|
||||
.onSecondCall().returns(124);
|
||||
this.client.join = sinon.stub();
|
||||
return this.RoomManager.joinDoc(this.client, this.doc_id);
|
||||
});
|
||||
|
||||
it "should join the room using the id", ->
|
||||
@client.join.called.should.equal true
|
||||
it("should join the room using the id", function() {
|
||||
return this.client.join.called.should.equal(true);
|
||||
});
|
||||
|
||||
it "should not emit any events", ->
|
||||
@RoomEvents.emit.called.should.equal false
|
||||
return it("should not emit any events", function() {
|
||||
return this.RoomEvents.emit.called.should.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe "leaveDoc", ->
|
||||
describe("leaveDoc", function() {
|
||||
|
||||
describe "when doc room will be empty after this client has left", ->
|
||||
describe("when doc room will be empty after this client has left", function() {
|
||||
|
||||
beforeEach ->
|
||||
@RoomManager._clientAlreadyInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.returns(true)
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
beforeEach(function() {
|
||||
this.RoomManager._clientAlreadyInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.returns(true);
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.onCall(0).returns(0);
|
||||
this.client.leave = sinon.stub();
|
||||
return this.RoomManager.leaveDoc(this.client, this.doc_id);
|
||||
});
|
||||
|
||||
it("should leave the room using the id", function() {
|
||||
return this.client.leave.calledWithExactly(this.doc_id).should.equal(true);
|
||||
});
|
||||
|
||||
return it("should emit a 'doc-empty' event with the id", function() {
|
||||
return this.RoomEvents.emit.calledWithExactly('doc-empty', this.doc_id).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("when there are other clients in the doc room", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.RoomManager._clientAlreadyInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.returns(true);
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.onCall(0).returns(123);
|
||||
this.client.leave = sinon.stub();
|
||||
return this.RoomManager.leaveDoc(this.client, this.doc_id);
|
||||
});
|
||||
|
||||
it("should leave the room using the id", function() {
|
||||
return this.client.leave.calledWithExactly(this.doc_id).should.equal(true);
|
||||
});
|
||||
|
||||
return it("should not emit any events", function() {
|
||||
return this.RoomEvents.emit.called.should.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
return describe("when the client is not in the doc room", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.RoomManager._clientAlreadyInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.returns(false);
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.onCall(0).returns(0);
|
||||
this.client.leave = sinon.stub();
|
||||
return this.RoomManager.leaveDoc(this.client, this.doc_id);
|
||||
});
|
||||
|
||||
it("should not leave the room", function() {
|
||||
return this.client.leave.called.should.equal(false);
|
||||
});
|
||||
|
||||
return it("should not emit any events", function() {
|
||||
return this.RoomEvents.emit.called.should.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return describe("leaveProjectAndDocs", () => describe("when the client is connected to the project and multiple docs", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.RoomManager._roomsClientIsIn = sinon.stub().returns([this.project_id, this.doc_id, this.other_doc_id]);
|
||||
this.client.join = sinon.stub();
|
||||
return this.client.leave = sinon.stub();
|
||||
});
|
||||
|
||||
describe("when this is the only client connected", function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
// first call is for the join,
|
||||
// second for the leave
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.onCall(0).returns(0)
|
||||
@client.leave = sinon.stub()
|
||||
@RoomManager.leaveDoc @client, @doc_id
|
||||
|
||||
it "should leave the room using the id", ->
|
||||
@client.leave.calledWithExactly(@doc_id).should.equal true
|
||||
|
||||
it "should emit a 'doc-empty' event with the id", ->
|
||||
@RoomEvents.emit.calledWithExactly('doc-empty', @doc_id).should.equal true
|
||||
|
||||
|
||||
describe "when there are other clients in the doc room", ->
|
||||
|
||||
beforeEach ->
|
||||
@RoomManager._clientAlreadyInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.returns(true)
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.onCall(0).returns(123)
|
||||
@client.leave = sinon.stub()
|
||||
@RoomManager.leaveDoc @client, @doc_id
|
||||
|
||||
it "should leave the room using the id", ->
|
||||
@client.leave.calledWithExactly(@doc_id).should.equal true
|
||||
|
||||
it "should not emit any events", ->
|
||||
@RoomEvents.emit.called.should.equal false
|
||||
|
||||
describe "when the client is not in the doc room", ->
|
||||
|
||||
beforeEach ->
|
||||
@RoomManager._clientAlreadyInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.returns(false)
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.onCall(1).returns(0);
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.other_doc_id)
|
||||
.onCall(0).returns(0)
|
||||
@client.leave = sinon.stub()
|
||||
@RoomManager.leaveDoc @client, @doc_id
|
||||
|
||||
it "should not leave the room", ->
|
||||
@client.leave.called.should.equal false
|
||||
|
||||
it "should not emit any events", ->
|
||||
@RoomEvents.emit.called.should.equal false
|
||||
|
||||
|
||||
describe "leaveProjectAndDocs", ->
|
||||
|
||||
describe "when the client is connected to the project and multiple docs", ->
|
||||
|
||||
beforeEach ->
|
||||
@RoomManager._roomsClientIsIn = sinon.stub().returns [@project_id, @doc_id, @other_doc_id]
|
||||
@client.join = sinon.stub()
|
||||
@client.leave = sinon.stub()
|
||||
|
||||
describe "when this is the only client connected", ->
|
||||
|
||||
beforeEach (done) ->
|
||||
# first call is for the join,
|
||||
# second for the leave
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.onCall(1).returns(0);
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.project_id)
|
||||
.onCall(0).returns(0)
|
||||
.onCall(1).returns(0)
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @other_doc_id)
|
||||
.onCall(0).returns(0)
|
||||
.onCall(1).returns(0)
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @project_id)
|
||||
.onCall(0).returns(0)
|
||||
.onCall(1).returns(0)
|
||||
@RoomManager._clientAlreadyInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.onCall(1).returns(0);
|
||||
this.RoomManager._clientAlreadyInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.returns(true)
|
||||
.withArgs(@client, @other_doc_id)
|
||||
.withArgs(this.client, this.other_doc_id)
|
||||
.returns(true)
|
||||
.withArgs(@client, @project_id)
|
||||
.returns(true)
|
||||
@RoomEvents.on 'project-active', (id) =>
|
||||
setTimeout () =>
|
||||
@RoomEvents.emit "project-subscribed-#{id}"
|
||||
, 100
|
||||
@RoomEvents.on 'doc-active', (id) =>
|
||||
setTimeout () =>
|
||||
@RoomEvents.emit "doc-subscribed-#{id}"
|
||||
, 100
|
||||
# put the client in the rooms
|
||||
@RoomManager.joinProject @client, @project_id, () =>
|
||||
@RoomManager.joinDoc @client, @doc_id, () =>
|
||||
@RoomManager.joinDoc @client, @other_doc_id, () =>
|
||||
# now leave the project
|
||||
@RoomManager.leaveProjectAndDocs @client
|
||||
done()
|
||||
.withArgs(this.client, this.project_id)
|
||||
.returns(true);
|
||||
this.RoomEvents.on('project-active', id => {
|
||||
return setTimeout(() => {
|
||||
return this.RoomEvents.emit(`project-subscribed-${id}`);
|
||||
}
|
||||
, 100);
|
||||
});
|
||||
this.RoomEvents.on('doc-active', id => {
|
||||
return setTimeout(() => {
|
||||
return this.RoomEvents.emit(`doc-subscribed-${id}`);
|
||||
}
|
||||
, 100);
|
||||
});
|
||||
// put the client in the rooms
|
||||
return this.RoomManager.joinProject(this.client, this.project_id, () => {
|
||||
return this.RoomManager.joinDoc(this.client, this.doc_id, () => {
|
||||
return this.RoomManager.joinDoc(this.client, this.other_doc_id, () => {
|
||||
// now leave the project
|
||||
this.RoomManager.leaveProjectAndDocs(this.client);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it "should leave all the docs", ->
|
||||
@client.leave.calledWithExactly(@doc_id).should.equal true
|
||||
@client.leave.calledWithExactly(@other_doc_id).should.equal true
|
||||
it("should leave all the docs", function() {
|
||||
this.client.leave.calledWithExactly(this.doc_id).should.equal(true);
|
||||
return this.client.leave.calledWithExactly(this.other_doc_id).should.equal(true);
|
||||
});
|
||||
|
||||
it "should leave the project", ->
|
||||
@client.leave.calledWithExactly(@project_id).should.equal true
|
||||
it("should leave the project", function() {
|
||||
return this.client.leave.calledWithExactly(this.project_id).should.equal(true);
|
||||
});
|
||||
|
||||
it "should emit a 'doc-empty' event with the id for each doc", ->
|
||||
@RoomEvents.emit.calledWithExactly('doc-empty', @doc_id).should.equal true
|
||||
@RoomEvents.emit.calledWithExactly('doc-empty', @other_doc_id).should.equal true
|
||||
it("should emit a 'doc-empty' event with the id for each doc", function() {
|
||||
this.RoomEvents.emit.calledWithExactly('doc-empty', this.doc_id).should.equal(true);
|
||||
return this.RoomEvents.emit.calledWithExactly('doc-empty', this.other_doc_id).should.equal(true);
|
||||
});
|
||||
|
||||
it "should emit a 'project-empty' event with the id for the project", ->
|
||||
@RoomEvents.emit.calledWithExactly('project-empty', @project_id).should.equal true
|
||||
return it("should emit a 'project-empty' event with the id for the project", function() {
|
||||
return this.RoomEvents.emit.calledWithExactly('project-empty', this.project_id).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when other clients are still connected", ->
|
||||
return describe("when other clients are still connected", function() {
|
||||
|
||||
beforeEach ->
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
beforeEach(function() {
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.onFirstCall().returns(123)
|
||||
.onSecondCall().returns(122)
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @other_doc_id)
|
||||
.onSecondCall().returns(122);
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.other_doc_id)
|
||||
.onFirstCall().returns(123)
|
||||
.onSecondCall().returns(122)
|
||||
@RoomManager._clientsInRoom
|
||||
.withArgs(@client, @project_id)
|
||||
.onSecondCall().returns(122);
|
||||
this.RoomManager._clientsInRoom
|
||||
.withArgs(this.client, this.project_id)
|
||||
.onFirstCall().returns(123)
|
||||
.onSecondCall().returns(122)
|
||||
@RoomManager._clientAlreadyInRoom
|
||||
.withArgs(@client, @doc_id)
|
||||
.onSecondCall().returns(122);
|
||||
this.RoomManager._clientAlreadyInRoom
|
||||
.withArgs(this.client, this.doc_id)
|
||||
.returns(true)
|
||||
.withArgs(@client, @other_doc_id)
|
||||
.withArgs(this.client, this.other_doc_id)
|
||||
.returns(true)
|
||||
.withArgs(@client, @project_id)
|
||||
.returns(true)
|
||||
@RoomManager.leaveProjectAndDocs @client
|
||||
.withArgs(this.client, this.project_id)
|
||||
.returns(true);
|
||||
return this.RoomManager.leaveProjectAndDocs(this.client);
|
||||
});
|
||||
|
||||
it "should leave all the docs", ->
|
||||
@client.leave.calledWithExactly(@doc_id).should.equal true
|
||||
@client.leave.calledWithExactly(@other_doc_id).should.equal true
|
||||
it("should leave all the docs", function() {
|
||||
this.client.leave.calledWithExactly(this.doc_id).should.equal(true);
|
||||
return this.client.leave.calledWithExactly(this.other_doc_id).should.equal(true);
|
||||
});
|
||||
|
||||
it "should leave the project", ->
|
||||
@client.leave.calledWithExactly(@project_id).should.equal true
|
||||
it("should leave the project", function() {
|
||||
return this.client.leave.calledWithExactly(this.project_id).should.equal(true);
|
||||
});
|
||||
|
||||
it "should not emit any events", ->
|
||||
@RoomEvents.emit.called.should.equal false
|
||||
return it("should not emit any events", function() {
|
||||
return this.RoomEvents.emit.called.should.equal(false);
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
|
@ -1,34 +1,51 @@
|
|||
require('chai').should()
|
||||
expect = require("chai").expect
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
modulePath = '../../../app/js/SafeJsonParse'
|
||||
sinon = require("sinon")
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
require('chai').should();
|
||||
const {
|
||||
expect
|
||||
} = require("chai");
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const modulePath = '../../../app/js/SafeJsonParse';
|
||||
const sinon = require("sinon");
|
||||
|
||||
describe 'SafeJsonParse', ->
|
||||
beforeEach ->
|
||||
@SafeJsonParse = SandboxedModule.require modulePath, requires:
|
||||
"settings-sharelatex": @Settings = {
|
||||
describe('SafeJsonParse', function() {
|
||||
beforeEach(function() {
|
||||
return this.SafeJsonParse = SandboxedModule.require(modulePath, { requires: {
|
||||
"settings-sharelatex": (this.Settings = {
|
||||
maxUpdateSize: 16 * 1024
|
||||
}),
|
||||
"logger-sharelatex": (this.logger = {error: sinon.stub()})
|
||||
}
|
||||
"logger-sharelatex": @logger = {error: sinon.stub()}
|
||||
});});
|
||||
|
||||
describe "parse", ->
|
||||
it "should parse documents correctly", (done) ->
|
||||
@SafeJsonParse.parse '{"foo": "bar"}', (error, parsed) ->
|
||||
expect(parsed).to.deep.equal {foo: "bar"}
|
||||
done()
|
||||
return describe("parse", function() {
|
||||
it("should parse documents correctly", function(done) {
|
||||
return this.SafeJsonParse.parse('{"foo": "bar"}', function(error, parsed) {
|
||||
expect(parsed).to.deep.equal({foo: "bar"});
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should return an error on bad data", (done) ->
|
||||
@SafeJsonParse.parse 'blah', (error, parsed) ->
|
||||
expect(error).to.exist
|
||||
done()
|
||||
it("should return an error on bad data", function(done) {
|
||||
return this.SafeJsonParse.parse('blah', function(error, parsed) {
|
||||
expect(error).to.exist;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it "should return an error on oversized data", (done) ->
|
||||
# we have a 2k overhead on top of max size
|
||||
big_blob = Array(16*1024).join("A")
|
||||
data = "{\"foo\": \"#{big_blob}\"}"
|
||||
@Settings.maxUpdateSize = 2 * 1024
|
||||
@SafeJsonParse.parse data, (error, parsed) =>
|
||||
@logger.error.called.should.equal true
|
||||
expect(error).to.exist
|
||||
done()
|
||||
return it("should return an error on oversized data", function(done) {
|
||||
// we have a 2k overhead on top of max size
|
||||
const big_blob = Array(16*1024).join("A");
|
||||
const data = `{\"foo\": \"${big_blob}\"}`;
|
||||
this.Settings.maxUpdateSize = 2 * 1024;
|
||||
return this.SafeJsonParse.parse(data, (error, parsed) => {
|
||||
this.logger.error.called.should.equal(true);
|
||||
expect(error).to.exist;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,126 +1,170 @@
|
|||
{EventEmitter} = require('events')
|
||||
{expect} = require('chai')
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
modulePath = '../../../app/js/SessionSockets'
|
||||
sinon = require('sinon')
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const {EventEmitter} = require('events');
|
||||
const {expect} = require('chai');
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const modulePath = '../../../app/js/SessionSockets';
|
||||
const sinon = require('sinon');
|
||||
|
||||
describe 'SessionSockets', ->
|
||||
before ->
|
||||
@SessionSocketsModule = SandboxedModule.require modulePath
|
||||
@io = new EventEmitter()
|
||||
@id1 = Math.random().toString()
|
||||
@id2 = Math.random().toString()
|
||||
redisResponses =
|
||||
error: [new Error('Redis: something went wrong'), null]
|
||||
describe('SessionSockets', function() {
|
||||
before(function() {
|
||||
this.SessionSocketsModule = SandboxedModule.require(modulePath);
|
||||
this.io = new EventEmitter();
|
||||
this.id1 = Math.random().toString();
|
||||
this.id2 = Math.random().toString();
|
||||
const redisResponses = {
|
||||
error: [new Error('Redis: something went wrong'), null],
|
||||
unknownId: [null, null]
|
||||
redisResponses[@id1] = [null, {user: {_id: '123'}}]
|
||||
redisResponses[@id2] = [null, {user: {_id: 'abc'}}]
|
||||
};
|
||||
redisResponses[this.id1] = [null, {user: {_id: '123'}}];
|
||||
redisResponses[this.id2] = [null, {user: {_id: 'abc'}}];
|
||||
|
||||
@sessionStore =
|
||||
get: sinon.stub().callsFake (id, fn) ->
|
||||
fn.apply(null, redisResponses[id])
|
||||
@cookieParser = (req, res, next) ->
|
||||
req.signedCookies = req._signedCookies
|
||||
next()
|
||||
@SessionSockets = @SessionSocketsModule(@io, @sessionStore, @cookieParser, 'ol.sid')
|
||||
@checkSocket = (socket, fn) =>
|
||||
@SessionSockets.once('connection', fn)
|
||||
@io.emit('connection', socket)
|
||||
this.sessionStore = {
|
||||
get: sinon.stub().callsFake((id, fn) => fn.apply(null, redisResponses[id]))
|
||||
};
|
||||
this.cookieParser = function(req, res, next) {
|
||||
req.signedCookies = req._signedCookies;
|
||||
return next();
|
||||
};
|
||||
this.SessionSockets = this.SessionSocketsModule(this.io, this.sessionStore, this.cookieParser, 'ol.sid');
|
||||
return this.checkSocket = (socket, fn) => {
|
||||
this.SessionSockets.once('connection', fn);
|
||||
return this.io.emit('connection', socket);
|
||||
};
|
||||
});
|
||||
|
||||
describe 'without cookies', ->
|
||||
before ->
|
||||
@socket = {handshake: {}}
|
||||
describe('without cookies', function() {
|
||||
before(function() {
|
||||
return this.socket = {handshake: {}};});
|
||||
|
||||
it 'should return a lookup error', (done) ->
|
||||
@checkSocket @socket, (error) ->
|
||||
expect(error).to.exist
|
||||
expect(error.message).to.equal('could not look up session by key')
|
||||
done()
|
||||
it('should return a lookup error', function(done) {
|
||||
return this.checkSocket(this.socket, function(error) {
|
||||
expect(error).to.exist;
|
||||
expect(error.message).to.equal('could not look up session by key');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should not query redis', (done) ->
|
||||
@checkSocket @socket, () =>
|
||||
expect(@sessionStore.get.called).to.equal(false)
|
||||
done()
|
||||
return it('should not query redis', function(done) {
|
||||
return this.checkSocket(this.socket, () => {
|
||||
expect(this.sessionStore.get.called).to.equal(false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'with a different cookie', ->
|
||||
before ->
|
||||
@socket = {handshake: {_signedCookies: {other: 1}}}
|
||||
describe('with a different cookie', function() {
|
||||
before(function() {
|
||||
return this.socket = {handshake: {_signedCookies: {other: 1}}};});
|
||||
|
||||
it 'should return a lookup error', (done) ->
|
||||
@checkSocket @socket, (error) ->
|
||||
expect(error).to.exist
|
||||
expect(error.message).to.equal('could not look up session by key')
|
||||
done()
|
||||
it('should return a lookup error', function(done) {
|
||||
return this.checkSocket(this.socket, function(error) {
|
||||
expect(error).to.exist;
|
||||
expect(error.message).to.equal('could not look up session by key');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should not query redis', (done) ->
|
||||
@checkSocket @socket, () =>
|
||||
expect(@sessionStore.get.called).to.equal(false)
|
||||
done()
|
||||
return it('should not query redis', function(done) {
|
||||
return this.checkSocket(this.socket, () => {
|
||||
expect(this.sessionStore.get.called).to.equal(false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'with a valid cookie and a failing session lookup', ->
|
||||
before ->
|
||||
@socket = {handshake: {_signedCookies: {'ol.sid': 'error'}}}
|
||||
describe('with a valid cookie and a failing session lookup', function() {
|
||||
before(function() {
|
||||
return this.socket = {handshake: {_signedCookies: {'ol.sid': 'error'}}};});
|
||||
|
||||
it 'should query redis', (done) ->
|
||||
@checkSocket @socket, () =>
|
||||
expect(@sessionStore.get.called).to.equal(true)
|
||||
done()
|
||||
it('should query redis', function(done) {
|
||||
return this.checkSocket(this.socket, () => {
|
||||
expect(this.sessionStore.get.called).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should return a redis error', (done) ->
|
||||
@checkSocket @socket, (error) ->
|
||||
expect(error).to.exist
|
||||
expect(error.message).to.equal('Redis: something went wrong')
|
||||
done()
|
||||
return it('should return a redis error', function(done) {
|
||||
return this.checkSocket(this.socket, function(error) {
|
||||
expect(error).to.exist;
|
||||
expect(error.message).to.equal('Redis: something went wrong');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'with a valid cookie and no matching session', ->
|
||||
before ->
|
||||
@socket = {handshake: {_signedCookies: {'ol.sid': 'unknownId'}}}
|
||||
describe('with a valid cookie and no matching session', function() {
|
||||
before(function() {
|
||||
return this.socket = {handshake: {_signedCookies: {'ol.sid': 'unknownId'}}};});
|
||||
|
||||
it 'should query redis', (done) ->
|
||||
@checkSocket @socket, () =>
|
||||
expect(@sessionStore.get.called).to.equal(true)
|
||||
done()
|
||||
it('should query redis', function(done) {
|
||||
return this.checkSocket(this.socket, () => {
|
||||
expect(this.sessionStore.get.called).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should return a lookup error', (done) ->
|
||||
@checkSocket @socket, (error) ->
|
||||
expect(error).to.exist
|
||||
expect(error.message).to.equal('could not look up session by key')
|
||||
done()
|
||||
return it('should return a lookup error', function(done) {
|
||||
return this.checkSocket(this.socket, function(error) {
|
||||
expect(error).to.exist;
|
||||
expect(error.message).to.equal('could not look up session by key');
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'with a valid cookie and a matching session', ->
|
||||
before ->
|
||||
@socket = {handshake: {_signedCookies: {'ol.sid': @id1}}}
|
||||
describe('with a valid cookie and a matching session', function() {
|
||||
before(function() {
|
||||
return this.socket = {handshake: {_signedCookies: {'ol.sid': this.id1}}};});
|
||||
|
||||
it 'should query redis', (done) ->
|
||||
@checkSocket @socket, () =>
|
||||
expect(@sessionStore.get.called).to.equal(true)
|
||||
done()
|
||||
it('should query redis', function(done) {
|
||||
return this.checkSocket(this.socket, () => {
|
||||
expect(this.sessionStore.get.called).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should not return an error', (done) ->
|
||||
@checkSocket @socket, (error) ->
|
||||
expect(error).to.not.exist
|
||||
done()
|
||||
it('should not return an error', function(done) {
|
||||
return this.checkSocket(this.socket, function(error) {
|
||||
expect(error).to.not.exist;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should return the session', (done) ->
|
||||
@checkSocket @socket, (error, s, session) ->
|
||||
expect(session).to.deep.equal({user: {_id: '123'}})
|
||||
done()
|
||||
return it('should return the session', function(done) {
|
||||
return this.checkSocket(this.socket, function(error, s, session) {
|
||||
expect(session).to.deep.equal({user: {_id: '123'}});
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe 'with a different valid cookie and matching session', ->
|
||||
before ->
|
||||
@socket = {handshake: {_signedCookies: {'ol.sid': @id2}}}
|
||||
return describe('with a different valid cookie and matching session', function() {
|
||||
before(function() {
|
||||
return this.socket = {handshake: {_signedCookies: {'ol.sid': this.id2}}};});
|
||||
|
||||
it 'should query redis', (done) ->
|
||||
@checkSocket @socket, () =>
|
||||
expect(@sessionStore.get.called).to.equal(true)
|
||||
done()
|
||||
it('should query redis', function(done) {
|
||||
return this.checkSocket(this.socket, () => {
|
||||
expect(this.sessionStore.get.called).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should not return an error', (done) ->
|
||||
@checkSocket @socket, (error) ->
|
||||
expect(error).to.not.exist
|
||||
done()
|
||||
it('should not return an error', function(done) {
|
||||
return this.checkSocket(this.socket, function(error) {
|
||||
expect(error).to.not.exist;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it 'should return the other session', (done) ->
|
||||
@checkSocket @socket, (error, s, session) ->
|
||||
expect(session).to.deep.equal({user: {_id: 'abc'}})
|
||||
done()
|
||||
return it('should return the other session', function(done) {
|
||||
return this.checkSocket(this.socket, function(error, s, session) {
|
||||
expect(session).to.deep.equal({user: {_id: 'abc'}});
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,84 +1,111 @@
|
|||
chai = require('chai')
|
||||
should = chai.should()
|
||||
sinon = require("sinon")
|
||||
modulePath = "../../../app/js/WebApiManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
{ CodedError } = require('../../../app/js/Errors')
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const chai = require('chai');
|
||||
const should = chai.should();
|
||||
const sinon = require("sinon");
|
||||
const modulePath = "../../../app/js/WebApiManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const { CodedError } = require('../../../app/js/Errors');
|
||||
|
||||
describe 'WebApiManager', ->
|
||||
beforeEach ->
|
||||
@project_id = "project-id-123"
|
||||
@user_id = "user-id-123"
|
||||
@user = {_id: @user_id}
|
||||
@callback = sinon.stub()
|
||||
@WebApiManager = SandboxedModule.require modulePath, requires:
|
||||
"request": @request = {}
|
||||
"settings-sharelatex": @settings =
|
||||
apis:
|
||||
web:
|
||||
url: "http://web.example.com"
|
||||
user: "username"
|
||||
describe('WebApiManager', function() {
|
||||
beforeEach(function() {
|
||||
this.project_id = "project-id-123";
|
||||
this.user_id = "user-id-123";
|
||||
this.user = {_id: this.user_id};
|
||||
this.callback = sinon.stub();
|
||||
return this.WebApiManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"request": (this.request = {}),
|
||||
"settings-sharelatex": (this.settings = {
|
||||
apis: {
|
||||
web: {
|
||||
url: "http://web.example.com",
|
||||
user: "username",
|
||||
pass: "password"
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
||||
}
|
||||
}
|
||||
}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() })
|
||||
}
|
||||
});});
|
||||
|
||||
describe "joinProject", ->
|
||||
describe "successfully", ->
|
||||
beforeEach ->
|
||||
@response = {
|
||||
project: { name: "Test project" }
|
||||
return describe("joinProject", function() {
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
this.response = {
|
||||
project: { name: "Test project" },
|
||||
privilegeLevel: "owner",
|
||||
isRestrictedUser: true
|
||||
}
|
||||
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 200}, @response)
|
||||
@WebApiManager.joinProject @project_id, @user, @callback
|
||||
};
|
||||
this.request.post = sinon.stub().callsArgWith(1, null, {statusCode: 200}, this.response);
|
||||
return this.WebApiManager.joinProject(this.project_id, this.user, this.callback);
|
||||
});
|
||||
|
||||
it "should send a request to web to join the project", ->
|
||||
@request.post
|
||||
it("should send a request to web to join the project", function() {
|
||||
return this.request.post
|
||||
.calledWith({
|
||||
url: "#{@settings.apis.web.url}/project/#{@project_id}/join"
|
||||
qs:
|
||||
user_id: @user_id
|
||||
auth:
|
||||
user: @settings.apis.web.user
|
||||
pass: @settings.apis.web.pass
|
||||
url: `${this.settings.apis.web.url}/project/${this.project_id}/join`,
|
||||
qs: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
auth: {
|
||||
user: this.settings.apis.web.user,
|
||||
pass: this.settings.apis.web.pass,
|
||||
sendImmediately: true
|
||||
json: true
|
||||
jar: false
|
||||
},
|
||||
json: true,
|
||||
jar: false,
|
||||
headers: {}
|
||||
})
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it "should return the project, privilegeLevel, and restricted flag", ->
|
||||
@callback
|
||||
.calledWith(null, @response.project, @response.privilegeLevel, @response.isRestrictedUser)
|
||||
.should.equal true
|
||||
return it("should return the project, privilegeLevel, and restricted flag", function() {
|
||||
return this.callback
|
||||
.calledWith(null, this.response.project, this.response.privilegeLevel, this.response.isRestrictedUser)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with an error from web", ->
|
||||
beforeEach ->
|
||||
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, null)
|
||||
@WebApiManager.joinProject @project_id, @user_id, @callback
|
||||
describe("with an error from web", function() {
|
||||
beforeEach(function() {
|
||||
this.request.post = sinon.stub().callsArgWith(1, null, {statusCode: 500}, null);
|
||||
return this.WebApiManager.joinProject(this.project_id, this.user_id, this.callback);
|
||||
});
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback
|
||||
return it("should call the callback with an error", function() {
|
||||
return this.callback
|
||||
.calledWith(sinon.match({message: "non-success status code from web: 500"}))
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with no data from web", ->
|
||||
beforeEach ->
|
||||
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 200}, null)
|
||||
@WebApiManager.joinProject @project_id, @user_id, @callback
|
||||
describe("with no data from web", function() {
|
||||
beforeEach(function() {
|
||||
this.request.post = sinon.stub().callsArgWith(1, null, {statusCode: 200}, null);
|
||||
return this.WebApiManager.joinProject(this.project_id, this.user_id, this.callback);
|
||||
});
|
||||
|
||||
it "should call the callback with an error", ->
|
||||
@callback
|
||||
return it("should call the callback with an error", function() {
|
||||
return this.callback
|
||||
.calledWith(sinon.match({message: "no data returned from joinProject request"}))
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "when the project is over its rate limit", ->
|
||||
beforeEach ->
|
||||
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 429}, null)
|
||||
@WebApiManager.joinProject @project_id, @user_id, @callback
|
||||
return describe("when the project is over its rate limit", function() {
|
||||
beforeEach(function() {
|
||||
this.request.post = sinon.stub().callsArgWith(1, null, {statusCode: 429}, null);
|
||||
return this.WebApiManager.joinProject(this.project_id, this.user_id, this.callback);
|
||||
});
|
||||
|
||||
it "should call the callback with a TooManyRequests error code", ->
|
||||
@callback
|
||||
return it("should call the callback with a TooManyRequests error code", function() {
|
||||
return this.callback
|
||||
.calledWith(sinon.match({message: "rate-limit hit when joining project", code: "TooManyRequests"}))
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,161 +1,204 @@
|
|||
SandboxedModule = require('sandboxed-module')
|
||||
sinon = require('sinon')
|
||||
require('chai').should()
|
||||
modulePath = require('path').join __dirname, '../../../app/js/WebsocketLoadBalancer'
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon');
|
||||
require('chai').should();
|
||||
const modulePath = require('path').join(__dirname, '../../../app/js/WebsocketLoadBalancer');
|
||||
|
||||
describe "WebsocketLoadBalancer", ->
|
||||
beforeEach ->
|
||||
@rclient = {}
|
||||
@RoomEvents = {on: sinon.stub()}
|
||||
@WebsocketLoadBalancer = SandboxedModule.require modulePath, requires:
|
||||
"./RedisClientManager":
|
||||
describe("WebsocketLoadBalancer", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient = {};
|
||||
this.RoomEvents = {on: sinon.stub()};
|
||||
this.WebsocketLoadBalancer = SandboxedModule.require(modulePath, { requires: {
|
||||
"./RedisClientManager": {
|
||||
createClientList: () => []
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
||||
"./SafeJsonParse": @SafeJsonParse =
|
||||
parse: (data, cb) => cb null, JSON.parse(data)
|
||||
"./EventLogger": {checkEventOrder: sinon.stub()}
|
||||
"./HealthCheckManager": {check: sinon.stub()}
|
||||
"./RoomManager" : @RoomManager = {eventSource: sinon.stub().returns @RoomEvents}
|
||||
"./ChannelManager": @ChannelManager = {publish: sinon.stub()}
|
||||
"./ConnectedUsersManager": @ConnectedUsersManager = {refreshClient: sinon.stub()}
|
||||
@io = {}
|
||||
@WebsocketLoadBalancer.rclientPubList = [{publish: sinon.stub()}]
|
||||
@WebsocketLoadBalancer.rclientSubList = [{
|
||||
subscribe: sinon.stub()
|
||||
},
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }),
|
||||
"./SafeJsonParse": (this.SafeJsonParse =
|
||||
{parse: (data, cb) => cb(null, JSON.parse(data))}),
|
||||
"./EventLogger": {checkEventOrder: sinon.stub()},
|
||||
"./HealthCheckManager": {check: sinon.stub()},
|
||||
"./RoomManager" : (this.RoomManager = {eventSource: sinon.stub().returns(this.RoomEvents)}),
|
||||
"./ChannelManager": (this.ChannelManager = {publish: sinon.stub()}),
|
||||
"./ConnectedUsersManager": (this.ConnectedUsersManager = {refreshClient: sinon.stub()})
|
||||
}
|
||||
});
|
||||
this.io = {};
|
||||
this.WebsocketLoadBalancer.rclientPubList = [{publish: sinon.stub()}];
|
||||
this.WebsocketLoadBalancer.rclientSubList = [{
|
||||
subscribe: sinon.stub(),
|
||||
on: sinon.stub()
|
||||
}]
|
||||
}];
|
||||
|
||||
@room_id = "room-id"
|
||||
@message = "otUpdateApplied"
|
||||
@payload = ["argument one", 42]
|
||||
this.room_id = "room-id";
|
||||
this.message = "otUpdateApplied";
|
||||
return this.payload = ["argument one", 42];});
|
||||
|
||||
describe "emitToRoom", ->
|
||||
beforeEach ->
|
||||
@WebsocketLoadBalancer.emitToRoom(@room_id, @message, @payload...)
|
||||
describe("emitToRoom", function() {
|
||||
beforeEach(function() {
|
||||
return this.WebsocketLoadBalancer.emitToRoom(this.room_id, this.message, ...Array.from(this.payload));
|
||||
});
|
||||
|
||||
it "should publish the message to redis", ->
|
||||
@ChannelManager.publish
|
||||
.calledWith(@WebsocketLoadBalancer.rclientPubList[0], "editor-events", @room_id, JSON.stringify(
|
||||
room_id: @room_id,
|
||||
message: @message
|
||||
payload: @payload
|
||||
))
|
||||
.should.equal true
|
||||
return it("should publish the message to redis", function() {
|
||||
return this.ChannelManager.publish
|
||||
.calledWith(this.WebsocketLoadBalancer.rclientPubList[0], "editor-events", this.room_id, JSON.stringify({
|
||||
room_id: this.room_id,
|
||||
message: this.message,
|
||||
payload: this.payload
|
||||
}))
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "emitToAll", ->
|
||||
beforeEach ->
|
||||
@WebsocketLoadBalancer.emitToRoom = sinon.stub()
|
||||
@WebsocketLoadBalancer.emitToAll @message, @payload...
|
||||
describe("emitToAll", function() {
|
||||
beforeEach(function() {
|
||||
this.WebsocketLoadBalancer.emitToRoom = sinon.stub();
|
||||
return this.WebsocketLoadBalancer.emitToAll(this.message, ...Array.from(this.payload));
|
||||
});
|
||||
|
||||
it "should emit to the room 'all'", ->
|
||||
@WebsocketLoadBalancer.emitToRoom
|
||||
.calledWith("all", @message, @payload...)
|
||||
.should.equal true
|
||||
return it("should emit to the room 'all'", function() {
|
||||
return this.WebsocketLoadBalancer.emitToRoom
|
||||
.calledWith("all", this.message, ...Array.from(this.payload))
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "listenForEditorEvents", ->
|
||||
beforeEach ->
|
||||
@WebsocketLoadBalancer._processEditorEvent = sinon.stub()
|
||||
@WebsocketLoadBalancer.listenForEditorEvents()
|
||||
describe("listenForEditorEvents", function() {
|
||||
beforeEach(function() {
|
||||
this.WebsocketLoadBalancer._processEditorEvent = sinon.stub();
|
||||
return this.WebsocketLoadBalancer.listenForEditorEvents();
|
||||
});
|
||||
|
||||
it "should subscribe to the editor-events channel", ->
|
||||
@WebsocketLoadBalancer.rclientSubList[0].subscribe
|
||||
it("should subscribe to the editor-events channel", function() {
|
||||
return this.WebsocketLoadBalancer.rclientSubList[0].subscribe
|
||||
.calledWith("editor-events")
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it "should process the events with _processEditorEvent", ->
|
||||
@WebsocketLoadBalancer.rclientSubList[0].on
|
||||
return it("should process the events with _processEditorEvent", function() {
|
||||
return this.WebsocketLoadBalancer.rclientSubList[0].on
|
||||
.calledWith("message", sinon.match.func)
|
||||
.should.equal true
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "_processEditorEvent", ->
|
||||
describe "with bad JSON", ->
|
||||
beforeEach ->
|
||||
@isRestrictedUser = false
|
||||
@SafeJsonParse.parse = sinon.stub().callsArgWith 1, new Error("oops")
|
||||
@WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", "blah")
|
||||
return describe("_processEditorEvent", function() {
|
||||
describe("with bad JSON", function() {
|
||||
beforeEach(function() {
|
||||
this.isRestrictedUser = false;
|
||||
this.SafeJsonParse.parse = sinon.stub().callsArgWith(1, new Error("oops"));
|
||||
return this.WebsocketLoadBalancer._processEditorEvent(this.io, "editor-events", "blah");
|
||||
});
|
||||
|
||||
it "should log an error", ->
|
||||
@logger.error.called.should.equal true
|
||||
return it("should log an error", function() {
|
||||
return this.logger.error.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe "with a designated room", ->
|
||||
beforeEach ->
|
||||
@io.sockets =
|
||||
describe("with a designated room", function() {
|
||||
beforeEach(function() {
|
||||
this.io.sockets = {
|
||||
clients: sinon.stub().returns([
|
||||
{id: 'client-id-1', emit: @emit1 = sinon.stub(), ol_context: {}}
|
||||
{id: 'client-id-2', emit: @emit2 = sinon.stub(), ol_context: {}}
|
||||
{id: 'client-id-1', emit: @emit3 = sinon.stub(), ol_context: {}} # duplicate client
|
||||
{id: 'client-id-1', emit: (this.emit1 = sinon.stub()), ol_context: {}},
|
||||
{id: 'client-id-2', emit: (this.emit2 = sinon.stub()), ol_context: {}},
|
||||
{id: 'client-id-1', emit: (this.emit3 = sinon.stub()), ol_context: {}} // duplicate client
|
||||
])
|
||||
data = JSON.stringify
|
||||
room_id: @room_id
|
||||
message: @message
|
||||
payload: @payload
|
||||
@WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
|
||||
};
|
||||
const data = JSON.stringify({
|
||||
room_id: this.room_id,
|
||||
message: this.message,
|
||||
payload: this.payload
|
||||
});
|
||||
return this.WebsocketLoadBalancer._processEditorEvent(this.io, "editor-events", data);
|
||||
});
|
||||
|
||||
it "should send the message to all (unique) clients in the room", ->
|
||||
@io.sockets.clients
|
||||
.calledWith(@room_id)
|
||||
.should.equal true
|
||||
@emit1.calledWith(@message, @payload...).should.equal true
|
||||
@emit2.calledWith(@message, @payload...).should.equal true
|
||||
@emit3.called.should.equal false # duplicate client should be ignored
|
||||
return it("should send the message to all (unique) clients in the room", function() {
|
||||
this.io.sockets.clients
|
||||
.calledWith(this.room_id)
|
||||
.should.equal(true);
|
||||
this.emit1.calledWith(this.message, ...Array.from(this.payload)).should.equal(true);
|
||||
this.emit2.calledWith(this.message, ...Array.from(this.payload)).should.equal(true);
|
||||
return this.emit3.called.should.equal(false);
|
||||
});
|
||||
}); // duplicate client should be ignored
|
||||
|
||||
describe "with a designated room, and restricted clients, not restricted message", ->
|
||||
beforeEach ->
|
||||
@io.sockets =
|
||||
describe("with a designated room, and restricted clients, not restricted message", function() {
|
||||
beforeEach(function() {
|
||||
this.io.sockets = {
|
||||
clients: sinon.stub().returns([
|
||||
{id: 'client-id-1', emit: @emit1 = sinon.stub(), ol_context: {}}
|
||||
{id: 'client-id-2', emit: @emit2 = sinon.stub(), ol_context: {}}
|
||||
{id: 'client-id-1', emit: @emit3 = sinon.stub(), ol_context: {}} # duplicate client
|
||||
{id: 'client-id-4', emit: @emit4 = sinon.stub(), ol_context: {is_restricted_user: true}}
|
||||
{id: 'client-id-1', emit: (this.emit1 = sinon.stub()), ol_context: {}},
|
||||
{id: 'client-id-2', emit: (this.emit2 = sinon.stub()), ol_context: {}},
|
||||
{id: 'client-id-1', emit: (this.emit3 = sinon.stub()), ol_context: {}}, // duplicate client
|
||||
{id: 'client-id-4', emit: (this.emit4 = sinon.stub()), ol_context: {is_restricted_user: true}}
|
||||
])
|
||||
data = JSON.stringify
|
||||
room_id: @room_id
|
||||
message: @message
|
||||
payload: @payload
|
||||
@WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
|
||||
};
|
||||
const data = JSON.stringify({
|
||||
room_id: this.room_id,
|
||||
message: this.message,
|
||||
payload: this.payload
|
||||
});
|
||||
return this.WebsocketLoadBalancer._processEditorEvent(this.io, "editor-events", data);
|
||||
});
|
||||
|
||||
it "should send the message to all (unique) clients in the room", ->
|
||||
@io.sockets.clients
|
||||
.calledWith(@room_id)
|
||||
.should.equal true
|
||||
@emit1.calledWith(@message, @payload...).should.equal true
|
||||
@emit2.calledWith(@message, @payload...).should.equal true
|
||||
@emit3.called.should.equal false # duplicate client should be ignored
|
||||
@emit4.called.should.equal true # restricted client, but should be called
|
||||
return it("should send the message to all (unique) clients in the room", function() {
|
||||
this.io.sockets.clients
|
||||
.calledWith(this.room_id)
|
||||
.should.equal(true);
|
||||
this.emit1.calledWith(this.message, ...Array.from(this.payload)).should.equal(true);
|
||||
this.emit2.calledWith(this.message, ...Array.from(this.payload)).should.equal(true);
|
||||
this.emit3.called.should.equal(false); // duplicate client should be ignored
|
||||
return this.emit4.called.should.equal(true);
|
||||
});
|
||||
}); // restricted client, but should be called
|
||||
|
||||
describe "with a designated room, and restricted clients, restricted message", ->
|
||||
beforeEach ->
|
||||
@io.sockets =
|
||||
describe("with a designated room, and restricted clients, restricted message", function() {
|
||||
beforeEach(function() {
|
||||
this.io.sockets = {
|
||||
clients: sinon.stub().returns([
|
||||
{id: 'client-id-1', emit: @emit1 = sinon.stub(), ol_context: {}}
|
||||
{id: 'client-id-2', emit: @emit2 = sinon.stub(), ol_context: {}}
|
||||
{id: 'client-id-1', emit: @emit3 = sinon.stub(), ol_context: {}} # duplicate client
|
||||
{id: 'client-id-4', emit: @emit4 = sinon.stub(), ol_context: {is_restricted_user: true}}
|
||||
{id: 'client-id-1', emit: (this.emit1 = sinon.stub()), ol_context: {}},
|
||||
{id: 'client-id-2', emit: (this.emit2 = sinon.stub()), ol_context: {}},
|
||||
{id: 'client-id-1', emit: (this.emit3 = sinon.stub()), ol_context: {}}, // duplicate client
|
||||
{id: 'client-id-4', emit: (this.emit4 = sinon.stub()), ol_context: {is_restricted_user: true}}
|
||||
])
|
||||
data = JSON.stringify
|
||||
room_id: @room_id
|
||||
message: @restrictedMessage = 'new-comment'
|
||||
payload: @payload
|
||||
@WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
|
||||
};
|
||||
const data = JSON.stringify({
|
||||
room_id: this.room_id,
|
||||
message: (this.restrictedMessage = 'new-comment'),
|
||||
payload: this.payload
|
||||
});
|
||||
return this.WebsocketLoadBalancer._processEditorEvent(this.io, "editor-events", data);
|
||||
});
|
||||
|
||||
it "should send the message to all (unique) clients in the room, who are not restricted", ->
|
||||
@io.sockets.clients
|
||||
.calledWith(@room_id)
|
||||
.should.equal true
|
||||
@emit1.calledWith(@restrictedMessage, @payload...).should.equal true
|
||||
@emit2.calledWith(@restrictedMessage, @payload...).should.equal true
|
||||
@emit3.called.should.equal false # duplicate client should be ignored
|
||||
@emit4.called.should.equal false # restricted client, should not be called
|
||||
return it("should send the message to all (unique) clients in the room, who are not restricted", function() {
|
||||
this.io.sockets.clients
|
||||
.calledWith(this.room_id)
|
||||
.should.equal(true);
|
||||
this.emit1.calledWith(this.restrictedMessage, ...Array.from(this.payload)).should.equal(true);
|
||||
this.emit2.calledWith(this.restrictedMessage, ...Array.from(this.payload)).should.equal(true);
|
||||
this.emit3.called.should.equal(false); // duplicate client should be ignored
|
||||
return this.emit4.called.should.equal(false);
|
||||
});
|
||||
}); // restricted client, should not be called
|
||||
|
||||
describe "when emitting to all", ->
|
||||
beforeEach ->
|
||||
@io.sockets =
|
||||
emit: @emit = sinon.stub()
|
||||
data = JSON.stringify
|
||||
room_id: "all"
|
||||
message: @message
|
||||
payload: @payload
|
||||
@WebsocketLoadBalancer._processEditorEvent(@io, "editor-events", data)
|
||||
return describe("when emitting to all", function() {
|
||||
beforeEach(function() {
|
||||
this.io.sockets =
|
||||
{emit: (this.emit = sinon.stub())};
|
||||
const data = JSON.stringify({
|
||||
room_id: "all",
|
||||
message: this.message,
|
||||
payload: this.payload
|
||||
});
|
||||
return this.WebsocketLoadBalancer._processEditorEvent(this.io, "editor-events", data);
|
||||
});
|
||||
|
||||
it "should send the message to all clients", ->
|
||||
@emit.calledWith(@message, @payload...).should.equal true
|
||||
return it("should send the message to all clients", function() {
|
||||
return this.emit.calledWith(this.message, ...Array.from(this.payload)).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
sinon = require('sinon')
|
||||
let MockClient;
|
||||
const sinon = require('sinon');
|
||||
|
||||
idCounter = 0
|
||||
let idCounter = 0;
|
||||
|
||||
module.exports = class MockClient
|
||||
constructor: () ->
|
||||
@ol_context = {}
|
||||
@join = sinon.stub()
|
||||
@emit = sinon.stub()
|
||||
@disconnect = sinon.stub()
|
||||
@id = idCounter++
|
||||
@publicId = idCounter++
|
||||
disconnect: () ->
|
||||
module.exports = (MockClient = class MockClient {
|
||||
constructor() {
|
||||
this.ol_context = {};
|
||||
this.join = sinon.stub();
|
||||
this.emit = sinon.stub();
|
||||
this.disconnect = sinon.stub();
|
||||
this.id = idCounter++;
|
||||
this.publicId = idCounter++;
|
||||
}
|
||||
disconnect() {}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue