2020-05-06 06:10:51 -04:00
|
|
|
/*
|
|
|
|
* 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
|
2017-01-10 10:58:11 -05:00
|
|
|
.calledWith({
|
2020-05-06 06:10:51 -04:00
|
|
|
project_id: this.project_id,
|
|
|
|
doc_id: this.doc_id,
|
|
|
|
error: this.error.message
|
2017-01-10 10:58:11 -05:00
|
|
|
})
|
2020-05-06 06:10:51 -04:00
|
|
|
.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
|
2019-04-11 08:25:03 -04:00
|
|
|
.calledWith(
|
2020-05-06 06:10:51 -04:00
|
|
|
this.project_id,
|
|
|
|
this.doc_id,
|
|
|
|
this.version,
|
|
|
|
this.pathname,
|
|
|
|
this.lines,
|
|
|
|
this.ranges
|
2019-04-11 08:25:03 -04:00
|
|
|
)
|
2020-05-06 06:10:51 -04:00
|
|
|
.should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("_addProjectHistoryMetadataToOps", () => 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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2016-12-01 13:06:33 -05:00
|
|
|
|
|
|
|
|
|
|
|
|