| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- /* eslint-disable
- no-return-assign,
- no-unused-vars,
- */
- // TODO: This file was created by bulk-decaffeinate.
- // Fix any style issues and re-enable lint.
- /*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS206: Consider reworking classes to avoid initClass
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
- const sinon = require('sinon');
- const chai = require('chai');
- const should = chai.should();
- const modulePath = "../../../../app/js/UpdateManager.js";
- const SandboxedModule = require('sandboxed-module');
- describe("UpdateManager", function() {
- beforeEach(function() {
- let Profiler, Timer;
- this.project_id = "project-id-123";
- this.projectHistoryId = "history-id-123";
- this.doc_id = "document-id-123";
- this.callback = sinon.stub();
- return this.UpdateManager = SandboxedModule.require(modulePath, { requires: {
- "./LockManager" : (this.LockManager = {}),
- "./RedisManager" : (this.RedisManager = {}),
- "./RealTimeRedisManager" : (this.RealTimeRedisManager = {}),
- "./ShareJsUpdateManager" : (this.ShareJsUpdateManager = {}),
- "./HistoryManager" : (this.HistoryManager = {}),
- "logger-sharelatex": (this.logger = { log: sinon.stub() }),
- "./Metrics": (this.Metrics = {
- Timer: (Timer = (function() {
- Timer = class Timer {
- static initClass() {
- this.prototype.done = sinon.stub();
- }
- };
- Timer.initClass();
- return Timer;
- })())
- }),
- "settings-sharelatex": (this.Settings = {}),
- "./DocumentManager": (this.DocumentManager = {}),
- "./RangesManager": (this.RangesManager = {}),
- "./SnapshotManager": (this.SnapshotManager = {}),
- "./Profiler": (Profiler = (function() {
- Profiler = class Profiler {
- static initClass() {
- this.prototype.log = sinon.stub().returns({ end: sinon.stub() });
- this.prototype.end = sinon.stub();
- }
- };
- Profiler.initClass();
- return Profiler;
- })())
- }
- }
- );
- });
- describe("processOutstandingUpdates", function() {
- beforeEach(function() {
- this.UpdateManager.fetchAndApplyUpdates = sinon.stub().callsArg(2);
- return this.UpdateManager.processOutstandingUpdates(this.project_id, this.doc_id, this.callback);
- });
- it("should apply the updates", function() {
- return this.UpdateManager.fetchAndApplyUpdates.calledWith(this.project_id, this.doc_id).should.equal(true);
- });
- it("should call the callback", function() {
- return this.callback.called.should.equal(true);
- });
- return it("should time the execution", function() {
- return this.Metrics.Timer.prototype.done.called.should.equal(true);
- });
- });
- describe("processOutstandingUpdatesWithLock", function() {
- describe("when the lock is free", function() {
- beforeEach(function() {
- this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, true, (this.lockValue = "mock-lock-value"));
- this.LockManager.releaseLock = sinon.stub().callsArg(2);
- this.UpdateManager.continueProcessingUpdatesWithLock = sinon.stub().callsArg(2);
- return this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArg(2);
- });
- describe("successfully", function() {
- beforeEach(function() {
- return this.UpdateManager.processOutstandingUpdatesWithLock(this.project_id, this.doc_id, this.callback);
- });
- it("should acquire the lock", function() {
- return this.LockManager.tryLock.calledWith(this.doc_id).should.equal(true);
- });
- it("should free the lock", function() {
- return this.LockManager.releaseLock.calledWith(this.doc_id, this.lockValue).should.equal(true);
- });
- it("should process the outstanding updates", function() {
- return this.UpdateManager.processOutstandingUpdates.calledWith(this.project_id, this.doc_id).should.equal(true);
- });
- it("should do everything with the lock acquired", function() {
- this.UpdateManager.processOutstandingUpdates.calledAfter(this.LockManager.tryLock).should.equal(true);
- return this.UpdateManager.processOutstandingUpdates.calledBefore(this.LockManager.releaseLock).should.equal(true);
- });
- it("should continue processing new updates that may have come in", function() {
- return this.UpdateManager.continueProcessingUpdatesWithLock.calledWith(this.project_id, this.doc_id).should.equal(true);
- });
- return it("should return the callback", function() {
- return this.callback.called.should.equal(true);
- });
- });
- return describe("when processOutstandingUpdates returns an error", function() {
- beforeEach(function() {
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArgWith(2, (this.error = new Error("Something went wrong")));
- return this.UpdateManager.processOutstandingUpdatesWithLock(this.project_id, this.doc_id, this.callback);
- });
- it("should free the lock", function() {
- return this.LockManager.releaseLock.calledWith(this.doc_id, this.lockValue).should.equal(true);
- });
- return it("should return the error in the callback", function() {
- return this.callback.calledWith(this.error).should.equal(true);
- });
- });
- });
- return describe("when the lock is taken", function() {
- beforeEach(function() {
- this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, false);
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArg(2);
- return this.UpdateManager.processOutstandingUpdatesWithLock(this.project_id, this.doc_id, this.callback);
- });
- it("should return the callback", function() {
- return this.callback.called.should.equal(true);
- });
- return it("should not process the updates", function() {
- return this.UpdateManager.processOutstandingUpdates.called.should.equal(false);
- });
- });
- });
- describe("continueProcessingUpdatesWithLock", function() {
- describe("when there are outstanding updates", function() {
- beforeEach(function() {
- this.RealTimeRedisManager.getUpdatesLength = sinon.stub().callsArgWith(1, null, 3);
- this.UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArg(2);
- return this.UpdateManager.continueProcessingUpdatesWithLock(this.project_id, this.doc_id, this.callback);
- });
- it("should process the outstanding updates", function() {
- return this.UpdateManager.processOutstandingUpdatesWithLock.calledWith(this.project_id, this.doc_id).should.equal(true);
- });
- return it("should return the callback", function() {
- return this.callback.called.should.equal(true);
- });
- });
- return describe("when there are no outstanding updates", function() {
- beforeEach(function() {
- this.RealTimeRedisManager.getUpdatesLength = sinon.stub().callsArgWith(1, null, 0);
- this.UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArg(2);
- return this.UpdateManager.continueProcessingUpdatesWithLock(this.project_id, this.doc_id, this.callback);
- });
- it("should not try to process the outstanding updates", function() {
- return this.UpdateManager.processOutstandingUpdatesWithLock.called.should.equal(false);
- });
- return it("should return the callback", function() {
- return this.callback.called.should.equal(true);
- });
- });
- });
- describe("fetchAndApplyUpdates", function() {
- describe("with updates", function() {
- beforeEach(function() {
- this.updates = [{p: 1, t: "foo"}];
- this.updatedDocLines = ["updated", "lines"];
- this.version = 34;
- this.RealTimeRedisManager.getPendingUpdatesForDoc = sinon.stub().callsArgWith(1, null, this.updates);
- this.UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null, this.updatedDocLines, this.version);
- return this.UpdateManager.fetchAndApplyUpdates(this.project_id, this.doc_id, this.callback);
- });
- it("should get the pending updates", function() {
- return this.RealTimeRedisManager.getPendingUpdatesForDoc.calledWith(this.doc_id).should.equal(true);
- });
- it("should apply the updates", function() {
- return Array.from(this.updates).map((update) =>
- this.UpdateManager.applyUpdate
- .calledWith(this.project_id, this.doc_id, update)
- .should.equal(true));
- });
- return it("should call the callback", function() {
- return this.callback.called.should.equal(true);
- });
- });
- return describe("when there are no updates", function() {
- beforeEach(function() {
- this.updates = [];
- this.RealTimeRedisManager.getPendingUpdatesForDoc = sinon.stub().callsArgWith(1, null, this.updates);
- this.UpdateManager.applyUpdate = sinon.stub();
- this.RedisManager.setDocument = sinon.stub();
- return this.UpdateManager.fetchAndApplyUpdates(this.project_id, this.doc_id, this.callback);
- });
- it("should not call applyUpdate", function() {
- return this.UpdateManager.applyUpdate.called.should.equal(false);
- });
- return it("should call the callback", function() {
- return this.callback.called.should.equal(true);
- });
- });
- });
- describe("applyUpdate", function() {
- beforeEach(function() {
- this.updateMeta = { user_id: 'last-author-fake-id' };
- this.update = {op: [{p: 42, i: "foo"}], meta: this.updateMeta};
- this.updatedDocLines = ["updated", "lines"];
- this.version = 34;
- this.lines = ["original", "lines"];
- this.ranges = { entries: "mock", comments: "mock" };
- this.updated_ranges = { entries: "updated", comments: "updated" };
- this.appliedOps = [ {v: 42, op: "mock-op-42"}, { v: 45, op: "mock-op-45" }];
- this.doc_ops_length = sinon.stub();
- this.project_ops_length = sinon.stub();
- this.pathname = '/a/b/c.tex';
- this.DocumentManager.getDoc = sinon.stub().yields(null, this.lines, this.version, this.ranges, this.pathname, this.projectHistoryId);
- this.RangesManager.applyUpdate = sinon.stub().yields(null, this.updated_ranges, false);
- this.ShareJsUpdateManager.applyUpdate = sinon.stub().yields(null, this.updatedDocLines, this.version, this.appliedOps);
- this.RedisManager.updateDocument = sinon.stub().yields(null, this.doc_ops_length, this.project_ops_length);
- this.RealTimeRedisManager.sendData = sinon.stub();
- this.UpdateManager._addProjectHistoryMetadataToOps = sinon.stub();
- return this.HistoryManager.recordAndFlushHistoryOps = sinon.stub().callsArg(5);
- });
- describe("normally", function() {
- beforeEach(function() {
- return this.UpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.callback);
- });
- it("should apply the updates via ShareJS", function() {
- return this.ShareJsUpdateManager.applyUpdate
- .calledWith(this.project_id, this.doc_id, this.update, this.lines, this.version)
- .should.equal(true);
- });
- it("should update the ranges", function() {
- return this.RangesManager.applyUpdate
- .calledWith(this.project_id, this.doc_id, this.ranges, this.appliedOps, this.updatedDocLines)
- .should.equal(true);
- });
- it("should save the document", function() {
- return this.RedisManager.updateDocument
- .calledWith(this.project_id, this.doc_id, this.updatedDocLines, this.version, this.appliedOps, this.updated_ranges, this.updateMeta)
- .should.equal(true);
- });
- it("should add metadata to the ops" , function() {
- return this.UpdateManager._addProjectHistoryMetadataToOps
- .calledWith(this.appliedOps, this.pathname, this.projectHistoryId, this.lines)
- .should.equal(true);
- });
- it("should push the applied ops into the history queue", function() {
- return this.HistoryManager.recordAndFlushHistoryOps
- .calledWith(this.project_id, this.doc_id, this.appliedOps, this.doc_ops_length, this.project_ops_length)
- .should.equal(true);
- });
- return it("should call the callback", function() {
- return this.callback.called.should.equal(true);
- });
- });
- describe("with UTF-16 surrogate pairs in the update", function() {
- beforeEach(function() {
- this.update = {op: [{p: 42, i: "\uD835\uDC00"}]};
- return this.UpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.callback);
- });
- return it("should apply the update but with surrogate pairs removed", function() {
- this.ShareJsUpdateManager.applyUpdate
- .calledWith(this.project_id, this.doc_id, this.update)
- .should.equal(true);
- // \uFFFD is 'replacement character'
- return this.update.op[0].i.should.equal("\uFFFD\uFFFD");
- });
- });
- describe("with an error", function() {
- beforeEach(function() {
- this.error = new Error("something went wrong");
- this.ShareJsUpdateManager.applyUpdate = sinon.stub().yields(this.error);
- return this.UpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.callback);
- });
- it("should call RealTimeRedisManager.sendData with the error", function() {
- return this.RealTimeRedisManager.sendData
- .calledWith({
- project_id: this.project_id,
- doc_id: this.doc_id,
- error: this.error.message
- })
- .should.equal(true);
- });
- return it("should call the callback with the error", function() {
- return this.callback.calledWith(this.error).should.equal(true);
- });
- });
- return describe("when ranges get collapsed", function() {
- beforeEach(function() {
- this.RangesManager.applyUpdate = sinon.stub().yields(null, this.updated_ranges, true);
- this.SnapshotManager.recordSnapshot = sinon.stub().yields();
- return this.UpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.callback);
- });
- return it("should call SnapshotManager.recordSnapshot", function() {
- return this.SnapshotManager.recordSnapshot
- .calledWith(
- this.project_id,
- this.doc_id,
- this.version,
- this.pathname,
- this.lines,
- this.ranges
- )
- .should.equal(true);
- });
- });
- });
- describe("_addProjectHistoryMetadataToOps", function() { return it("should add projectHistoryId, pathname and doc_length metadata to the ops", function() {
- const lines = [
- 'some',
- 'test',
- 'data'
- ];
- const appliedOps = [
- { v: 42, op: [{i: "foo", p: 4}, { i: "bar", p: 6 }] },
- { v: 45, op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }] },
- { v: 49, op: [{i: "penguin", p: 18}] }
- ];
- this.UpdateManager._addProjectHistoryMetadataToOps(appliedOps, this.pathname, this.projectHistoryId, lines);
- return appliedOps.should.deep.equal([{
- projectHistoryId: this.projectHistoryId,
- v: 42,
- op: [{i: "foo", p: 4}, { i: "bar", p: 6 }],
- meta: {
- pathname: this.pathname,
- doc_length: 14
- }
- }, {
- projectHistoryId: this.projectHistoryId,
- v: 45,
- op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }],
- meta: {
- pathname: this.pathname,
- doc_length: 20
- } // 14 + 'foo' + 'bar'
- }, {
- projectHistoryId: this.projectHistoryId,
- v: 49,
- op: [{i: "penguin", p: 18}],
- meta: {
- pathname: this.pathname,
- doc_length: 23
- } // 14 - 'qux' + 'bazbaz'
- }]);
- }); });
- return describe("lockUpdatesAndDo", function() {
- beforeEach(function() {
- this.method = sinon.stub().callsArgWith(3, null, this.response_arg1);
- this.callback = sinon.stub();
- this.arg1 = "argument 1";
- this.response_arg1 = "response argument 1";
- this.lockValue = "mock-lock-value";
- this.LockManager.getLock = sinon.stub().callsArgWith(1, null, this.lockValue);
- return this.LockManager.releaseLock = sinon.stub().callsArg(2);
- });
- describe("successfully", function() {
- beforeEach(function() {
- this.UpdateManager.continueProcessingUpdatesWithLock = sinon.stub();
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArg(2);
- return this.UpdateManager.lockUpdatesAndDo(this.method, this.project_id, this.doc_id, this.arg1, this.callback);
- });
- it("should lock the doc", function() {
- return this.LockManager.getLock
- .calledWith(this.doc_id)
- .should.equal(true);
- });
- it("should process any outstanding updates", function() {
- return this.UpdateManager.processOutstandingUpdates
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true);
- });
- it("should call the method", function() {
- return this.method
- .calledWith(this.project_id, this.doc_id, this.arg1)
- .should.equal(true);
- });
- it("should return the method response to the callback", function() {
- return this.callback
- .calledWith(null, this.response_arg1)
- .should.equal(true);
- });
- it("should release the lock", function() {
- return this.LockManager.releaseLock
- .calledWith(this.doc_id, this.lockValue)
- .should.equal(true);
- });
- return it("should continue processing updates", function() {
- return this.UpdateManager.continueProcessingUpdatesWithLock
- .calledWith(this.project_id, this.doc_id)
- .should.equal(true);
- });
- });
- describe("when processOutstandingUpdates returns an error", function() {
- beforeEach(function() {
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArgWith(2, (this.error = new Error("Something went wrong")));
- return this.UpdateManager.lockUpdatesAndDo(this.method, this.project_id, this.doc_id, this.arg1, this.callback);
- });
- it("should free the lock", function() {
- return this.LockManager.releaseLock.calledWith(this.doc_id, this.lockValue).should.equal(true);
- });
- return it("should return the error in the callback", function() {
- return this.callback.calledWith(this.error).should.equal(true);
- });
- });
- return describe("when the method returns an error", function() {
- beforeEach(function() {
- this.UpdateManager.processOutstandingUpdates = sinon.stub().callsArg(2);
- this.method = sinon.stub().callsArgWith(3, (this.error = new Error("something went wrong")), this.response_arg1);
- return this.UpdateManager.lockUpdatesAndDo(this.method, this.project_id, this.doc_id, this.arg1, this.callback);
- });
- it("should free the lock", function() {
- return this.LockManager.releaseLock.calledWith(this.doc_id, this.lockValue).should.equal(true);
- });
- return it("should return the error in the callback", function() {
- return this.callback.calledWith(this.error).should.equal(true);
- });
- });
- });
- });
|