2020-06-23 13:29:59 -04:00
|
|
|
/*
|
|
|
|
* 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", 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}`; }
|
|
|
|
}
|
|
|
|
},
|
2019-06-29 07:28:54 -04:00
|
|
|
pubsub: null
|
2020-06-23 13:29:59 -04:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
"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", 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();
|
|
|
|
});
|
2014-11-14 10:30:18 -05:00
|
|
|
|
2020-06-23 13:29:59 -04:00
|
|
|
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", function() {
|
|
|
|
return this.rclient[0].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", 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");
|
|
|
|
});
|
2015-12-01 06:05:49 -05:00
|
|
|
|
2020-06-23 13:29:59 -04:00
|
|
|
return it("should log an error", function() {
|
|
|
|
return this.logger.error.called.should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("with update", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.message = {
|
|
|
|
doc_id: this.doc_id,
|
2014-11-14 10:30:18 -05:00
|
|
|
op: {t: "foo", p: 12}
|
2020-06-23 13:29:59 -04:00
|
|
|
};
|
|
|
|
this.EditorUpdatesController._applyUpdateFromDocumentUpdater = sinon.stub();
|
|
|
|
return this.EditorUpdatesController._processMessageFromDocumentUpdater(this.io, "applied-ops", JSON.stringify(this.message));
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should apply the update", function() {
|
|
|
|
return this.EditorUpdatesController._applyUpdateFromDocumentUpdater
|
|
|
|
.calledWith(this.io, this.doc_id, this.message.op)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("with error", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.message = {
|
|
|
|
doc_id: this.doc_id,
|
2014-11-14 10:30:18 -05:00
|
|
|
error: "Something went wrong"
|
2020-06-23 13:29:59 -04:00
|
|
|
};
|
|
|
|
this.EditorUpdatesController._processErrorFromDocumentUpdater = sinon.stub();
|
|
|
|
return this.EditorUpdatesController._processMessageFromDocumentUpdater(this.io, "applied-ops", JSON.stringify(this.message));
|
|
|
|
});
|
|
|
|
|
|
|
|
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", 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
|
2015-11-19 05:58:28 -05:00
|
|
|
|
2020-06-23 13:29:59 -04:00
|
|
|
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", 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", function() {
|
|
|
|
return this.io.sockets.clients
|
|
|
|
.calledWith(this.doc_id)
|
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should send the full update to the other clients", function() {
|
|
|
|
return Array.from(this.otherClients).map((client) =>
|
2015-11-19 05:58:28 -05:00
|
|
|
client.emit
|
2020-06-23 13:29:59 -04:00
|
|
|
.calledWith("otUpdateApplied", this.update)
|
|
|
|
.should.equal(true));
|
|
|
|
});
|
|
|
|
});
|
2015-11-19 05:58:28 -05:00
|
|
|
|
2020-06-23 13:29:59 -04:00
|
|
|
return describe("with a duplicate op", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.update.dup = true;
|
|
|
|
return this.EditorUpdatesController._applyUpdateFromDocumentUpdater(this.io, this.doc_id, this.update);
|
|
|
|
});
|
2015-11-19 05:58:28 -05:00
|
|
|
|
2020-06-23 13:29:59 -04:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should not send anything to the other clients (they've already had the op)", function() {
|
|
|
|
return Array.from(this.otherClients).map((client) =>
|
2015-11-19 05:58:28 -05:00
|
|
|
client.emit
|
|
|
|
.calledWith("otUpdateApplied")
|
2020-06-23 13:29:59 -04:00
|
|
|
.should.equal(false));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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", function() {
|
|
|
|
return this.logger.warn.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));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2014-11-14 10:30:18 -05:00
|
|
|
|