mirror of
https://github.com/overleaf/overleaf.git
synced 2024-09-16 02:52:31 -04:00
prettier: convert test/unit decaffeinated files to Prettier format
This commit is contained in:
parent
bb20394243
commit
9a2f8ecbd4
24 changed files with 7979 additions and 5902 deletions
|
@ -10,74 +10,86 @@
|
|||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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 {
|
||||
expect
|
||||
} = chai;
|
||||
const modulePath = "../../../../app/js/DiffCodec.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const { expect } = chai
|
||||
const modulePath = '../../../../app/js/DiffCodec.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe("DiffCodec", function() {
|
||||
beforeEach(function() {
|
||||
this.callback = sinon.stub();
|
||||
return this.DiffCodec = SandboxedModule.require(modulePath);
|
||||
});
|
||||
describe('DiffCodec', function () {
|
||||
beforeEach(function () {
|
||||
this.callback = sinon.stub()
|
||||
return (this.DiffCodec = SandboxedModule.require(modulePath))
|
||||
})
|
||||
|
||||
return describe("diffAsShareJsOps", function() {
|
||||
it("should insert new text correctly", function(done) {
|
||||
this.before = ["hello world"];
|
||||
this.after = ["hello beautiful world"];
|
||||
return this.DiffCodec.diffAsShareJsOp(this.before, this.after, (error, ops) => {
|
||||
expect(ops).to.deep.equal([{
|
||||
i: "beautiful ",
|
||||
p: 6
|
||||
}
|
||||
]);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return describe('diffAsShareJsOps', function () {
|
||||
it('should insert new text correctly', function (done) {
|
||||
this.before = ['hello world']
|
||||
this.after = ['hello beautiful world']
|
||||
return this.DiffCodec.diffAsShareJsOp(
|
||||
this.before,
|
||||
this.after,
|
||||
(error, ops) => {
|
||||
expect(ops).to.deep.equal([
|
||||
{
|
||||
i: 'beautiful ',
|
||||
p: 6
|
||||
}
|
||||
])
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should shift later inserts by previous inserts", function(done) {
|
||||
this.before = ["the boy played with the ball"];
|
||||
this.after = ["the tall boy played with the red ball"];
|
||||
return this.DiffCodec.diffAsShareJsOp(this.before, this.after, (error, ops) => {
|
||||
expect(ops).to.deep.equal([
|
||||
{ i: "tall ", p: 4 },
|
||||
{ i: "red ", p: 29 }
|
||||
]);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it('should shift later inserts by previous inserts', function (done) {
|
||||
this.before = ['the boy played with the ball']
|
||||
this.after = ['the tall boy played with the red ball']
|
||||
return this.DiffCodec.diffAsShareJsOp(
|
||||
this.before,
|
||||
this.after,
|
||||
(error, ops) => {
|
||||
expect(ops).to.deep.equal([
|
||||
{ i: 'tall ', p: 4 },
|
||||
{ i: 'red ', p: 29 }
|
||||
])
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should delete text correctly", function(done) {
|
||||
this.before = ["hello beautiful world"];
|
||||
this.after = ["hello world"];
|
||||
return this.DiffCodec.diffAsShareJsOp(this.before, this.after, (error, ops) => {
|
||||
expect(ops).to.deep.equal([{
|
||||
d: "beautiful ",
|
||||
p: 6
|
||||
}
|
||||
]);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return it("should shift later deletes by the first deletes", function(done) {
|
||||
this.before = ["the tall boy played with the red ball"];
|
||||
this.after = ["the boy played with the ball"];
|
||||
return this.DiffCodec.diffAsShareJsOp(this.before, this.after, (error, ops) => {
|
||||
expect(ops).to.deep.equal([
|
||||
{ d: "tall ", p: 4 },
|
||||
{ d: "red ", p: 24 }
|
||||
]);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should delete text correctly', function (done) {
|
||||
this.before = ['hello beautiful world']
|
||||
this.after = ['hello world']
|
||||
return this.DiffCodec.diffAsShareJsOp(
|
||||
this.before,
|
||||
this.after,
|
||||
(error, ops) => {
|
||||
expect(ops).to.deep.equal([
|
||||
{
|
||||
d: 'beautiful ',
|
||||
p: 6
|
||||
}
|
||||
])
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return it('should shift later deletes by the first deletes', function (done) {
|
||||
this.before = ['the tall boy played with the red ball']
|
||||
this.after = ['the boy played with the ball']
|
||||
return this.DiffCodec.diffAsShareJsOp(
|
||||
this.before,
|
||||
this.after,
|
||||
(error, ops) => {
|
||||
expect(ops).to.deep.equal([
|
||||
{ d: 'tall ', p: 4 },
|
||||
{ d: 'red ', p: 24 }
|
||||
])
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -11,144 +11,166 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* 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/DispatchManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const Errors = require("../../../../app/js/Errors.js");
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/DispatchManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const Errors = require('../../../../app/js/Errors.js')
|
||||
|
||||
describe("DispatchManager", function() {
|
||||
beforeEach(function() {
|
||||
this.timeout(3000);
|
||||
this.DispatchManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"./UpdateManager" : (this.UpdateManager = {}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), warn: sinon.stub() }),
|
||||
"settings-sharelatex": (this.settings = {
|
||||
redis: {
|
||||
documentupdater: {}
|
||||
}
|
||||
}),
|
||||
"redis-sharelatex": (this.redis = {}),
|
||||
"./RateLimitManager": {},
|
||||
"./Errors": Errors,
|
||||
"./Metrics": {
|
||||
Timer() {
|
||||
return {done() {}};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
this.callback = sinon.stub();
|
||||
return this.RateLimiter = { run(task,cb) { return task(cb); } };}); // run task without rate limit
|
||||
describe('DispatchManager', function () {
|
||||
beforeEach(function () {
|
||||
this.timeout(3000)
|
||||
this.DispatchManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./UpdateManager': (this.UpdateManager = {}),
|
||||
'logger-sharelatex': (this.logger = {
|
||||
log: sinon.stub(),
|
||||
error: sinon.stub(),
|
||||
warn: sinon.stub()
|
||||
}),
|
||||
'settings-sharelatex': (this.settings = {
|
||||
redis: {
|
||||
documentupdater: {}
|
||||
}
|
||||
}),
|
||||
'redis-sharelatex': (this.redis = {}),
|
||||
'./RateLimitManager': {},
|
||||
'./Errors': Errors,
|
||||
'./Metrics': {
|
||||
Timer() {
|
||||
return { done() {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
this.callback = sinon.stub()
|
||||
return (this.RateLimiter = {
|
||||
run(task, cb) {
|
||||
return task(cb)
|
||||
}
|
||||
})
|
||||
}) // run task without rate limit
|
||||
|
||||
return describe("each worker", function() {
|
||||
beforeEach(function() {
|
||||
this.client =
|
||||
{auth: sinon.stub()};
|
||||
this.redis.createClient = sinon.stub().returns(this.client);
|
||||
return this.worker = this.DispatchManager.createDispatcher(this.RateLimiter);
|
||||
});
|
||||
|
||||
it("should create a new redis client", function() {
|
||||
return this.redis.createClient.called.should.equal(true);
|
||||
});
|
||||
|
||||
describe("_waitForUpdateThenDispatchWorker", function() {
|
||||
beforeEach(function() {
|
||||
this.project_id = "project-id-123";
|
||||
this.doc_id = "doc-id-123";
|
||||
this.doc_key = `${this.project_id}:${this.doc_id}`;
|
||||
return this.client.blpop = sinon.stub().callsArgWith(2, null, ["pending-updates-list", this.doc_key]);
|
||||
});
|
||||
return describe('each worker', function () {
|
||||
beforeEach(function () {
|
||||
this.client = { auth: sinon.stub() }
|
||||
this.redis.createClient = sinon.stub().returns(this.client)
|
||||
return (this.worker = this.DispatchManager.createDispatcher(
|
||||
this.RateLimiter
|
||||
))
|
||||
})
|
||||
|
||||
describe("in the normal case", function() {
|
||||
beforeEach(function() {
|
||||
this.UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArg(2);
|
||||
return this.worker._waitForUpdateThenDispatchWorker(this.callback);
|
||||
});
|
||||
it('should create a new redis client', function () {
|
||||
return this.redis.createClient.called.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call redis with BLPOP", function() {
|
||||
return this.client.blpop
|
||||
.calledWith("pending-updates-list", 0)
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it("should call processOutstandingUpdatesWithLock", function() {
|
||||
return this.UpdateManager.processOutstandingUpdatesWithLock
|
||||
.calledWith(this.project_id, this.doc_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
describe('_waitForUpdateThenDispatchWorker', function () {
|
||||
beforeEach(function () {
|
||||
this.project_id = 'project-id-123'
|
||||
this.doc_id = 'doc-id-123'
|
||||
this.doc_key = `${this.project_id}:${this.doc_id}`
|
||||
return (this.client.blpop = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, ['pending-updates-list', this.doc_key]))
|
||||
})
|
||||
|
||||
it("should not log any errors", function() {
|
||||
this.logger.error.called.should.equal(false);
|
||||
return this.logger.warn.called.should.equal(false);
|
||||
});
|
||||
describe('in the normal case', function () {
|
||||
beforeEach(function () {
|
||||
this.UpdateManager.processOutstandingUpdatesWithLock = sinon
|
||||
.stub()
|
||||
.callsArg(2)
|
||||
return this.worker._waitForUpdateThenDispatchWorker(this.callback)
|
||||
})
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
it('should call redis with BLPOP', function () {
|
||||
return this.client.blpop
|
||||
.calledWith('pending-updates-list', 0)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
describe("with an error", function() {
|
||||
beforeEach(function() {
|
||||
this.UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArgWith(2, new Error("a generic error"));
|
||||
return this.worker._waitForUpdateThenDispatchWorker(this.callback);
|
||||
});
|
||||
it('should call processOutstandingUpdatesWithLock', function () {
|
||||
return this.UpdateManager.processOutstandingUpdatesWithLock
|
||||
.calledWith(this.project_id, this.doc_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should log an error", function() {
|
||||
return this.logger.error.called.should.equal(true);
|
||||
});
|
||||
it('should not log any errors', function () {
|
||||
this.logger.error.called.should.equal(false)
|
||||
return this.logger.warn.called.should.equal(false)
|
||||
})
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("with a 'Delete component' error", function() {
|
||||
beforeEach(function() {
|
||||
this.UpdateManager.processOutstandingUpdatesWithLock = sinon.stub().callsArgWith(2, new Errors.DeleteMismatchError());
|
||||
return this.worker._waitForUpdateThenDispatchWorker(this.callback);
|
||||
});
|
||||
describe('with an error', function () {
|
||||
beforeEach(function () {
|
||||
this.UpdateManager.processOutstandingUpdatesWithLock = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, new Error('a generic error'))
|
||||
return this.worker._waitForUpdateThenDispatchWorker(this.callback)
|
||||
})
|
||||
|
||||
it("should log a warning", function() {
|
||||
return this.logger.warn.called.should.equal(true);
|
||||
});
|
||||
it('should log an error', function () {
|
||||
return this.logger.error.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("run", function() { return it("should call _waitForUpdateThenDispatchWorker until shutting down", function(done) {
|
||||
let callCount = 0;
|
||||
this.worker._waitForUpdateThenDispatchWorker = callback => {
|
||||
if (callback == null) { callback = function(error) {}; }
|
||||
callCount++;
|
||||
if (callCount === 3) {
|
||||
this.settings.shuttingDown = true;
|
||||
}
|
||||
return setTimeout(() => callback()
|
||||
, 10);
|
||||
};
|
||||
sinon.spy(this.worker, "_waitForUpdateThenDispatchWorker");
|
||||
|
||||
|
||||
this.worker.run();
|
||||
return describe("with a 'Delete component' error", function () {
|
||||
beforeEach(function () {
|
||||
this.UpdateManager.processOutstandingUpdatesWithLock = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, new Errors.DeleteMismatchError())
|
||||
return this.worker._waitForUpdateThenDispatchWorker(this.callback)
|
||||
})
|
||||
|
||||
var checkStatus = () => {
|
||||
if (!this.settings.shuttingDown) { // retry until shutdown
|
||||
setTimeout(checkStatus, 100);
|
||||
|
||||
} else {
|
||||
this.worker._waitForUpdateThenDispatchWorker.callCount.should.equal(3);
|
||||
return done();
|
||||
}
|
||||
};
|
||||
it('should log a warning', function () {
|
||||
return this.logger.warn.called.should.equal(true)
|
||||
})
|
||||
|
||||
return checkStatus();
|
||||
}); });
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return describe('run', function () {
|
||||
return it('should call _waitForUpdateThenDispatchWorker until shutting down', function (done) {
|
||||
let callCount = 0
|
||||
this.worker._waitForUpdateThenDispatchWorker = (callback) => {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
callCount++
|
||||
if (callCount === 3) {
|
||||
this.settings.shuttingDown = true
|
||||
}
|
||||
return setTimeout(() => callback(), 10)
|
||||
}
|
||||
sinon.spy(this.worker, '_waitForUpdateThenDispatchWorker')
|
||||
|
||||
this.worker.run()
|
||||
|
||||
var checkStatus = () => {
|
||||
if (!this.settings.shuttingDown) {
|
||||
// retry until shutdown
|
||||
setTimeout(checkStatus, 100)
|
||||
} else {
|
||||
this.worker._waitForUpdateThenDispatchWorker.callCount.should.equal(
|
||||
3
|
||||
)
|
||||
return done()
|
||||
}
|
||||
}
|
||||
|
||||
return checkStatus()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,305 +9,417 @@
|
|||
* 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/HistoryManager');
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
require('chai').should()
|
||||
const modulePath = require('path').join(
|
||||
__dirname,
|
||||
'../../../../app/js/HistoryManager'
|
||||
)
|
||||
|
||||
describe("HistoryManager", function() {
|
||||
beforeEach(function() {
|
||||
this.HistoryManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"request": (this.request = {}),
|
||||
"settings-sharelatex": (this.Settings = {
|
||||
apis: {
|
||||
project_history: {
|
||||
enabled: true,
|
||||
url: "http://project_history.example.com"
|
||||
},
|
||||
trackchanges: {
|
||||
url: "http://trackchanges.example.com"
|
||||
}
|
||||
}
|
||||
}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub(), debug: sinon.stub() }),
|
||||
"./DocumentManager": (this.DocumentManager = {}),
|
||||
"./HistoryRedisManager": (this.HistoryRedisManager = {}),
|
||||
"./RedisManager": (this.RedisManager = {}),
|
||||
"./ProjectHistoryRedisManager": (this.ProjectHistoryRedisManager = {}),
|
||||
"./Metrics": (this.metrics = {inc: sinon.stub()})
|
||||
}
|
||||
});
|
||||
this.project_id = "mock-project-id";
|
||||
this.doc_id = "mock-doc-id";
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
describe('HistoryManager', function () {
|
||||
beforeEach(function () {
|
||||
this.HistoryManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
request: (this.request = {}),
|
||||
'settings-sharelatex': (this.Settings = {
|
||||
apis: {
|
||||
project_history: {
|
||||
enabled: true,
|
||||
url: 'http://project_history.example.com'
|
||||
},
|
||||
trackchanges: {
|
||||
url: 'http://trackchanges.example.com'
|
||||
}
|
||||
}
|
||||
}),
|
||||
'logger-sharelatex': (this.logger = {
|
||||
log: sinon.stub(),
|
||||
error: sinon.stub(),
|
||||
debug: sinon.stub()
|
||||
}),
|
||||
'./DocumentManager': (this.DocumentManager = {}),
|
||||
'./HistoryRedisManager': (this.HistoryRedisManager = {}),
|
||||
'./RedisManager': (this.RedisManager = {}),
|
||||
'./ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
|
||||
'./Metrics': (this.metrics = { inc: sinon.stub() })
|
||||
}
|
||||
})
|
||||
this.project_id = 'mock-project-id'
|
||||
this.doc_id = 'mock-doc-id'
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
describe("flushDocChangesAsync", function() {
|
||||
beforeEach(function() {
|
||||
return this.request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204});
|
||||
});
|
||||
describe('flushDocChangesAsync', function () {
|
||||
beforeEach(function () {
|
||||
return (this.request.post = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, { statusCode: 204 }))
|
||||
})
|
||||
|
||||
describe("when the project uses track changes", function() {
|
||||
beforeEach(function() {
|
||||
this.RedisManager.getHistoryType = sinon.stub().yields(null, 'track-changes');
|
||||
return this.HistoryManager.flushDocChangesAsync(this.project_id, this.doc_id);
|
||||
});
|
||||
describe('when the project uses track changes', function () {
|
||||
beforeEach(function () {
|
||||
this.RedisManager.getHistoryType = sinon
|
||||
.stub()
|
||||
.yields(null, 'track-changes')
|
||||
return this.HistoryManager.flushDocChangesAsync(
|
||||
this.project_id,
|
||||
this.doc_id
|
||||
)
|
||||
})
|
||||
|
||||
return it("should send a request to the track changes api", function() {
|
||||
return this.request.post
|
||||
.calledWith(`${this.Settings.apis.trackchanges.url}/project/${this.project_id}/doc/${this.doc_id}/flush`)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should send a request to the track changes api', function () {
|
||||
return this.request.post
|
||||
.calledWith(
|
||||
`${this.Settings.apis.trackchanges.url}/project/${this.project_id}/doc/${this.doc_id}/flush`
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the project uses project history and double flush is not disabled", function() {
|
||||
beforeEach(function() {
|
||||
this.RedisManager.getHistoryType = sinon.stub().yields(null, 'project-history');
|
||||
return this.HistoryManager.flushDocChangesAsync(this.project_id, this.doc_id);
|
||||
});
|
||||
describe('when the project uses project history and double flush is not disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.RedisManager.getHistoryType = sinon
|
||||
.stub()
|
||||
.yields(null, 'project-history')
|
||||
return this.HistoryManager.flushDocChangesAsync(
|
||||
this.project_id,
|
||||
this.doc_id
|
||||
)
|
||||
})
|
||||
|
||||
return it("should send a request to the track changes api", function() {
|
||||
return this.request.post
|
||||
.called
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should send a request to the track changes api', function () {
|
||||
return this.request.post.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("when the project uses project history and double flush is disabled", function() {
|
||||
beforeEach(function() {
|
||||
this.Settings.disableDoubleFlush = true;
|
||||
this.RedisManager.getHistoryType = sinon.stub().yields(null, 'project-history');
|
||||
return this.HistoryManager.flushDocChangesAsync(this.project_id, this.doc_id);
|
||||
});
|
||||
return describe('when the project uses project history and double flush is disabled', function () {
|
||||
beforeEach(function () {
|
||||
this.Settings.disableDoubleFlush = true
|
||||
this.RedisManager.getHistoryType = sinon
|
||||
.stub()
|
||||
.yields(null, 'project-history')
|
||||
return this.HistoryManager.flushDocChangesAsync(
|
||||
this.project_id,
|
||||
this.doc_id
|
||||
)
|
||||
})
|
||||
|
||||
return it("should not send a request to the track changes api", function() {
|
||||
return this.request.post
|
||||
.called
|
||||
.should.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should not send a request to the track changes api', function () {
|
||||
return this.request.post.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('flushProjectChangesAsync', function () {
|
||||
beforeEach(function () {
|
||||
this.request.post = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, { statusCode: 204 })
|
||||
|
||||
describe("flushProjectChangesAsync", function() {
|
||||
beforeEach(function() {
|
||||
this.request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204});
|
||||
return this.HistoryManager.flushProjectChangesAsync(this.project_id)
|
||||
})
|
||||
|
||||
return this.HistoryManager.flushProjectChangesAsync(this.project_id);
|
||||
});
|
||||
return it('should send a request to the project history api', function () {
|
||||
return this.request.post
|
||||
.calledWith({
|
||||
url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`,
|
||||
qs: { background: true }
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return it("should send a request to the project history api", function() {
|
||||
return this.request.post
|
||||
.calledWith({url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`, qs:{background:true}})
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
describe('flushProjectChanges', function () {
|
||||
describe('in the normal case', function () {
|
||||
beforeEach(function () {
|
||||
this.request.post = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, { statusCode: 204 })
|
||||
return this.HistoryManager.flushProjectChanges(this.project_id, {
|
||||
background: true
|
||||
})
|
||||
})
|
||||
|
||||
describe("flushProjectChanges", function() {
|
||||
return it('should send a request to the project history api', function () {
|
||||
return this.request.post
|
||||
.calledWith({
|
||||
url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`,
|
||||
qs: { background: true }
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("in the normal case", function() {
|
||||
beforeEach(function() {
|
||||
this.request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204});
|
||||
return this.HistoryManager.flushProjectChanges(this.project_id, {background:true});});
|
||||
return describe('with the skip_history_flush option', function () {
|
||||
beforeEach(function () {
|
||||
this.request.post = sinon.stub()
|
||||
return this.HistoryManager.flushProjectChanges(this.project_id, {
|
||||
skip_history_flush: true
|
||||
})
|
||||
})
|
||||
|
||||
return it("should send a request to the project history api", function() {
|
||||
return this.request.post
|
||||
.calledWith({url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`, qs:{background:true}})
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should not send a request to the project history api', function () {
|
||||
return this.request.post.called.should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return describe("with the skip_history_flush option", function() {
|
||||
beforeEach(function() {
|
||||
this.request.post = sinon.stub();
|
||||
return this.HistoryManager.flushProjectChanges(this.project_id, {skip_history_flush:true});});
|
||||
describe('recordAndFlushHistoryOps', function () {
|
||||
beforeEach(function () {
|
||||
this.ops = ['mock-ops']
|
||||
this.project_ops_length = 10
|
||||
this.doc_ops_length = 5
|
||||
|
||||
return it("should not send a request to the project history api", function() {
|
||||
return this.request.post
|
||||
.called
|
||||
.should.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
this.HistoryManager.flushProjectChangesAsync = sinon.stub()
|
||||
this.HistoryRedisManager.recordDocHasHistoryOps = sinon.stub().callsArg(3)
|
||||
return (this.HistoryManager.flushDocChangesAsync = sinon.stub())
|
||||
})
|
||||
|
||||
describe("recordAndFlushHistoryOps", function() {
|
||||
beforeEach(function() {
|
||||
this.ops = [ 'mock-ops' ];
|
||||
this.project_ops_length = 10;
|
||||
this.doc_ops_length = 5;
|
||||
describe('with no ops', function () {
|
||||
beforeEach(function () {
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
[],
|
||||
this.doc_ops_length,
|
||||
this.project_ops_length,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
this.HistoryManager.flushProjectChangesAsync = sinon.stub();
|
||||
this.HistoryRedisManager.recordDocHasHistoryOps = sinon.stub().callsArg(3);
|
||||
return this.HistoryManager.flushDocChangesAsync = sinon.stub();
|
||||
});
|
||||
it('should not flush project changes', function () {
|
||||
return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
describe("with no ops", function() {
|
||||
beforeEach(function() {
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id, this.doc_id, [], this.doc_ops_length, this.project_ops_length, this.callback
|
||||
);
|
||||
});
|
||||
it('should not record doc has history ops', function () {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it("should not flush project changes", function() {
|
||||
return this.HistoryManager.flushProjectChangesAsync.called.should.equal(false);
|
||||
});
|
||||
it('should not flush doc changes', function () {
|
||||
return this.HistoryManager.flushDocChangesAsync.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it("should not record doc has history ops", function() {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps.called.should.equal(false);
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should not flush doc changes", function() {
|
||||
return this.HistoryManager.flushDocChangesAsync.called.should.equal(false);
|
||||
});
|
||||
describe('with enough ops to flush project changes', function () {
|
||||
beforeEach(function () {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
|
||||
this.HistoryManager.shouldFlushHistoryOps
|
||||
.withArgs(this.project_ops_length)
|
||||
.returns(true)
|
||||
this.HistoryManager.shouldFlushHistoryOps
|
||||
.withArgs(this.doc_ops_length)
|
||||
.returns(false)
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.ops,
|
||||
this.doc_ops_length,
|
||||
this.project_ops_length,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
describe("with enough ops to flush project changes", function() {
|
||||
beforeEach(function() {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub();
|
||||
this.HistoryManager.shouldFlushHistoryOps.withArgs(this.project_ops_length).returns(true);
|
||||
this.HistoryManager.shouldFlushHistoryOps.withArgs(this.doc_ops_length).returns(false);
|
||||
it('should flush project changes', function () {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id, this.doc_id, this.ops, this.doc_ops_length, this.project_ops_length, this.callback
|
||||
);
|
||||
});
|
||||
it('should record doc has history ops', function () {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.ops
|
||||
)
|
||||
})
|
||||
|
||||
it("should flush project changes", function() {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should not flush doc changes', function () {
|
||||
return this.HistoryManager.flushDocChangesAsync.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it("should record doc has history ops", function() {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps
|
||||
.calledWith(this.project_id, this.doc_id, this.ops);
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should not flush doc changes", function() {
|
||||
return this.HistoryManager.flushDocChangesAsync.called.should.equal(false);
|
||||
});
|
||||
describe('with enough ops to flush doc changes', function () {
|
||||
beforeEach(function () {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
|
||||
this.HistoryManager.shouldFlushHistoryOps
|
||||
.withArgs(this.project_ops_length)
|
||||
.returns(false)
|
||||
this.HistoryManager.shouldFlushHistoryOps
|
||||
.withArgs(this.doc_ops_length)
|
||||
.returns(true)
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.ops,
|
||||
this.doc_ops_length,
|
||||
this.project_ops_length,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
describe("with enough ops to flush doc changes", function() {
|
||||
beforeEach(function() {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub();
|
||||
this.HistoryManager.shouldFlushHistoryOps.withArgs(this.project_ops_length).returns(false);
|
||||
this.HistoryManager.shouldFlushHistoryOps.withArgs(this.doc_ops_length).returns(true);
|
||||
it('should not flush project changes', function () {
|
||||
return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id, this.doc_id, this.ops, this.doc_ops_length, this.project_ops_length, this.callback
|
||||
);
|
||||
});
|
||||
it('should record doc has history ops', function () {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.ops
|
||||
)
|
||||
})
|
||||
|
||||
it("should not flush project changes", function() {
|
||||
return this.HistoryManager.flushProjectChangesAsync.called.should.equal(false);
|
||||
});
|
||||
it('should flush doc changes', function () {
|
||||
return this.HistoryManager.flushDocChangesAsync
|
||||
.calledWith(this.project_id, this.doc_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should record doc has history ops", function() {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps
|
||||
.calledWith(this.project_id, this.doc_id, this.ops);
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should flush doc changes", function() {
|
||||
return this.HistoryManager.flushDocChangesAsync
|
||||
.calledWith(this.project_id, this.doc_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
describe('when recording doc has history ops errors', function () {
|
||||
beforeEach(function () {
|
||||
this.error = new Error('error')
|
||||
this.HistoryRedisManager.recordDocHasHistoryOps = sinon
|
||||
.stub()
|
||||
.callsArgWith(3, this.error)
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.ops,
|
||||
this.doc_ops_length,
|
||||
this.project_ops_length,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
describe("when recording doc has history ops errors", function() {
|
||||
beforeEach(function() {
|
||||
this.error = new Error("error");
|
||||
this.HistoryRedisManager.recordDocHasHistoryOps =
|
||||
sinon.stub().callsArgWith(3, this.error);
|
||||
it('should not flush doc changes', function () {
|
||||
return this.HistoryManager.flushDocChangesAsync.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
return this.HistoryManager.recordAndFlushHistoryOps(
|
||||
this.project_id, this.doc_id, this.ops, this.doc_ops_length, this.project_ops_length, this.callback
|
||||
);
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should not flush doc changes", function() {
|
||||
return this.HistoryManager.flushDocChangesAsync.called.should.equal(false);
|
||||
});
|
||||
return describe('shouldFlushHistoryOps', function () {
|
||||
it('should return false if the number of ops is not known', function () {
|
||||
return this.HistoryManager.shouldFlushHistoryOps(
|
||||
null,
|
||||
['a', 'b', 'c'].length,
|
||||
1
|
||||
).should.equal(false)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
it("should return false if the updates didn't take us past the threshold", function () {
|
||||
// Currently there are 14 ops
|
||||
// Previously we were on 11 ops
|
||||
// We didn't pass over a multiple of 5
|
||||
this.HistoryManager.shouldFlushHistoryOps(
|
||||
14,
|
||||
['a', 'b', 'c'].length,
|
||||
5
|
||||
).should.equal(false)
|
||||
|
||||
return describe("shouldFlushHistoryOps", function() {
|
||||
it("should return false if the number of ops is not known", function() {
|
||||
return this.HistoryManager.shouldFlushHistoryOps(null, ['a', 'b', 'c'].length, 1).should.equal(false);
|
||||
});
|
||||
it('should return true if the updates took to the threshold', function () {})
|
||||
// Currently there are 15 ops
|
||||
// Previously we were on 12 ops
|
||||
// We've reached a new multiple of 5
|
||||
return this.HistoryManager.shouldFlushHistoryOps(
|
||||
15,
|
||||
['a', 'b', 'c'].length,
|
||||
5
|
||||
).should.equal(true)
|
||||
})
|
||||
|
||||
it("should return false if the updates didn't take us past the threshold", function() {
|
||||
// Currently there are 14 ops
|
||||
// Previously we were on 11 ops
|
||||
// We didn't pass over a multiple of 5
|
||||
this.HistoryManager.shouldFlushHistoryOps(14, ['a', 'b', 'c'].length, 5).should.equal(false);
|
||||
return it('should return true if the updates took past the threshold', function () {
|
||||
// Currently there are 19 ops
|
||||
// Previously we were on 16 ops
|
||||
// We didn't pass over a multiple of 5
|
||||
return this.HistoryManager.shouldFlushHistoryOps(
|
||||
17,
|
||||
['a', 'b', 'c'].length,
|
||||
5
|
||||
).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should return true if the updates took to the threshold", function() {});
|
||||
// Currently there are 15 ops
|
||||
// Previously we were on 12 ops
|
||||
// We've reached a new multiple of 5
|
||||
return this.HistoryManager.shouldFlushHistoryOps(15, ['a', 'b', 'c'].length, 5).should.equal(true);
|
||||
});
|
||||
return describe('resyncProjectHistory', function () {
|
||||
beforeEach(function () {
|
||||
this.projectHistoryId = 'history-id-1234'
|
||||
this.docs = [
|
||||
{
|
||||
doc: this.doc_id,
|
||||
path: 'main.tex'
|
||||
}
|
||||
]
|
||||
this.files = [
|
||||
{
|
||||
file: 'mock-file-id',
|
||||
path: 'universe.png',
|
||||
url: `www.filestore.test/${this.project_id}/mock-file-id`
|
||||
}
|
||||
]
|
||||
this.ProjectHistoryRedisManager.queueResyncProjectStructure = sinon
|
||||
.stub()
|
||||
.yields()
|
||||
this.DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
|
||||
return this.HistoryManager.resyncProjectHistory(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.docs,
|
||||
this.files,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should return true if the updates took past the threshold", function() {
|
||||
// Currently there are 19 ops
|
||||
// Previously we were on 16 ops
|
||||
// We didn't pass over a multiple of 5
|
||||
return this.HistoryManager.shouldFlushHistoryOps(17, ['a', 'b', 'c'].length, 5).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should queue a project structure reync', function () {
|
||||
return this.ProjectHistoryRedisManager.queueResyncProjectStructure
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.docs,
|
||||
this.files
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return describe("resyncProjectHistory", function() {
|
||||
beforeEach(function() {
|
||||
this.projectHistoryId = 'history-id-1234';
|
||||
this.docs = [{
|
||||
doc: this.doc_id,
|
||||
path: 'main.tex'
|
||||
}
|
||||
];
|
||||
this.files = [{
|
||||
file: 'mock-file-id',
|
||||
path: 'universe.png',
|
||||
url: `www.filestore.test/${this.project_id}/mock-file-id`
|
||||
}
|
||||
];
|
||||
this.ProjectHistoryRedisManager.queueResyncProjectStructure = sinon.stub().yields();
|
||||
this.DocumentManager.resyncDocContentsWithLock = sinon.stub().yields();
|
||||
return this.HistoryManager.resyncProjectHistory(this.project_id, this.projectHistoryId, this.docs, this.files, this.callback);
|
||||
});
|
||||
it('should queue doc content reyncs', function () {
|
||||
return this.DocumentManager.resyncDocContentsWithLock
|
||||
.calledWith(this.project_id, this.doc_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should queue a project structure reync", function() {
|
||||
return this.ProjectHistoryRedisManager.queueResyncProjectStructure
|
||||
.calledWith(this.project_id, this.projectHistoryId, this.docs, this.files)
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it("should queue doc content reyncs", function() {
|
||||
return this.DocumentManager
|
||||
.resyncDocContentsWithLock
|
||||
.calledWith(this.project_id, this.doc_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -11,79 +11,93 @@
|
|||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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/HistoryRedisManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const Errors = require("../../../../app/js/Errors");
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/HistoryRedisManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const Errors = require('../../../../app/js/Errors')
|
||||
|
||||
describe("HistoryRedisManager", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient = {
|
||||
auth() {},
|
||||
exec: sinon.stub()
|
||||
};
|
||||
this.rclient.multi = () => this.rclient;
|
||||
this.HistoryRedisManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"redis-sharelatex": { createClient: () => this.rclient
|
||||
},
|
||||
"settings-sharelatex": {
|
||||
redis: {
|
||||
history: (this.settings = {
|
||||
key_schema: {
|
||||
uncompressedHistoryOps({doc_id}) { return `UncompressedHistoryOps:${doc_id}`; },
|
||||
docsWithHistoryOps({project_id}) { return `DocsWithHistoryOps:${project_id}`; }
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
"logger-sharelatex": { log() {} }
|
||||
}
|
||||
});
|
||||
this.doc_id = "doc-id-123";
|
||||
this.project_id = "project-id-123";
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
describe('HistoryRedisManager', function () {
|
||||
beforeEach(function () {
|
||||
this.rclient = {
|
||||
auth() {},
|
||||
exec: sinon.stub()
|
||||
}
|
||||
this.rclient.multi = () => this.rclient
|
||||
this.HistoryRedisManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'redis-sharelatex': { createClient: () => this.rclient },
|
||||
'settings-sharelatex': {
|
||||
redis: {
|
||||
history: (this.settings = {
|
||||
key_schema: {
|
||||
uncompressedHistoryOps({ doc_id }) {
|
||||
return `UncompressedHistoryOps:${doc_id}`
|
||||
},
|
||||
docsWithHistoryOps({ project_id }) {
|
||||
return `DocsWithHistoryOps:${project_id}`
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
'logger-sharelatex': { log() {} }
|
||||
}
|
||||
})
|
||||
this.doc_id = 'doc-id-123'
|
||||
this.project_id = 'project-id-123'
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
return describe("recordDocHasHistoryOps", function() {
|
||||
beforeEach(function() {
|
||||
this.ops = [{ op: [{ i: "foo", p: 4 }] },{ op: [{ i: "bar", p: 56 }] }];
|
||||
return this.rclient.sadd = sinon.stub().yields();
|
||||
});
|
||||
|
||||
describe("with ops", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps(this.project_id, this.doc_id, this.ops, (...args) => {
|
||||
this.callback(...Array.from(args || []));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
return it("should add the doc_id to the set of which records the project docs", function() {
|
||||
return this.rclient.sadd
|
||||
.calledWith(`DocsWithHistoryOps:${this.project_id}`, this.doc_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
return describe('recordDocHasHistoryOps', function () {
|
||||
beforeEach(function () {
|
||||
this.ops = [{ op: [{ i: 'foo', p: 4 }] }, { op: [{ i: 'bar', p: 56 }] }]
|
||||
return (this.rclient.sadd = sinon.stub().yields())
|
||||
})
|
||||
|
||||
return describe("with no ops", function() {
|
||||
beforeEach(function(done) {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps(this.project_id, this.doc_id, [], (...args) => {
|
||||
this.callback(...Array.from(args || []));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not add the doc_id to the set of which records the project docs", function() {
|
||||
return this.rclient.sadd
|
||||
.called
|
||||
.should.equal(false);
|
||||
});
|
||||
describe('with ops', function () {
|
||||
beforeEach(function (done) {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.ops,
|
||||
(...args) => {
|
||||
this.callback(...Array.from(args || []))
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with an error", function() {
|
||||
return this.callback.calledWith(new Error("cannot push no ops")).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should add the doc_id to the set of which records the project docs', function () {
|
||||
return this.rclient.sadd
|
||||
.calledWith(`DocsWithHistoryOps:${this.project_id}`, this.doc_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('with no ops', function () {
|
||||
beforeEach(function (done) {
|
||||
return this.HistoryRedisManager.recordDocHasHistoryOps(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
[],
|
||||
(...args) => {
|
||||
this.callback(...Array.from(args || []))
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should not add the doc_id to the set of which records the project docs', function () {
|
||||
return this.rclient.sadd.called.should.equal(false)
|
||||
})
|
||||
|
||||
return it('should call the callback with an error', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('cannot push no ops'))
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,59 +11,57 @@
|
|||
* DS206: Consider reworking classes to avoid initClass
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
require('coffee-script');
|
||||
const sinon = require('sinon');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const modulePath = path.join(__dirname, '../../../../app/js/LockManager.js');
|
||||
const project_id = 1234;
|
||||
const doc_id = 5678;
|
||||
const blockingKey = `Blocking:${doc_id}`;
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
require('coffee-script')
|
||||
const sinon = require('sinon')
|
||||
const assert = require('assert')
|
||||
const path = require('path')
|
||||
const modulePath = path.join(__dirname, '../../../../app/js/LockManager.js')
|
||||
const project_id = 1234
|
||||
const doc_id = 5678
|
||||
const blockingKey = `Blocking:${doc_id}`
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe('LockManager - checking the lock', function(){
|
||||
describe('LockManager - checking the lock', function () {
|
||||
let Profiler
|
||||
const existsStub = sinon.stub()
|
||||
|
||||
let Profiler;
|
||||
const existsStub = sinon.stub();
|
||||
|
||||
const mocks = {
|
||||
"logger-sharelatex": { log() {}
|
||||
},
|
||||
"redis-sharelatex": {
|
||||
createClient(){
|
||||
return {
|
||||
auth() {},
|
||||
exists: existsStub
|
||||
};
|
||||
}
|
||||
},
|
||||
"./Metrics": {inc() {}},
|
||||
"./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;
|
||||
})())
|
||||
};
|
||||
const LockManager = SandboxedModule.require(modulePath, {requires: mocks});
|
||||
const mocks = {
|
||||
'logger-sharelatex': { log() {} },
|
||||
'redis-sharelatex': {
|
||||
createClient() {
|
||||
return {
|
||||
auth() {},
|
||||
exists: existsStub
|
||||
}
|
||||
}
|
||||
},
|
||||
'./Metrics': { inc() {} },
|
||||
'./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
|
||||
})())
|
||||
}
|
||||
const LockManager = SandboxedModule.require(modulePath, { requires: mocks })
|
||||
|
||||
it('should return true if the key does not exists', function(done){
|
||||
existsStub.yields(null, "0");
|
||||
return LockManager.checkLock(doc_id, (err, free) => {
|
||||
free.should.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it('should return true if the key does not exists', function (done) {
|
||||
existsStub.yields(null, '0')
|
||||
return LockManager.checkLock(doc_id, (err, free) => {
|
||||
free.should.equal(true)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
return it('should return false if the key does exists', function(done){
|
||||
existsStub.yields(null, "1");
|
||||
return LockManager.checkLock(doc_id, (err, free) => {
|
||||
free.should.equal(false);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should return false if the key does exists', function (done) {
|
||||
existsStub.yields(null, '1')
|
||||
return LockManager.checkLock(doc_id, (err, free) => {
|
||||
free.should.equal(false)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -11,79 +11,90 @@
|
|||
* DS206: Consider reworking classes to avoid initClass
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
require('coffee-script');
|
||||
const sinon = require('sinon');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const modulePath = path.join(__dirname, '../../../../app/js/LockManager.js');
|
||||
const project_id = 1234;
|
||||
const doc_id = 5678;
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
require('coffee-script')
|
||||
const sinon = require('sinon')
|
||||
const assert = require('assert')
|
||||
const path = require('path')
|
||||
const modulePath = path.join(__dirname, '../../../../app/js/LockManager.js')
|
||||
const project_id = 1234
|
||||
const doc_id = 5678
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe('LockManager - releasing the lock', function(){
|
||||
beforeEach(function() {
|
||||
let Profiler;
|
||||
this.client = {
|
||||
auth() {},
|
||||
eval: sinon.stub()
|
||||
};
|
||||
const mocks = {
|
||||
"logger-sharelatex": {
|
||||
log() {},
|
||||
error() {}
|
||||
},
|
||||
"redis-sharelatex": {
|
||||
createClient : () => this.client
|
||||
},
|
||||
"settings-sharelatex": {
|
||||
redis: {
|
||||
lock: {
|
||||
key_schema: {
|
||||
blockingKey({doc_id}) { return `Blocking:${doc_id}`; }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"./Metrics": {inc() {}},
|
||||
"./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;
|
||||
})())
|
||||
};
|
||||
this.LockManager = SandboxedModule.require(modulePath, {requires: mocks});
|
||||
this.lockValue = "lock-value-stub";
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
describe('LockManager - releasing the lock', function () {
|
||||
beforeEach(function () {
|
||||
let Profiler
|
||||
this.client = {
|
||||
auth() {},
|
||||
eval: sinon.stub()
|
||||
}
|
||||
const mocks = {
|
||||
'logger-sharelatex': {
|
||||
log() {},
|
||||
error() {}
|
||||
},
|
||||
'redis-sharelatex': {
|
||||
createClient: () => this.client
|
||||
},
|
||||
'settings-sharelatex': {
|
||||
redis: {
|
||||
lock: {
|
||||
key_schema: {
|
||||
blockingKey({ doc_id }) {
|
||||
return `Blocking:${doc_id}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'./Metrics': { inc() {} },
|
||||
'./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
|
||||
})())
|
||||
}
|
||||
this.LockManager = SandboxedModule.require(modulePath, { requires: mocks })
|
||||
this.lockValue = 'lock-value-stub'
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
describe("when the lock is current", function() {
|
||||
beforeEach(function() {
|
||||
this.client.eval = sinon.stub().yields(null, 1);
|
||||
return this.LockManager.releaseLock(doc_id, this.lockValue, this.callback);
|
||||
});
|
||||
describe('when the lock is current', function () {
|
||||
beforeEach(function () {
|
||||
this.client.eval = sinon.stub().yields(null, 1)
|
||||
return this.LockManager.releaseLock(doc_id, this.lockValue, this.callback)
|
||||
})
|
||||
|
||||
it('should clear the data from redis', function() {
|
||||
return this.client.eval.calledWith(this.LockManager.unlockScript, 1, `Blocking:${doc_id}`, this.lockValue).should.equal(true);
|
||||
});
|
||||
it('should clear the data from redis', function () {
|
||||
return this.client.eval
|
||||
.calledWith(
|
||||
this.LockManager.unlockScript,
|
||||
1,
|
||||
`Blocking:${doc_id}`,
|
||||
this.lockValue
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it('should call the callback', function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("when the lock has expired", function() {
|
||||
beforeEach(function() {
|
||||
this.client.eval = sinon.stub().yields(null, 0);
|
||||
return this.LockManager.releaseLock(doc_id, this.lockValue, this.callback);
|
||||
});
|
||||
return describe('when the lock has expired', function () {
|
||||
beforeEach(function () {
|
||||
this.client.eval = sinon.stub().yields(null, 0)
|
||||
return this.LockManager.releaseLock(doc_id, this.lockValue, this.callback)
|
||||
})
|
||||
|
||||
return it('should return an error if the lock has expired', function() {
|
||||
return this.callback.calledWith(new Error("tried to release timed out lock")).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should return an error if the lock has expired', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('tried to release timed out lock'))
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -14,116 +14,114 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* 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/LockManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/LockManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe('LockManager - getting the lock', function() {
|
||||
beforeEach(function() {
|
||||
let Profiler;
|
||||
this.LockManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"logger-sharelatex": { log() {}
|
||||
},
|
||||
"redis-sharelatex": {
|
||||
createClient : () => {
|
||||
return {auth() {}};
|
||||
}
|
||||
},
|
||||
"./Metrics": {inc() {}},
|
||||
"./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;
|
||||
})())
|
||||
}
|
||||
}
|
||||
);
|
||||
this.callback = sinon.stub();
|
||||
return this.doc_id = "doc-id-123";
|
||||
});
|
||||
|
||||
describe("when the lock is not set", function() {
|
||||
beforeEach(function(done) {
|
||||
this.lockValue = "mock-lock-value";
|
||||
this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, true, this.lockValue);
|
||||
return this.LockManager.getLock(this.doc_id, (...args) => {
|
||||
this.callback(...Array.from(args || []));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('LockManager - getting the lock', function () {
|
||||
beforeEach(function () {
|
||||
let Profiler
|
||||
this.LockManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'logger-sharelatex': { log() {} },
|
||||
'redis-sharelatex': {
|
||||
createClient: () => {
|
||||
return { auth() {} }
|
||||
}
|
||||
},
|
||||
'./Metrics': { inc() {} },
|
||||
'./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
|
||||
})())
|
||||
}
|
||||
})
|
||||
this.callback = sinon.stub()
|
||||
return (this.doc_id = 'doc-id-123')
|
||||
})
|
||||
|
||||
it("should try to get the lock", function() {
|
||||
return this.LockManager.tryLock
|
||||
.calledWith(this.doc_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
describe('when the lock is not set', function () {
|
||||
beforeEach(function (done) {
|
||||
this.lockValue = 'mock-lock-value'
|
||||
this.LockManager.tryLock = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, true, this.lockValue)
|
||||
return this.LockManager.getLock(this.doc_id, (...args) => {
|
||||
this.callback(...Array.from(args || []))
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it("should only need to try once", function() {
|
||||
return this.LockManager.tryLock.callCount.should.equal(1);
|
||||
});
|
||||
it('should try to get the lock', function () {
|
||||
return this.LockManager.tryLock.calledWith(this.doc_id).should.equal(true)
|
||||
})
|
||||
|
||||
return it("should return the callback with the lock value", function() {
|
||||
return this.callback.calledWith(null, this.lockValue).should.equal(true);
|
||||
});
|
||||
});
|
||||
it('should only need to try once', function () {
|
||||
return this.LockManager.tryLock.callCount.should.equal(1)
|
||||
})
|
||||
|
||||
describe("when the lock is initially set", function() {
|
||||
beforeEach(function(done) {
|
||||
this.lockValue = "mock-lock-value";
|
||||
const startTime = Date.now();
|
||||
let tries = 0;
|
||||
this.LockManager.LOCK_TEST_INTERVAL = 5;
|
||||
this.LockManager.tryLock = (doc_id, callback) => {
|
||||
if (callback == null) { callback = function(error, isFree) {}; }
|
||||
if (((Date.now() - startTime) < 20) || (tries < 2)) {
|
||||
tries = tries + 1;
|
||||
return callback(null, false);
|
||||
} else {
|
||||
return callback(null, true, this.lockValue);
|
||||
}
|
||||
};
|
||||
sinon.spy(this.LockManager, "tryLock");
|
||||
return it('should return the callback with the lock value', function () {
|
||||
return this.callback.calledWith(null, this.lockValue).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return this.LockManager.getLock(this.doc_id, (...args) => {
|
||||
this.callback(...Array.from(args || []));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('when the lock is initially set', function () {
|
||||
beforeEach(function (done) {
|
||||
this.lockValue = 'mock-lock-value'
|
||||
const startTime = Date.now()
|
||||
let tries = 0
|
||||
this.LockManager.LOCK_TEST_INTERVAL = 5
|
||||
this.LockManager.tryLock = (doc_id, callback) => {
|
||||
if (callback == null) {
|
||||
callback = function (error, isFree) {}
|
||||
}
|
||||
if (Date.now() - startTime < 20 || tries < 2) {
|
||||
tries = tries + 1
|
||||
return callback(null, false)
|
||||
} else {
|
||||
return callback(null, true, this.lockValue)
|
||||
}
|
||||
}
|
||||
sinon.spy(this.LockManager, 'tryLock')
|
||||
|
||||
it("should call tryLock multiple times until free", function() {
|
||||
return (this.LockManager.tryLock.callCount > 1).should.equal(true);
|
||||
});
|
||||
return this.LockManager.getLock(this.doc_id, (...args) => {
|
||||
this.callback(...Array.from(args || []))
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
return it("should return the callback with the lock value", function() {
|
||||
return this.callback.calledWith(null, this.lockValue).should.equal(true);
|
||||
});
|
||||
});
|
||||
it('should call tryLock multiple times until free', function () {
|
||||
return (this.LockManager.tryLock.callCount > 1).should.equal(true)
|
||||
})
|
||||
|
||||
return describe("when the lock times out", function() {
|
||||
beforeEach(function(done) {
|
||||
const time = Date.now();
|
||||
this.LockManager.MAX_LOCK_WAIT_TIME = 5;
|
||||
this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, false);
|
||||
return this.LockManager.getLock(this.doc_id, (...args) => {
|
||||
this.callback(...Array.from(args || []));
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
return it("should return the callback with an error", function() {
|
||||
const e = new Error("Timeout");
|
||||
e.doc_id = this.doc_id;
|
||||
return this.callback.calledWith(e).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return it('should return the callback with the lock value', function () {
|
||||
return this.callback.calledWith(null, this.lockValue).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('when the lock times out', function () {
|
||||
beforeEach(function (done) {
|
||||
const time = Date.now()
|
||||
this.LockManager.MAX_LOCK_WAIT_TIME = 5
|
||||
this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, false)
|
||||
return this.LockManager.getLock(this.doc_id, (...args) => {
|
||||
this.callback(...Array.from(args || []))
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
return it('should return the callback with an error', function () {
|
||||
const e = new Error('Timeout')
|
||||
e.doc_id = this.doc_id
|
||||
return this.callback.calledWith(e).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -11,129 +11,140 @@
|
|||
* 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/LockManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/LockManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe('LockManager - trying the lock', function() {
|
||||
beforeEach(function() {
|
||||
let Profiler;
|
||||
this.LockManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"logger-sharelatex": { log() {}
|
||||
},
|
||||
"redis-sharelatex": {
|
||||
createClient : () => {
|
||||
return {
|
||||
auth() {},
|
||||
set: (this.set = sinon.stub())
|
||||
};
|
||||
}
|
||||
},
|
||||
"./Metrics": {inc() {}},
|
||||
"settings-sharelatex": {
|
||||
redis: {
|
||||
lock: {
|
||||
key_schema: {
|
||||
blockingKey({doc_id}) { return `Blocking:${doc_id}`; }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"./Profiler": (this.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('LockManager - trying the lock', function () {
|
||||
beforeEach(function () {
|
||||
let Profiler
|
||||
this.LockManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'logger-sharelatex': { log() {} },
|
||||
'redis-sharelatex': {
|
||||
createClient: () => {
|
||||
return {
|
||||
auth() {},
|
||||
set: (this.set = sinon.stub())
|
||||
}
|
||||
}
|
||||
},
|
||||
'./Metrics': { inc() {} },
|
||||
'settings-sharelatex': {
|
||||
redis: {
|
||||
lock: {
|
||||
key_schema: {
|
||||
blockingKey({ doc_id }) {
|
||||
return `Blocking:${doc_id}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'./Profiler': (this.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
|
||||
})())
|
||||
}
|
||||
})
|
||||
|
||||
this.callback = sinon.stub();
|
||||
return this.doc_id = "doc-id-123";
|
||||
});
|
||||
|
||||
describe("when the lock is not set", function() {
|
||||
beforeEach(function() {
|
||||
this.lockValue = "mock-lock-value";
|
||||
this.LockManager.randomLock = sinon.stub().returns(this.lockValue);
|
||||
this.set.callsArgWith(5, null, "OK");
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback);
|
||||
});
|
||||
this.callback = sinon.stub()
|
||||
return (this.doc_id = 'doc-id-123')
|
||||
})
|
||||
|
||||
it("should set the lock key with an expiry if it is not set", function() {
|
||||
return this.set.calledWith(`Blocking:${this.doc_id}`, this.lockValue, "EX", 30, "NX")
|
||||
.should.equal(true);
|
||||
});
|
||||
describe('when the lock is not set', function () {
|
||||
beforeEach(function () {
|
||||
this.lockValue = 'mock-lock-value'
|
||||
this.LockManager.randomLock = sinon.stub().returns(this.lockValue)
|
||||
this.set.callsArgWith(5, null, 'OK')
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback)
|
||||
})
|
||||
|
||||
return it("should return the callback with true and the lock value", function() {
|
||||
return this.callback.calledWith(null, true, this.lockValue).should.equal(true);
|
||||
});
|
||||
});
|
||||
it('should set the lock key with an expiry if it is not set', function () {
|
||||
return this.set
|
||||
.calledWith(`Blocking:${this.doc_id}`, this.lockValue, 'EX', 30, 'NX')
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
describe("when the lock is already set", function() {
|
||||
beforeEach(function() {
|
||||
this.set.callsArgWith(5, null, null);
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback);
|
||||
});
|
||||
return it('should return the callback with true and the lock value', function () {
|
||||
return this.callback
|
||||
.calledWith(null, true, this.lockValue)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return it("should return the callback with false", function() {
|
||||
return this.callback.calledWith(null, false).should.equal(true);
|
||||
});
|
||||
});
|
||||
describe('when the lock is already set', function () {
|
||||
beforeEach(function () {
|
||||
this.set.callsArgWith(5, null, null)
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback)
|
||||
})
|
||||
|
||||
return describe("when it takes a long time for redis to set the lock", function() {
|
||||
beforeEach(function() {
|
||||
this.Profiler.prototype.end = () => 7000; // take a long time
|
||||
this.Profiler.prototype.log = sinon.stub().returns({ end: this.Profiler.prototype.end });
|
||||
this.lockValue = "mock-lock-value";
|
||||
this.LockManager.randomLock = sinon.stub().returns(this.lockValue);
|
||||
this.LockManager.releaseLock = sinon.stub().callsArgWith(2,null);
|
||||
return this.set.callsArgWith(5, null, "OK");
|
||||
});
|
||||
return it('should return the callback with false', function () {
|
||||
return this.callback.calledWith(null, false).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("in all cases", function() {
|
||||
beforeEach(function() {
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback);
|
||||
});
|
||||
return describe('when it takes a long time for redis to set the lock', function () {
|
||||
beforeEach(function () {
|
||||
this.Profiler.prototype.end = () => 7000 // take a long time
|
||||
this.Profiler.prototype.log = sinon
|
||||
.stub()
|
||||
.returns({ end: this.Profiler.prototype.end })
|
||||
this.lockValue = 'mock-lock-value'
|
||||
this.LockManager.randomLock = sinon.stub().returns(this.lockValue)
|
||||
this.LockManager.releaseLock = sinon.stub().callsArgWith(2, null)
|
||||
return this.set.callsArgWith(5, null, 'OK')
|
||||
})
|
||||
|
||||
it("should set the lock key with an expiry if it is not set", function() {
|
||||
return this.set.calledWith(`Blocking:${this.doc_id}`, this.lockValue, "EX", 30, "NX")
|
||||
.should.equal(true);
|
||||
});
|
||||
describe('in all cases', function () {
|
||||
beforeEach(function () {
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback)
|
||||
})
|
||||
|
||||
return it("should try to release the lock", function() {
|
||||
return this.LockManager.releaseLock.calledWith(this.doc_id, this.lockValue).should.equal(true);
|
||||
});
|
||||
});
|
||||
it('should set the lock key with an expiry if it is not set', function () {
|
||||
return this.set
|
||||
.calledWith(`Blocking:${this.doc_id}`, this.lockValue, 'EX', 30, 'NX')
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
describe("if the lock is released successfully", function() {
|
||||
beforeEach(function() {
|
||||
this.LockManager.releaseLock = sinon.stub().callsArgWith(2,null);
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback);
|
||||
});
|
||||
return it('should try to release the lock', function () {
|
||||
return this.LockManager.releaseLock
|
||||
.calledWith(this.doc_id, this.lockValue)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return it("should return the callback with false", function() {
|
||||
return this.callback.calledWith(null, false).should.equal(true);
|
||||
});
|
||||
});
|
||||
describe('if the lock is released successfully', function () {
|
||||
beforeEach(function () {
|
||||
this.LockManager.releaseLock = sinon.stub().callsArgWith(2, null)
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback)
|
||||
})
|
||||
|
||||
return describe("if the lock has already timed out", function() {
|
||||
beforeEach(function() {
|
||||
this.LockManager.releaseLock = sinon.stub().callsArgWith(2, new Error("tried to release timed out lock"));
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback);
|
||||
});
|
||||
return it('should return the callback with false', function () {
|
||||
return this.callback.calledWith(null, false).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return it("should return the callback with an error", function() {
|
||||
const e = new Error("tried to release timed out lock");
|
||||
return this.callback.calledWith(e).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return describe('if the lock has already timed out', function () {
|
||||
beforeEach(function () {
|
||||
this.LockManager.releaseLock = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, new Error('tried to release timed out lock'))
|
||||
return this.LockManager.tryLock(this.doc_id, this.callback)
|
||||
})
|
||||
|
||||
return it('should return the callback with an error', function () {
|
||||
const e = new Error('tried to release timed out lock')
|
||||
return this.callback.calledWith(e).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,301 +10,427 @@
|
|||
* 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/PersistenceManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const Errors = require("../../../../app/js/Errors");
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/PersistenceManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const Errors = require('../../../../app/js/Errors')
|
||||
|
||||
describe("PersistenceManager", function() {
|
||||
beforeEach(function() {
|
||||
let Timer;
|
||||
this.request = sinon.stub();
|
||||
this.request.defaults = () => this.request;
|
||||
this.PersistenceManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"requestretry": this.request,
|
||||
"settings-sharelatex": (this.Settings = {}),
|
||||
"./Metrics": (this.Metrics = {
|
||||
Timer: (Timer = (function() {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub();
|
||||
}
|
||||
};
|
||||
Timer.initClass();
|
||||
return Timer;
|
||||
})()),
|
||||
inc: sinon.stub()
|
||||
}),
|
||||
"logger-sharelatex": (this.logger = {log: sinon.stub(), err: sinon.stub()})
|
||||
}
|
||||
});
|
||||
this.project_id = "project-id-123";
|
||||
this.projectHistoryId = "history-id-123";
|
||||
this.doc_id = "doc-id-123";
|
||||
this.lines = ["one", "two", "three"];
|
||||
this.version = 42;
|
||||
this.callback = sinon.stub();
|
||||
this.ranges = { comments: "mock", entries: "mock" };
|
||||
this.pathname = '/a/b/c.tex';
|
||||
this.lastUpdatedAt = Date.now();
|
||||
this.lastUpdatedBy = 'last-author-id';
|
||||
return this.Settings.apis = {
|
||||
web: {
|
||||
url: (this.url = "www.example.com"),
|
||||
user: (this.user = "sharelatex"),
|
||||
pass: (this.pass = "password")
|
||||
}
|
||||
};
|
||||
});
|
||||
describe('PersistenceManager', function () {
|
||||
beforeEach(function () {
|
||||
let Timer
|
||||
this.request = sinon.stub()
|
||||
this.request.defaults = () => this.request
|
||||
this.PersistenceManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
requestretry: this.request,
|
||||
'settings-sharelatex': (this.Settings = {}),
|
||||
'./Metrics': (this.Metrics = {
|
||||
Timer: (Timer = (function () {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub()
|
||||
}
|
||||
}
|
||||
Timer.initClass()
|
||||
return Timer
|
||||
})()),
|
||||
inc: sinon.stub()
|
||||
}),
|
||||
'logger-sharelatex': (this.logger = {
|
||||
log: sinon.stub(),
|
||||
err: sinon.stub()
|
||||
})
|
||||
}
|
||||
})
|
||||
this.project_id = 'project-id-123'
|
||||
this.projectHistoryId = 'history-id-123'
|
||||
this.doc_id = 'doc-id-123'
|
||||
this.lines = ['one', 'two', 'three']
|
||||
this.version = 42
|
||||
this.callback = sinon.stub()
|
||||
this.ranges = { comments: 'mock', entries: 'mock' }
|
||||
this.pathname = '/a/b/c.tex'
|
||||
this.lastUpdatedAt = Date.now()
|
||||
this.lastUpdatedBy = 'last-author-id'
|
||||
return (this.Settings.apis = {
|
||||
web: {
|
||||
url: (this.url = 'www.example.com'),
|
||||
user: (this.user = 'sharelatex'),
|
||||
pass: (this.pass = 'password')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("getDoc", function() {
|
||||
beforeEach(function() {
|
||||
return this.webResponse = {
|
||||
lines: this.lines,
|
||||
version: this.version,
|
||||
ranges: this.ranges,
|
||||
pathname: this.pathname,
|
||||
projectHistoryId: this.projectHistoryId
|
||||
};});
|
||||
describe('getDoc', function () {
|
||||
beforeEach(function () {
|
||||
return (this.webResponse = {
|
||||
lines: this.lines,
|
||||
version: this.version,
|
||||
ranges: this.ranges,
|
||||
pathname: this.pathname,
|
||||
projectHistoryId: this.projectHistoryId
|
||||
})
|
||||
})
|
||||
|
||||
describe("with a successful response from the web api", function() {
|
||||
beforeEach(function() {
|
||||
this.request.callsArgWith(1, null, {statusCode: 200}, JSON.stringify(this.webResponse));
|
||||
return this.PersistenceManager.getDoc(this.project_id, this.doc_id, this.callback);
|
||||
});
|
||||
describe('with a successful response from the web api', function () {
|
||||
beforeEach(function () {
|
||||
this.request.callsArgWith(
|
||||
1,
|
||||
null,
|
||||
{ statusCode: 200 },
|
||||
JSON.stringify(this.webResponse)
|
||||
)
|
||||
return this.PersistenceManager.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should call the web api", function() {
|
||||
return this.request
|
||||
.calledWith({
|
||||
url: `${this.url}/project/${this.project_id}/doc/${this.doc_id}`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"accept": "application/json"
|
||||
},
|
||||
auth: {
|
||||
user: this.user,
|
||||
pass: this.pass,
|
||||
sendImmediately: true
|
||||
},
|
||||
jar: false,
|
||||
timeout: 5000
|
||||
})
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should call the web api', function () {
|
||||
return this.request
|
||||
.calledWith({
|
||||
url: `${this.url}/project/${this.project_id}/doc/${this.doc_id}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
accept: 'application/json'
|
||||
},
|
||||
auth: {
|
||||
user: this.user,
|
||||
pass: this.pass,
|
||||
sendImmediately: true
|
||||
},
|
||||
jar: false,
|
||||
timeout: 5000
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback with the doc lines, version and ranges", function() {
|
||||
return this.callback
|
||||
.calledWith(null, this.lines, this.version, this.ranges, this.pathname, this.projectHistoryId)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should call the callback with the doc lines, version and ranges', function () {
|
||||
return this.callback
|
||||
.calledWith(
|
||||
null,
|
||||
this.lines,
|
||||
this.version,
|
||||
this.ranges,
|
||||
this.pathname,
|
||||
this.projectHistoryId
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("getDoc", 1, {status: 200}).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('getDoc', 1, { status: 200 })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when request returns an error", function() {
|
||||
beforeEach(function() {
|
||||
this.error = new Error("oops");
|
||||
this.error.code = "EOOPS";
|
||||
this.request.callsArgWith(1, this.error, null, null);
|
||||
return this.PersistenceManager.getDoc(this.project_id, this.doc_id, this.callback);
|
||||
});
|
||||
describe('when request returns an error', function () {
|
||||
beforeEach(function () {
|
||||
this.error = new Error('oops')
|
||||
this.error.code = 'EOOPS'
|
||||
this.request.callsArgWith(1, this.error, null, null)
|
||||
return this.PersistenceManager.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should return the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
it('should return the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("getDoc", 1, {status: "EOOPS"}).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('getDoc', 1, { status: 'EOOPS' })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the request returns 404", function() {
|
||||
beforeEach(function() {
|
||||
this.request.callsArgWith(1, null, {statusCode: 404}, "");
|
||||
return this.PersistenceManager.getDoc(this.project_id, this.doc_id, this.callback);
|
||||
});
|
||||
describe('when the request returns 404', function () {
|
||||
beforeEach(function () {
|
||||
this.request.callsArgWith(1, null, { statusCode: 404 }, '')
|
||||
return this.PersistenceManager.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should return a NotFoundError", function() {
|
||||
return this.callback.calledWith(new Errors.NotFoundError("not found")).should.equal(true);
|
||||
});
|
||||
it('should return a NotFoundError', function () {
|
||||
return this.callback
|
||||
.calledWith(new Errors.NotFoundError('not found'))
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("getDoc", 1, {status: 404}).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('getDoc', 1, { status: 404 })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the request returns an error status code", function() {
|
||||
beforeEach(function() {
|
||||
this.request.callsArgWith(1, null, {statusCode: 500}, "");
|
||||
return this.PersistenceManager.getDoc(this.project_id, this.doc_id, this.callback);
|
||||
});
|
||||
describe('when the request returns an error status code', function () {
|
||||
beforeEach(function () {
|
||||
this.request.callsArgWith(1, null, { statusCode: 500 }, '')
|
||||
return this.PersistenceManager.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should return an error", function() {
|
||||
return this.callback.calledWith(new Error("web api error")).should.equal(true);
|
||||
});
|
||||
it('should return an error', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('web api error'))
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("getDoc", 1, {status: 500}).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('getDoc', 1, { status: 500 })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when request returns an doc without lines", function() {
|
||||
beforeEach(function() {
|
||||
delete this.webResponse.lines;
|
||||
this.request.callsArgWith(1, null, {statusCode: 200}, JSON.stringify(this.webResponse));
|
||||
return this.PersistenceManager.getDoc(this.project_id, this.doc_id, this.callback);
|
||||
});
|
||||
describe('when request returns an doc without lines', function () {
|
||||
beforeEach(function () {
|
||||
delete this.webResponse.lines
|
||||
this.request.callsArgWith(
|
||||
1,
|
||||
null,
|
||||
{ statusCode: 200 },
|
||||
JSON.stringify(this.webResponse)
|
||||
)
|
||||
return this.PersistenceManager.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should return and error", function() {
|
||||
return this.callback.calledWith(new Error("web API response had no doc lines")).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should return and error', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('web API response had no doc lines'))
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when request returns an doc without a version", function() {
|
||||
beforeEach(function() {
|
||||
delete this.webResponse.version;
|
||||
this.request.callsArgWith(1, null, {statusCode: 200}, JSON.stringify(this.webResponse));
|
||||
return this.PersistenceManager.getDoc(this.project_id, this.doc_id, this.callback);
|
||||
});
|
||||
describe('when request returns an doc without a version', function () {
|
||||
beforeEach(function () {
|
||||
delete this.webResponse.version
|
||||
this.request.callsArgWith(
|
||||
1,
|
||||
null,
|
||||
{ statusCode: 200 },
|
||||
JSON.stringify(this.webResponse)
|
||||
)
|
||||
return this.PersistenceManager.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should return and error", function() {
|
||||
return this.callback.calledWith(new Error("web API response had no valid doc version")).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should return and error', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('web API response had no valid doc version'))
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("when request returns an doc without a pathname", function() {
|
||||
beforeEach(function() {
|
||||
delete this.webResponse.pathname;
|
||||
this.request.callsArgWith(1, null, {statusCode: 200}, JSON.stringify(this.webResponse));
|
||||
return this.PersistenceManager.getDoc(this.project_id, this.doc_id, this.callback);
|
||||
});
|
||||
return describe('when request returns an doc without a pathname', function () {
|
||||
beforeEach(function () {
|
||||
delete this.webResponse.pathname
|
||||
this.request.callsArgWith(
|
||||
1,
|
||||
null,
|
||||
{ statusCode: 200 },
|
||||
JSON.stringify(this.webResponse)
|
||||
)
|
||||
return this.PersistenceManager.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should return and error", function() {
|
||||
return this.callback.calledWith(new Error("web API response had no valid doc pathname")).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should return and error', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('web API response had no valid doc pathname'))
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return describe("setDoc", function() {
|
||||
describe("with a successful response from the web api", function() {
|
||||
beforeEach(function() {
|
||||
this.request.callsArgWith(1, null, {statusCode: 200});
|
||||
return this.PersistenceManager.setDoc(this.project_id, this.doc_id, this.lines, this.version, this.ranges, this.lastUpdatedAt, this.lastUpdatedBy, this.callback);
|
||||
});
|
||||
return describe('setDoc', function () {
|
||||
describe('with a successful response from the web api', function () {
|
||||
beforeEach(function () {
|
||||
this.request.callsArgWith(1, null, { statusCode: 200 })
|
||||
return this.PersistenceManager.setDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.lines,
|
||||
this.version,
|
||||
this.ranges,
|
||||
this.lastUpdatedAt,
|
||||
this.lastUpdatedBy,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should call the web api", function() {
|
||||
return this.request
|
||||
.calledWith({
|
||||
url: `${this.url}/project/${this.project_id}/doc/${this.doc_id}`,
|
||||
json: {
|
||||
lines: this.lines,
|
||||
version: this.version,
|
||||
ranges: this.ranges,
|
||||
lastUpdatedAt: this.lastUpdatedAt,
|
||||
lastUpdatedBy: this.lastUpdatedBy
|
||||
},
|
||||
method: "POST",
|
||||
auth: {
|
||||
user: this.user,
|
||||
pass: this.pass,
|
||||
sendImmediately: true
|
||||
},
|
||||
jar: false,
|
||||
timeout: 5000
|
||||
})
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should call the web api', function () {
|
||||
return this.request
|
||||
.calledWith({
|
||||
url: `${this.url}/project/${this.project_id}/doc/${this.doc_id}`,
|
||||
json: {
|
||||
lines: this.lines,
|
||||
version: this.version,
|
||||
ranges: this.ranges,
|
||||
lastUpdatedAt: this.lastUpdatedAt,
|
||||
lastUpdatedBy: this.lastUpdatedBy
|
||||
},
|
||||
method: 'POST',
|
||||
auth: {
|
||||
user: this.user,
|
||||
pass: this.pass,
|
||||
sendImmediately: true
|
||||
},
|
||||
jar: false,
|
||||
timeout: 5000
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback without error", function() {
|
||||
return this.callback.calledWith(null).should.equal(true);
|
||||
});
|
||||
it('should call the callback without error', function () {
|
||||
return this.callback.calledWith(null).should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("setDoc", 1, {status: 200}).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('setDoc', 1, { status: 200 })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when request returns an error", function() {
|
||||
beforeEach(function() {
|
||||
this.error = new Error("oops");
|
||||
this.error.code = "EOOPS";
|
||||
this.request.callsArgWith(1, this.error, null, null);
|
||||
return this.PersistenceManager.setDoc(this.project_id, this.doc_id, this.lines, this.version, this.ranges, this.lastUpdatedAt, this.lastUpdatedBy, this.callback);
|
||||
});
|
||||
describe('when request returns an error', function () {
|
||||
beforeEach(function () {
|
||||
this.error = new Error('oops')
|
||||
this.error.code = 'EOOPS'
|
||||
this.request.callsArgWith(1, this.error, null, null)
|
||||
return this.PersistenceManager.setDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.lines,
|
||||
this.version,
|
||||
this.ranges,
|
||||
this.lastUpdatedAt,
|
||||
this.lastUpdatedBy,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should return the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
it('should return the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("setDoc", 1, {status: "EOOPS"}).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('setDoc', 1, { status: 'EOOPS' })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the request returns 404", function() {
|
||||
beforeEach(function() {
|
||||
this.request.callsArgWith(1, null, {statusCode: 404}, "");
|
||||
return this.PersistenceManager.setDoc(this.project_id, this.doc_id, this.lines, this.version, this.ranges, this.lastUpdatedAt, this.lastUpdatedBy, this.callback);
|
||||
});
|
||||
describe('when the request returns 404', function () {
|
||||
beforeEach(function () {
|
||||
this.request.callsArgWith(1, null, { statusCode: 404 }, '')
|
||||
return this.PersistenceManager.setDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.lines,
|
||||
this.version,
|
||||
this.ranges,
|
||||
this.lastUpdatedAt,
|
||||
this.lastUpdatedBy,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should return a NotFoundError", function() {
|
||||
return this.callback.calledWith(new Errors.NotFoundError("not found")).should.equal(true);
|
||||
});
|
||||
it('should return a NotFoundError', function () {
|
||||
return this.callback
|
||||
.calledWith(new Errors.NotFoundError('not found'))
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("setDoc", 1, {status: 404}).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('setDoc', 1, { status: 404 })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("when the request returns an error status code", function() {
|
||||
beforeEach(function() {
|
||||
this.request.callsArgWith(1, null, {statusCode: 500}, "");
|
||||
return this.PersistenceManager.setDoc(this.project_id, this.doc_id, this.lines, this.version, this.ranges, this.lastUpdatedAt, this.lastUpdatedBy, this.callback);
|
||||
});
|
||||
return describe('when the request returns an error status code', function () {
|
||||
beforeEach(function () {
|
||||
this.request.callsArgWith(1, null, { statusCode: 500 }, '')
|
||||
return this.PersistenceManager.setDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.lines,
|
||||
this.version,
|
||||
this.ranges,
|
||||
this.lastUpdatedAt,
|
||||
this.lastUpdatedBy,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should return an error", function() {
|
||||
return this.callback.calledWith(new Error("web api error")).should.equal(true);
|
||||
});
|
||||
it('should return an error', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('web api error'))
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should increment the metric", function() {
|
||||
return this.Metrics.inc.calledWith("setDoc", 1, {status: 500}).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should increment the metric', function () {
|
||||
return this.Metrics.inc
|
||||
.calledWith('setDoc', 1, { status: 500 })
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -11,149 +11,191 @@
|
|||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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/ProjectHistoryRedisManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const tk = require("timekeeper");
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/ProjectHistoryRedisManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const tk = require('timekeeper')
|
||||
|
||||
describe("ProjectHistoryRedisManager", function() {
|
||||
beforeEach(function() {
|
||||
this.project_id = "project-id-123";
|
||||
this.projectHistoryId = "history-id-123";
|
||||
this.user_id = "user-id-123";
|
||||
this.callback = sinon.stub();
|
||||
this.rclient = {};
|
||||
tk.freeze(new Date());
|
||||
return this.ProjectHistoryRedisManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
"settings-sharelatex": (this.settings = {
|
||||
redis: {
|
||||
project_history: {
|
||||
key_schema: {
|
||||
projectHistoryOps({project_id}) { return `ProjectHistory:Ops:${project_id}`; },
|
||||
projectHistoryFirstOpTimestamp({project_id}) { return `ProjectHistory:FirstOpTimestamp:${project_id}`; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
"redis-sharelatex": {
|
||||
createClient: () => this.rclient
|
||||
},
|
||||
"logger-sharelatex": {
|
||||
log() {}
|
||||
},
|
||||
"./Metrics": (this.metrics = { summary: sinon.stub()})
|
||||
},
|
||||
globals: {
|
||||
JSON: (this.JSON = JSON)
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
describe('ProjectHistoryRedisManager', function () {
|
||||
beforeEach(function () {
|
||||
this.project_id = 'project-id-123'
|
||||
this.projectHistoryId = 'history-id-123'
|
||||
this.user_id = 'user-id-123'
|
||||
this.callback = sinon.stub()
|
||||
this.rclient = {}
|
||||
tk.freeze(new Date())
|
||||
return (this.ProjectHistoryRedisManager = SandboxedModule.require(
|
||||
modulePath,
|
||||
{
|
||||
requires: {
|
||||
'settings-sharelatex': (this.settings = {
|
||||
redis: {
|
||||
project_history: {
|
||||
key_schema: {
|
||||
projectHistoryOps({ project_id }) {
|
||||
return `ProjectHistory:Ops:${project_id}`
|
||||
},
|
||||
projectHistoryFirstOpTimestamp({ project_id }) {
|
||||
return `ProjectHistory:FirstOpTimestamp:${project_id}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
'redis-sharelatex': {
|
||||
createClient: () => this.rclient
|
||||
},
|
||||
'logger-sharelatex': {
|
||||
log() {}
|
||||
},
|
||||
'./Metrics': (this.metrics = { summary: sinon.stub() })
|
||||
},
|
||||
globals: {
|
||||
JSON: (this.JSON = JSON)
|
||||
}
|
||||
}
|
||||
))
|
||||
})
|
||||
|
||||
afterEach(function() { return tk.reset(); });
|
||||
afterEach(function () {
|
||||
return tk.reset()
|
||||
})
|
||||
|
||||
describe("queueOps", function() {
|
||||
beforeEach(function() {
|
||||
this.ops = ["mock-op-1", "mock-op-2"];
|
||||
this.multi = {exec: sinon.stub()};
|
||||
this.multi.rpush = sinon.stub();
|
||||
this.multi.setnx = sinon.stub();
|
||||
this.rclient.multi = () => this.multi;
|
||||
// @rclient = multi: () => @multi
|
||||
return this.ProjectHistoryRedisManager.queueOps(this.project_id, ...Array.from(this.ops), this.callback);
|
||||
});
|
||||
describe('queueOps', function () {
|
||||
beforeEach(function () {
|
||||
this.ops = ['mock-op-1', 'mock-op-2']
|
||||
this.multi = { exec: sinon.stub() }
|
||||
this.multi.rpush = sinon.stub()
|
||||
this.multi.setnx = sinon.stub()
|
||||
this.rclient.multi = () => this.multi
|
||||
// @rclient = multi: () => @multi
|
||||
return this.ProjectHistoryRedisManager.queueOps(
|
||||
this.project_id,
|
||||
...Array.from(this.ops),
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should queue an update", function() {
|
||||
return this.multi.rpush
|
||||
.calledWithExactly(
|
||||
`ProjectHistory:Ops:${this.project_id}`,
|
||||
this.ops[0],
|
||||
this.ops[1]
|
||||
).should.equal(true);
|
||||
});
|
||||
it('should queue an update', function () {
|
||||
return this.multi.rpush
|
||||
.calledWithExactly(
|
||||
`ProjectHistory:Ops:${this.project_id}`,
|
||||
this.ops[0],
|
||||
this.ops[1]
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should set the queue timestamp if not present", function() {
|
||||
return this.multi.setnx
|
||||
.calledWithExactly(
|
||||
`ProjectHistory:FirstOpTimestamp:${this.project_id}`,
|
||||
Date.now()
|
||||
).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should set the queue timestamp if not present', function () {
|
||||
return this.multi.setnx
|
||||
.calledWithExactly(
|
||||
`ProjectHistory:FirstOpTimestamp:${this.project_id}`,
|
||||
Date.now()
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("queueRenameEntity", function() {
|
||||
beforeEach(function() {
|
||||
this.file_id = 1234;
|
||||
describe('queueRenameEntity', function () {
|
||||
beforeEach(function () {
|
||||
this.file_id = 1234
|
||||
|
||||
this.rawUpdate = {
|
||||
pathname: (this.pathname = '/old'),
|
||||
newPathname: (this.newPathname = '/new'),
|
||||
version: (this.version = 2)
|
||||
};
|
||||
this.rawUpdate = {
|
||||
pathname: (this.pathname = '/old'),
|
||||
newPathname: (this.newPathname = '/new'),
|
||||
version: (this.version = 2)
|
||||
}
|
||||
|
||||
this.ProjectHistoryRedisManager.queueOps = sinon.stub();
|
||||
return this.ProjectHistoryRedisManager.queueRenameEntity(this.project_id, this.projectHistoryId, 'file', this.file_id, this.user_id, this.rawUpdate, this.callback);
|
||||
});
|
||||
this.ProjectHistoryRedisManager.queueOps = sinon.stub()
|
||||
return this.ProjectHistoryRedisManager.queueRenameEntity(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
'file',
|
||||
this.file_id,
|
||||
this.user_id,
|
||||
this.rawUpdate,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should queue an update", function() {
|
||||
const update = {
|
||||
pathname: this.pathname,
|
||||
new_pathname: this.newPathname,
|
||||
meta: {
|
||||
user_id: this.user_id,
|
||||
ts: new Date()
|
||||
},
|
||||
version: this.version,
|
||||
projectHistoryId: this.projectHistoryId,
|
||||
file: this.file_id
|
||||
};
|
||||
return it('should queue an update', function () {
|
||||
const update = {
|
||||
pathname: this.pathname,
|
||||
new_pathname: this.newPathname,
|
||||
meta: {
|
||||
user_id: this.user_id,
|
||||
ts: new Date()
|
||||
},
|
||||
version: this.version,
|
||||
projectHistoryId: this.projectHistoryId,
|
||||
file: this.file_id
|
||||
}
|
||||
|
||||
return this.ProjectHistoryRedisManager.queueOps
|
||||
.calledWithExactly(this.project_id, this.JSON.stringify(update), this.callback)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
return this.ProjectHistoryRedisManager.queueOps
|
||||
.calledWithExactly(
|
||||
this.project_id,
|
||||
this.JSON.stringify(update),
|
||||
this.callback
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("queueAddEntity", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.rpush = sinon.stub().yields();
|
||||
this.doc_id = 1234;
|
||||
return describe('queueAddEntity', function () {
|
||||
beforeEach(function () {
|
||||
this.rclient.rpush = sinon.stub().yields()
|
||||
this.doc_id = 1234
|
||||
|
||||
this.rawUpdate = {
|
||||
pathname: (this.pathname = '/old'),
|
||||
docLines: (this.docLines = 'a\nb'),
|
||||
version: (this.version = 2),
|
||||
url: (this.url = 'filestore.example.com')
|
||||
};
|
||||
this.rawUpdate = {
|
||||
pathname: (this.pathname = '/old'),
|
||||
docLines: (this.docLines = 'a\nb'),
|
||||
version: (this.version = 2),
|
||||
url: (this.url = 'filestore.example.com')
|
||||
}
|
||||
|
||||
this.ProjectHistoryRedisManager.queueOps = sinon.stub();
|
||||
return this.ProjectHistoryRedisManager.queueAddEntity(this.project_id, this.projectHistoryId, 'doc', this.doc_id, this.user_id, this.rawUpdate, this.callback);
|
||||
});
|
||||
this.ProjectHistoryRedisManager.queueOps = sinon.stub()
|
||||
return this.ProjectHistoryRedisManager.queueAddEntity(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
'doc',
|
||||
this.doc_id,
|
||||
this.user_id,
|
||||
this.rawUpdate,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should queue an update", function() {
|
||||
const update = {
|
||||
pathname: this.pathname,
|
||||
docLines: this.docLines,
|
||||
url: this.url,
|
||||
meta: {
|
||||
user_id: this.user_id,
|
||||
ts: new Date()
|
||||
},
|
||||
version: this.version,
|
||||
projectHistoryId: this.projectHistoryId,
|
||||
doc: this.doc_id
|
||||
};
|
||||
it('should queue an update', function () {
|
||||
const update = {
|
||||
pathname: this.pathname,
|
||||
docLines: this.docLines,
|
||||
url: this.url,
|
||||
meta: {
|
||||
user_id: this.user_id,
|
||||
ts: new Date()
|
||||
},
|
||||
version: this.version,
|
||||
projectHistoryId: this.projectHistoryId,
|
||||
doc: this.doc_id
|
||||
}
|
||||
|
||||
return this.ProjectHistoryRedisManager.queueOps
|
||||
.calledWithExactly(this.project_id, this.JSON.stringify(update), this.callback)
|
||||
.should.equal(true);
|
||||
});
|
||||
return this.ProjectHistoryRedisManager.queueOps
|
||||
.calledWithExactly(
|
||||
this.project_id,
|
||||
this.JSON.stringify(update),
|
||||
this.callback
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
describe("queueResyncProjectStructure", function() { return it("should queue an update", function() {}); });
|
||||
describe('queueResyncProjectStructure', function () {
|
||||
return it('should queue an update', function () {})
|
||||
})
|
||||
|
||||
return describe("queueResyncDocContent", function() { return it("should queue an update", function() {}); });
|
||||
});
|
||||
});
|
||||
return describe('queueResyncDocContent', function () {
|
||||
return it('should queue an update', function () {})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,121 +12,146 @@
|
|||
* 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/ProjectManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/ProjectManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe("ProjectManager - flushAndDeleteProject", function() {
|
||||
beforeEach(function() {
|
||||
let Timer;
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"./RedisManager": (this.RedisManager = {}),
|
||||
"./ProjectHistoryRedisManager": (this.ProjectHistoryRedisManager = {}),
|
||||
"./DocumentManager": (this.DocumentManager = {}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }),
|
||||
"./HistoryManager": (this.HistoryManager =
|
||||
{flushProjectChanges: sinon.stub().callsArg(2)}),
|
||||
"./Metrics": (this.Metrics = {
|
||||
Timer: (Timer = (function() {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub();
|
||||
}
|
||||
};
|
||||
Timer.initClass();
|
||||
return Timer;
|
||||
})())
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
this.project_id = "project-id-123";
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
describe('ProjectManager - flushAndDeleteProject', function () {
|
||||
beforeEach(function () {
|
||||
let Timer
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./RedisManager': (this.RedisManager = {}),
|
||||
'./ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
|
||||
'./DocumentManager': (this.DocumentManager = {}),
|
||||
'logger-sharelatex': (this.logger = {
|
||||
log: sinon.stub(),
|
||||
error: sinon.stub()
|
||||
}),
|
||||
'./HistoryManager': (this.HistoryManager = {
|
||||
flushProjectChanges: sinon.stub().callsArg(2)
|
||||
}),
|
||||
'./Metrics': (this.Metrics = {
|
||||
Timer: (Timer = (function () {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub()
|
||||
}
|
||||
}
|
||||
Timer.initClass()
|
||||
return Timer
|
||||
})())
|
||||
})
|
||||
}
|
||||
})
|
||||
this.project_id = 'project-id-123'
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function(done) {
|
||||
this.doc_ids = ["doc-id-1", "doc-id-2", "doc-id-3"];
|
||||
this.RedisManager.getDocIdsInProject = sinon.stub().callsArgWith(1, null, this.doc_ids);
|
||||
this.DocumentManager.flushAndDeleteDocWithLock = sinon.stub().callsArg(3);
|
||||
return this.ProjectManager.flushAndDeleteProjectWithLocks(this.project_id, {}, error => {
|
||||
this.callback(error);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('successfully', function () {
|
||||
beforeEach(function (done) {
|
||||
this.doc_ids = ['doc-id-1', 'doc-id-2', 'doc-id-3']
|
||||
this.RedisManager.getDocIdsInProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.doc_ids)
|
||||
this.DocumentManager.flushAndDeleteDocWithLock = sinon.stub().callsArg(3)
|
||||
return this.ProjectManager.flushAndDeleteProjectWithLocks(
|
||||
this.project_id,
|
||||
{},
|
||||
(error) => {
|
||||
this.callback(error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should get the doc ids in the project", function() {
|
||||
return this.RedisManager.getDocIdsInProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should get the doc ids in the project', function () {
|
||||
return this.RedisManager.getDocIdsInProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should delete each doc in the project", function() {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushAndDeleteDocWithLock
|
||||
.calledWith(this.project_id, doc_id, {})
|
||||
.should.equal(true));
|
||||
});
|
||||
it('should delete each doc in the project', function () {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushAndDeleteDocWithLock
|
||||
.calledWith(this.project_id, doc_id, {})
|
||||
.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
it("should flush project history", function() {
|
||||
return this.HistoryManager.flushProjectChanges
|
||||
.calledWith(this.project_id, {})
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should flush project history', function () {
|
||||
return this.HistoryManager.flushProjectChanges
|
||||
.calledWith(this.project_id, {})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback without error", function() {
|
||||
return this.callback.calledWith(null).should.equal(true);
|
||||
});
|
||||
it('should call the callback without error', function () {
|
||||
return this.callback.calledWith(null).should.equal(true)
|
||||
})
|
||||
|
||||
return it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("when a doc errors", function() {
|
||||
beforeEach(function(done) {
|
||||
this.doc_ids = ["doc-id-1", "doc-id-2", "doc-id-3"];
|
||||
this.RedisManager.getDocIdsInProject = sinon.stub().callsArgWith(1, null, this.doc_ids);
|
||||
this.DocumentManager.flushAndDeleteDocWithLock = sinon.spy((project_id, doc_id, options, callback) => {
|
||||
if (doc_id === "doc-id-1") {
|
||||
return callback(this.error = new Error("oops, something went wrong"));
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
});
|
||||
return this.ProjectManager.flushAndDeleteProjectWithLocks(this.project_id, {}, error => {
|
||||
this.callback(error);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return describe('when a doc errors', function () {
|
||||
beforeEach(function (done) {
|
||||
this.doc_ids = ['doc-id-1', 'doc-id-2', 'doc-id-3']
|
||||
this.RedisManager.getDocIdsInProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.doc_ids)
|
||||
this.DocumentManager.flushAndDeleteDocWithLock = sinon.spy(
|
||||
(project_id, doc_id, options, callback) => {
|
||||
if (doc_id === 'doc-id-1') {
|
||||
return callback(
|
||||
(this.error = new Error('oops, something went wrong'))
|
||||
)
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
)
|
||||
return this.ProjectManager.flushAndDeleteProjectWithLocks(
|
||||
this.project_id,
|
||||
{},
|
||||
(error) => {
|
||||
this.callback(error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should still flush each doc in the project", function() {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushAndDeleteDocWithLock
|
||||
.calledWith(this.project_id, doc_id, {})
|
||||
.should.equal(true));
|
||||
});
|
||||
it('should still flush each doc in the project', function () {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushAndDeleteDocWithLock
|
||||
.calledWith(this.project_id, doc_id, {})
|
||||
.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
it("should still flush project history", function() {
|
||||
return this.HistoryManager.flushProjectChanges
|
||||
.calledWith(this.project_id, {})
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should still flush project history', function () {
|
||||
return this.HistoryManager.flushProjectChanges
|
||||
.calledWith(this.project_id, {})
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should record the error", function() {
|
||||
return this.logger.error
|
||||
.calledWith({err: this.error, project_id: this.project_id, doc_id: "doc-id-1"}, "error deleting doc")
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should record the error', function () {
|
||||
return this.logger.error
|
||||
.calledWith(
|
||||
{ err: this.error, project_id: this.project_id, doc_id: 'doc-id-1' },
|
||||
'error deleting doc'
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback with an error", function() {
|
||||
return this.callback.calledWith(new Error()).should.equal(true);
|
||||
});
|
||||
it('should call the callback with an error', function () {
|
||||
return this.callback.calledWith(new Error()).should.equal(true)
|
||||
})
|
||||
|
||||
return it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -14,109 +14,133 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* 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/ProjectManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/ProjectManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe("ProjectManager - flushProject", function() {
|
||||
beforeEach(function() {
|
||||
let Timer;
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"./RedisManager": (this.RedisManager = {}),
|
||||
"./ProjectHistoryRedisManager": (this.ProjectHistoryRedisManager = {}),
|
||||
"./DocumentManager": (this.DocumentManager = {}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }),
|
||||
"./HistoryManager": (this.HistoryManager = {}),
|
||||
"./Metrics": (this.Metrics = {
|
||||
Timer: (Timer = (function() {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub();
|
||||
}
|
||||
};
|
||||
Timer.initClass();
|
||||
return Timer;
|
||||
})())
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
this.project_id = "project-id-123";
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
describe('ProjectManager - flushProject', function () {
|
||||
beforeEach(function () {
|
||||
let Timer
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./RedisManager': (this.RedisManager = {}),
|
||||
'./ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
|
||||
'./DocumentManager': (this.DocumentManager = {}),
|
||||
'logger-sharelatex': (this.logger = {
|
||||
log: sinon.stub(),
|
||||
error: sinon.stub()
|
||||
}),
|
||||
'./HistoryManager': (this.HistoryManager = {}),
|
||||
'./Metrics': (this.Metrics = {
|
||||
Timer: (Timer = (function () {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub()
|
||||
}
|
||||
}
|
||||
Timer.initClass()
|
||||
return Timer
|
||||
})())
|
||||
})
|
||||
}
|
||||
})
|
||||
this.project_id = 'project-id-123'
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function(done) {
|
||||
this.doc_ids = ["doc-id-1", "doc-id-2", "doc-id-3"];
|
||||
this.RedisManager.getDocIdsInProject = sinon.stub().callsArgWith(1, null, this.doc_ids);
|
||||
this.DocumentManager.flushDocIfLoadedWithLock = sinon.stub().callsArg(2);
|
||||
return this.ProjectManager.flushProjectWithLocks(this.project_id, error => {
|
||||
this.callback(error);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('successfully', function () {
|
||||
beforeEach(function (done) {
|
||||
this.doc_ids = ['doc-id-1', 'doc-id-2', 'doc-id-3']
|
||||
this.RedisManager.getDocIdsInProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.doc_ids)
|
||||
this.DocumentManager.flushDocIfLoadedWithLock = sinon.stub().callsArg(2)
|
||||
return this.ProjectManager.flushProjectWithLocks(
|
||||
this.project_id,
|
||||
(error) => {
|
||||
this.callback(error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should get the doc ids in the project", function() {
|
||||
return this.RedisManager.getDocIdsInProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
|
||||
it("should flush each doc in the project", function() {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushDocIfLoadedWithLock
|
||||
.calledWith(this.project_id, doc_id)
|
||||
.should.equal(true));
|
||||
});
|
||||
it('should get the doc ids in the project', function () {
|
||||
return this.RedisManager.getDocIdsInProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback without error", function() {
|
||||
return this.callback.calledWith(null).should.equal(true);
|
||||
});
|
||||
it('should flush each doc in the project', function () {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushDocIfLoadedWithLock
|
||||
.calledWith(this.project_id, doc_id)
|
||||
.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
return it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
it('should call the callback without error', function () {
|
||||
return this.callback.calledWith(null).should.equal(true)
|
||||
})
|
||||
|
||||
return describe("when a doc errors", function() {
|
||||
beforeEach(function(done) {
|
||||
this.doc_ids = ["doc-id-1", "doc-id-2", "doc-id-3"];
|
||||
this.RedisManager.getDocIdsInProject = sinon.stub().callsArgWith(1, null, this.doc_ids);
|
||||
this.DocumentManager.flushDocIfLoadedWithLock = sinon.spy((project_id, doc_id, callback) => {
|
||||
if (callback == null) { callback = function(error) {}; }
|
||||
if (doc_id === "doc-id-1") {
|
||||
return callback(this.error = new Error("oops, something went wrong"));
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
});
|
||||
return this.ProjectManager.flushProjectWithLocks(this.project_id, error => {
|
||||
this.callback(error);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should still flush each doc in the project", function() {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushDocIfLoadedWithLock
|
||||
.calledWith(this.project_id, doc_id)
|
||||
.should.equal(true));
|
||||
});
|
||||
return it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should record the error", function() {
|
||||
return this.logger.error
|
||||
.calledWith({err: this.error, project_id: this.project_id, doc_id: "doc-id-1"}, "error flushing doc")
|
||||
.should.equal(true);
|
||||
});
|
||||
return describe('when a doc errors', function () {
|
||||
beforeEach(function (done) {
|
||||
this.doc_ids = ['doc-id-1', 'doc-id-2', 'doc-id-3']
|
||||
this.RedisManager.getDocIdsInProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.doc_ids)
|
||||
this.DocumentManager.flushDocIfLoadedWithLock = sinon.spy(
|
||||
(project_id, doc_id, callback) => {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
if (doc_id === 'doc-id-1') {
|
||||
return callback(
|
||||
(this.error = new Error('oops, something went wrong'))
|
||||
)
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
)
|
||||
return this.ProjectManager.flushProjectWithLocks(
|
||||
this.project_id,
|
||||
(error) => {
|
||||
this.callback(error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should call the callback with an error", function() {
|
||||
return this.callback.calledWith(new Error()).should.equal(true);
|
||||
});
|
||||
it('should still flush each doc in the project', function () {
|
||||
return Array.from(this.doc_ids).map((doc_id) =>
|
||||
this.DocumentManager.flushDocIfLoadedWithLock
|
||||
.calledWith(this.project_id, doc_id)
|
||||
.should.equal(true)
|
||||
)
|
||||
})
|
||||
|
||||
return it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should record the error', function () {
|
||||
return this.logger.error
|
||||
.calledWith(
|
||||
{ err: this.error, project_id: this.project_id, doc_id: 'doc-id-1' },
|
||||
'error flushing doc'
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback with an error', function () {
|
||||
return this.callback.calledWith(new Error()).should.equal(true)
|
||||
})
|
||||
|
||||
return it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,158 +10,215 @@
|
|||
* 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/ProjectManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const Errors = require("../../../../app/js/Errors.js");
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/ProjectManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const Errors = require('../../../../app/js/Errors.js')
|
||||
|
||||
describe("ProjectManager - getProjectDocsAndFlushIfOld", function() {
|
||||
beforeEach(function() {
|
||||
let Timer;
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"./RedisManager": (this.RedisManager = {}),
|
||||
"./ProjectHistoryRedisManager": (this.ProjectHistoryRedisManager = {}),
|
||||
"./DocumentManager": (this.DocumentManager = {}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }),
|
||||
"./HistoryManager": (this.HistoryManager = {}),
|
||||
"./Metrics": (this.Metrics = {
|
||||
Timer: (Timer = (function() {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub();
|
||||
}
|
||||
};
|
||||
Timer.initClass();
|
||||
return Timer;
|
||||
})())
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
this.project_id = "project-id-123";
|
||||
this.callback = sinon.stub();
|
||||
return this.doc_versions = [111, 222, 333];});
|
||||
describe('ProjectManager - getProjectDocsAndFlushIfOld', function () {
|
||||
beforeEach(function () {
|
||||
let Timer
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./RedisManager': (this.RedisManager = {}),
|
||||
'./ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
|
||||
'./DocumentManager': (this.DocumentManager = {}),
|
||||
'logger-sharelatex': (this.logger = {
|
||||
log: sinon.stub(),
|
||||
error: sinon.stub()
|
||||
}),
|
||||
'./HistoryManager': (this.HistoryManager = {}),
|
||||
'./Metrics': (this.Metrics = {
|
||||
Timer: (Timer = (function () {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub()
|
||||
}
|
||||
}
|
||||
Timer.initClass()
|
||||
return Timer
|
||||
})())
|
||||
})
|
||||
}
|
||||
})
|
||||
this.project_id = 'project-id-123'
|
||||
this.callback = sinon.stub()
|
||||
return (this.doc_versions = [111, 222, 333])
|
||||
})
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function(done) {
|
||||
this.doc_ids = ["doc-id-1", "doc-id-2", "doc-id-3"];
|
||||
this.doc_lines = [["aaa","aaa"],["bbb","bbb"],["ccc","ccc"]];
|
||||
this.docs = [
|
||||
{_id: this.doc_ids[0], lines: this.doc_lines[0], v: this.doc_versions[0]},
|
||||
{_id: this.doc_ids[1], lines: this.doc_lines[1], v: this.doc_versions[1]},
|
||||
{_id: this.doc_ids[2], lines: this.doc_lines[2], v: this.doc_versions[2]}
|
||||
];
|
||||
this.RedisManager.checkOrSetProjectState = sinon.stub().callsArgWith(2, null);
|
||||
this.RedisManager.getDocIdsInProject = sinon.stub().callsArgWith(1, null, this.doc_ids);
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock = sinon.stub();
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock.withArgs(this.project_id, this.doc_ids[0])
|
||||
.callsArgWith(2, null, this.doc_lines[0], this.doc_versions[0]);
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock.withArgs(this.project_id, this.doc_ids[1])
|
||||
.callsArgWith(2, null, this.doc_lines[1], this.doc_versions[1]);
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock.withArgs(this.project_id, this.doc_ids[2])
|
||||
.callsArgWith(2, null, this.doc_lines[2], this.doc_versions[2]);
|
||||
return this.ProjectManager.getProjectDocsAndFlushIfOld(this.project_id, this.projectStateHash, this.excludeVersions, (error, docs) => {
|
||||
this.callback(error, docs);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('successfully', function () {
|
||||
beforeEach(function (done) {
|
||||
this.doc_ids = ['doc-id-1', 'doc-id-2', 'doc-id-3']
|
||||
this.doc_lines = [
|
||||
['aaa', 'aaa'],
|
||||
['bbb', 'bbb'],
|
||||
['ccc', 'ccc']
|
||||
]
|
||||
this.docs = [
|
||||
{
|
||||
_id: this.doc_ids[0],
|
||||
lines: this.doc_lines[0],
|
||||
v: this.doc_versions[0]
|
||||
},
|
||||
{
|
||||
_id: this.doc_ids[1],
|
||||
lines: this.doc_lines[1],
|
||||
v: this.doc_versions[1]
|
||||
},
|
||||
{
|
||||
_id: this.doc_ids[2],
|
||||
lines: this.doc_lines[2],
|
||||
v: this.doc_versions[2]
|
||||
}
|
||||
]
|
||||
this.RedisManager.checkOrSetProjectState = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null)
|
||||
this.RedisManager.getDocIdsInProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.doc_ids)
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock = sinon.stub()
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock
|
||||
.withArgs(this.project_id, this.doc_ids[0])
|
||||
.callsArgWith(2, null, this.doc_lines[0], this.doc_versions[0])
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock
|
||||
.withArgs(this.project_id, this.doc_ids[1])
|
||||
.callsArgWith(2, null, this.doc_lines[1], this.doc_versions[1])
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock
|
||||
.withArgs(this.project_id, this.doc_ids[2])
|
||||
.callsArgWith(2, null, this.doc_lines[2], this.doc_versions[2])
|
||||
return this.ProjectManager.getProjectDocsAndFlushIfOld(
|
||||
this.project_id,
|
||||
this.projectStateHash,
|
||||
this.excludeVersions,
|
||||
(error, docs) => {
|
||||
this.callback(error, docs)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should check the project state", function() {
|
||||
return this.RedisManager.checkOrSetProjectState
|
||||
.calledWith(this.project_id, this.projectStateHash)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should check the project state', function () {
|
||||
return this.RedisManager.checkOrSetProjectState
|
||||
.calledWith(this.project_id, this.projectStateHash)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should get the doc ids in the project", function() {
|
||||
return this.RedisManager.getDocIdsInProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should get the doc ids in the project', function () {
|
||||
return this.RedisManager.getDocIdsInProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback without error", function() {
|
||||
return this.callback.calledWith(null, this.docs).should.equal(true);
|
||||
});
|
||||
it('should call the callback without error', function () {
|
||||
return this.callback.calledWith(null, this.docs).should.equal(true)
|
||||
})
|
||||
|
||||
return it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the state does not match", function() {
|
||||
beforeEach(function(done) {
|
||||
this.doc_ids = ["doc-id-1", "doc-id-2", "doc-id-3"];
|
||||
this.RedisManager.checkOrSetProjectState = sinon.stub().callsArgWith(2, null, true);
|
||||
return this.ProjectManager.getProjectDocsAndFlushIfOld(this.project_id, this.projectStateHash, this.excludeVersions, (error, docs) => {
|
||||
this.callback(error, docs);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('when the state does not match', function () {
|
||||
beforeEach(function (done) {
|
||||
this.doc_ids = ['doc-id-1', 'doc-id-2', 'doc-id-3']
|
||||
this.RedisManager.checkOrSetProjectState = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, true)
|
||||
return this.ProjectManager.getProjectDocsAndFlushIfOld(
|
||||
this.project_id,
|
||||
this.projectStateHash,
|
||||
this.excludeVersions,
|
||||
(error, docs) => {
|
||||
this.callback(error, docs)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should check the project state", function() {
|
||||
return this.RedisManager.checkOrSetProjectState
|
||||
.calledWith(this.project_id, this.projectStateHash)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should check the project state', function () {
|
||||
return this.RedisManager.checkOrSetProjectState
|
||||
.calledWith(this.project_id, this.projectStateHash)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback with an error", function() {
|
||||
return this.callback.calledWith(new Errors.ProjectStateChangedError("project state changed")).should.equal(true);
|
||||
});
|
||||
it('should call the callback with an error', function () {
|
||||
return this.callback
|
||||
.calledWith(
|
||||
new Errors.ProjectStateChangedError('project state changed')
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when a doc errors", function() {
|
||||
beforeEach(function(done) {
|
||||
this.doc_ids = ["doc-id-1", "doc-id-2", "doc-id-3"];
|
||||
this.RedisManager.checkOrSetProjectState = sinon.stub().callsArgWith(2, null);
|
||||
this.RedisManager.getDocIdsInProject = sinon.stub().callsArgWith(1, null, this.doc_ids);
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock = sinon.stub();
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock.withArgs(this.project_id, "doc-id-1")
|
||||
.callsArgWith(2, null, ["test doc content"], this.doc_versions[1]);
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock.withArgs(this.project_id, "doc-id-2")
|
||||
.callsArgWith(2, (this.error = new Error("oops"))); // trigger an error
|
||||
return this.ProjectManager.getProjectDocsAndFlushIfOld(this.project_id, this.projectStateHash, this.excludeVersions, (error, docs) => {
|
||||
this.callback(error);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('when a doc errors', function () {
|
||||
beforeEach(function (done) {
|
||||
this.doc_ids = ['doc-id-1', 'doc-id-2', 'doc-id-3']
|
||||
this.RedisManager.checkOrSetProjectState = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null)
|
||||
this.RedisManager.getDocIdsInProject = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, this.doc_ids)
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock = sinon.stub()
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock
|
||||
.withArgs(this.project_id, 'doc-id-1')
|
||||
.callsArgWith(2, null, ['test doc content'], this.doc_versions[1])
|
||||
this.DocumentManager.getDocAndFlushIfOldWithLock
|
||||
.withArgs(this.project_id, 'doc-id-2')
|
||||
.callsArgWith(2, (this.error = new Error('oops'))) // trigger an error
|
||||
return this.ProjectManager.getProjectDocsAndFlushIfOld(
|
||||
this.project_id,
|
||||
this.projectStateHash,
|
||||
this.excludeVersions,
|
||||
(error, docs) => {
|
||||
this.callback(error)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should record the error", function() {
|
||||
return this.logger.error
|
||||
.calledWith({err: this.error, project_id: this.project_id, doc_id: "doc-id-2"}, "error getting project doc lines in getProjectDocsAndFlushIfOld")
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should record the error', function () {
|
||||
return this.logger.error
|
||||
.calledWith(
|
||||
{ err: this.error, project_id: this.project_id, doc_id: 'doc-id-2' },
|
||||
'error getting project doc lines in getProjectDocsAndFlushIfOld'
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback with an error", function() {
|
||||
return this.callback.calledWith(new Error("oops")).should.equal(true);
|
||||
});
|
||||
it('should call the callback with an error', function () {
|
||||
return this.callback.calledWith(new Error('oops')).should.equal(true)
|
||||
})
|
||||
|
||||
return it("should time the execution", function() {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should time the execution', function () {
|
||||
return this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("clearing the project state with clearProjectState", function() {
|
||||
beforeEach(function(done) {
|
||||
this.RedisManager.clearProjectState = sinon.stub().callsArg(1);
|
||||
return this.ProjectManager.clearProjectState(this.project_id, error => {
|
||||
this.callback(error);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return describe('clearing the project state with clearProjectState', function () {
|
||||
beforeEach(function (done) {
|
||||
this.RedisManager.clearProjectState = sinon.stub().callsArg(1)
|
||||
return this.ProjectManager.clearProjectState(this.project_id, (error) => {
|
||||
this.callback(error)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it("should clear the project state", function() {
|
||||
return this.RedisManager.clearProjectState
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should clear the project state', function () {
|
||||
return this.RedisManager.clearProjectState
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,239 +10,393 @@
|
|||
* 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/ProjectManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const _ = require('lodash');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/ProjectManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const _ = require('lodash')
|
||||
|
||||
describe("ProjectManager", function() {
|
||||
beforeEach(function() {
|
||||
let Timer;
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"./RedisManager": (this.RedisManager = {}),
|
||||
"./ProjectHistoryRedisManager": (this.ProjectHistoryRedisManager = {}),
|
||||
"./DocumentManager": (this.DocumentManager = {}),
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub(), error: sinon.stub() }),
|
||||
"./HistoryManager": (this.HistoryManager = {}),
|
||||
"./Metrics": (this.Metrics = {
|
||||
Timer: (Timer = (function() {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub();
|
||||
}
|
||||
};
|
||||
Timer.initClass();
|
||||
return Timer;
|
||||
})())
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
describe('ProjectManager', function () {
|
||||
beforeEach(function () {
|
||||
let Timer
|
||||
this.ProjectManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./RedisManager': (this.RedisManager = {}),
|
||||
'./ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
|
||||
'./DocumentManager': (this.DocumentManager = {}),
|
||||
'logger-sharelatex': (this.logger = {
|
||||
log: sinon.stub(),
|
||||
error: sinon.stub()
|
||||
}),
|
||||
'./HistoryManager': (this.HistoryManager = {}),
|
||||
'./Metrics': (this.Metrics = {
|
||||
Timer: (Timer = (function () {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub()
|
||||
}
|
||||
}
|
||||
Timer.initClass()
|
||||
return Timer
|
||||
})())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.project_id = "project-id-123";
|
||||
this.projectHistoryId = 'history-id-123';
|
||||
this.user_id = "user-id-123";
|
||||
this.version = 1234567;
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(false);
|
||||
this.HistoryManager.flushProjectChangesAsync = sinon.stub();
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
this.project_id = 'project-id-123'
|
||||
this.projectHistoryId = 'history-id-123'
|
||||
this.user_id = 'user-id-123'
|
||||
this.version = 1234567
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(false)
|
||||
this.HistoryManager.flushProjectChangesAsync = sinon.stub()
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
return describe("updateProjectWithLocks", function() {
|
||||
describe("rename operations", function() {
|
||||
beforeEach(function() {
|
||||
this.firstDocUpdate = {
|
||||
id: 1,
|
||||
pathname: 'foo',
|
||||
newPathname: 'foo'
|
||||
};
|
||||
this.secondDocUpdate = {
|
||||
id: 2,
|
||||
pathname: 'bar',
|
||||
newPathname: 'bar2'
|
||||
};
|
||||
this.docUpdates = [ this.firstDocUpdate, this.secondDocUpdate ];
|
||||
this.firstFileUpdate = {
|
||||
id: 2,
|
||||
pathname: 'bar',
|
||||
newPathname: 'bar2'
|
||||
};
|
||||
this.fileUpdates = [ this.firstFileUpdate ];
|
||||
this.DocumentManager.renameDocWithLock = sinon.stub().yields();
|
||||
return this.ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields();
|
||||
});
|
||||
return describe('updateProjectWithLocks', function () {
|
||||
describe('rename operations', function () {
|
||||
beforeEach(function () {
|
||||
this.firstDocUpdate = {
|
||||
id: 1,
|
||||
pathname: 'foo',
|
||||
newPathname: 'foo'
|
||||
}
|
||||
this.secondDocUpdate = {
|
||||
id: 2,
|
||||
pathname: 'bar',
|
||||
newPathname: 'bar2'
|
||||
}
|
||||
this.docUpdates = [this.firstDocUpdate, this.secondDocUpdate]
|
||||
this.firstFileUpdate = {
|
||||
id: 2,
|
||||
pathname: 'bar',
|
||||
newPathname: 'bar2'
|
||||
}
|
||||
this.fileUpdates = [this.firstFileUpdate]
|
||||
this.DocumentManager.renameDocWithLock = sinon.stub().yields()
|
||||
return (this.ProjectHistoryRedisManager.queueRenameEntity = sinon
|
||||
.stub()
|
||||
.yields())
|
||||
})
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
describe('successfully', function () {
|
||||
beforeEach(function () {
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should rename the docs in the updates", function() {
|
||||
const firstDocUpdateWithVersion = _.extend({}, this.firstDocUpdate, {version: `${this.version}.0`});
|
||||
const secondDocUpdateWithVersion = _.extend({}, this.secondDocUpdate, {version: `${this.version}.1`});
|
||||
this.DocumentManager.renameDocWithLock
|
||||
.calledWith(this.project_id, this.firstDocUpdate.id, this.user_id, firstDocUpdateWithVersion, this.projectHistoryId)
|
||||
.should.equal(true);
|
||||
return this.DocumentManager.renameDocWithLock
|
||||
.calledWith(this.project_id, this.secondDocUpdate.id, this.user_id, secondDocUpdateWithVersion, this.projectHistoryId)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should rename the docs in the updates', function () {
|
||||
const firstDocUpdateWithVersion = _.extend({}, this.firstDocUpdate, {
|
||||
version: `${this.version}.0`
|
||||
})
|
||||
const secondDocUpdateWithVersion = _.extend(
|
||||
{},
|
||||
this.secondDocUpdate,
|
||||
{ version: `${this.version}.1` }
|
||||
)
|
||||
this.DocumentManager.renameDocWithLock
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.firstDocUpdate.id,
|
||||
this.user_id,
|
||||
firstDocUpdateWithVersion,
|
||||
this.projectHistoryId
|
||||
)
|
||||
.should.equal(true)
|
||||
return this.DocumentManager.renameDocWithLock
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.secondDocUpdate.id,
|
||||
this.user_id,
|
||||
secondDocUpdateWithVersion,
|
||||
this.projectHistoryId
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should rename the files in the updates", function() {
|
||||
const firstFileUpdateWithVersion = _.extend({}, this.firstFileUpdate, {version: `${this.version}.2`});
|
||||
return this.ProjectHistoryRedisManager.queueRenameEntity
|
||||
.calledWith(this.project_id, this.projectHistoryId, 'file', this.firstFileUpdate.id, this.user_id, firstFileUpdateWithVersion)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should rename the files in the updates', function () {
|
||||
const firstFileUpdateWithVersion = _.extend(
|
||||
{},
|
||||
this.firstFileUpdate,
|
||||
{ version: `${this.version}.2` }
|
||||
)
|
||||
return this.ProjectHistoryRedisManager.queueRenameEntity
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
'file',
|
||||
this.firstFileUpdate.id,
|
||||
this.user_id,
|
||||
firstFileUpdateWithVersion
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should not flush the history", function() {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(false);
|
||||
});
|
||||
it('should not flush the history', function () {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(false)
|
||||
})
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when renaming a doc fails", function() {
|
||||
beforeEach(function() {
|
||||
this.error = new Error('error');
|
||||
this.DocumentManager.renameDocWithLock = sinon.stub().yields(this.error);
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
describe('when renaming a doc fails', function () {
|
||||
beforeEach(function () {
|
||||
this.error = new Error('error')
|
||||
this.DocumentManager.renameDocWithLock = sinon
|
||||
.stub()
|
||||
.yields(this.error)
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when renaming a file fails", function() {
|
||||
beforeEach(function() {
|
||||
this.error = new Error('error');
|
||||
this.ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields(this.error);
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
describe('when renaming a file fails', function () {
|
||||
beforeEach(function () {
|
||||
this.error = new Error('error')
|
||||
this.ProjectHistoryRedisManager.queueRenameEntity = sinon
|
||||
.stub()
|
||||
.yields(this.error)
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("with enough ops to flush", function() {
|
||||
beforeEach(function() {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true);
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
return describe('with enough ops to flush', function () {
|
||||
beforeEach(function () {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should flush the history", function() {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should flush the history', function () {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return describe("add operations", function() {
|
||||
beforeEach(function() {
|
||||
this.firstDocUpdate = {
|
||||
id: 1,
|
||||
docLines: "a\nb"
|
||||
};
|
||||
this.secondDocUpdate = {
|
||||
id: 2,
|
||||
docLines: "a\nb"
|
||||
};
|
||||
this.docUpdates = [ this.firstDocUpdate, this.secondDocUpdate ];
|
||||
this.firstFileUpdate = {
|
||||
id: 3,
|
||||
url: 'filestore.example.com/2'
|
||||
};
|
||||
this.secondFileUpdate = {
|
||||
id: 4,
|
||||
url: 'filestore.example.com/3'
|
||||
};
|
||||
this.fileUpdates = [ this.firstFileUpdate, this.secondFileUpdate ];
|
||||
return this.ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields();
|
||||
});
|
||||
return describe('add operations', function () {
|
||||
beforeEach(function () {
|
||||
this.firstDocUpdate = {
|
||||
id: 1,
|
||||
docLines: 'a\nb'
|
||||
}
|
||||
this.secondDocUpdate = {
|
||||
id: 2,
|
||||
docLines: 'a\nb'
|
||||
}
|
||||
this.docUpdates = [this.firstDocUpdate, this.secondDocUpdate]
|
||||
this.firstFileUpdate = {
|
||||
id: 3,
|
||||
url: 'filestore.example.com/2'
|
||||
}
|
||||
this.secondFileUpdate = {
|
||||
id: 4,
|
||||
url: 'filestore.example.com/3'
|
||||
}
|
||||
this.fileUpdates = [this.firstFileUpdate, this.secondFileUpdate]
|
||||
return (this.ProjectHistoryRedisManager.queueAddEntity = sinon
|
||||
.stub()
|
||||
.yields())
|
||||
})
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
describe('successfully', function () {
|
||||
beforeEach(function () {
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should add the docs in the updates", function() {
|
||||
const firstDocUpdateWithVersion = _.extend({}, this.firstDocUpdate, {version: `${this.version}.0`});
|
||||
const secondDocUpdateWithVersion = _.extend({}, this.secondDocUpdate, {version: `${this.version}.1`});
|
||||
this.ProjectHistoryRedisManager.queueAddEntity.getCall(0)
|
||||
.calledWith(this.project_id, this.projectHistoryId, 'doc', this.firstDocUpdate.id, this.user_id, firstDocUpdateWithVersion)
|
||||
.should.equal(true);
|
||||
return this.ProjectHistoryRedisManager.queueAddEntity.getCall(1)
|
||||
.calledWith(this.project_id, this.projectHistoryId, 'doc', this.secondDocUpdate.id, this.user_id, secondDocUpdateWithVersion)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should add the docs in the updates', function () {
|
||||
const firstDocUpdateWithVersion = _.extend({}, this.firstDocUpdate, {
|
||||
version: `${this.version}.0`
|
||||
})
|
||||
const secondDocUpdateWithVersion = _.extend(
|
||||
{},
|
||||
this.secondDocUpdate,
|
||||
{ version: `${this.version}.1` }
|
||||
)
|
||||
this.ProjectHistoryRedisManager.queueAddEntity
|
||||
.getCall(0)
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
'doc',
|
||||
this.firstDocUpdate.id,
|
||||
this.user_id,
|
||||
firstDocUpdateWithVersion
|
||||
)
|
||||
.should.equal(true)
|
||||
return this.ProjectHistoryRedisManager.queueAddEntity
|
||||
.getCall(1)
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
'doc',
|
||||
this.secondDocUpdate.id,
|
||||
this.user_id,
|
||||
secondDocUpdateWithVersion
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should add the files in the updates", function() {
|
||||
const firstFileUpdateWithVersion = _.extend({}, this.firstFileUpdate, {version: `${this.version}.2`});
|
||||
const secondFileUpdateWithVersion = _.extend({}, this.secondFileUpdate, {version: `${this.version}.3`});
|
||||
this.ProjectHistoryRedisManager.queueAddEntity.getCall(2)
|
||||
.calledWith(this.project_id, this.projectHistoryId, 'file', this.firstFileUpdate.id, this.user_id, firstFileUpdateWithVersion)
|
||||
.should.equal(true);
|
||||
return this.ProjectHistoryRedisManager.queueAddEntity.getCall(3)
|
||||
.calledWith(this.project_id, this.projectHistoryId, 'file', this.secondFileUpdate.id, this.user_id, secondFileUpdateWithVersion)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should add the files in the updates', function () {
|
||||
const firstFileUpdateWithVersion = _.extend(
|
||||
{},
|
||||
this.firstFileUpdate,
|
||||
{ version: `${this.version}.2` }
|
||||
)
|
||||
const secondFileUpdateWithVersion = _.extend(
|
||||
{},
|
||||
this.secondFileUpdate,
|
||||
{ version: `${this.version}.3` }
|
||||
)
|
||||
this.ProjectHistoryRedisManager.queueAddEntity
|
||||
.getCall(2)
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
'file',
|
||||
this.firstFileUpdate.id,
|
||||
this.user_id,
|
||||
firstFileUpdateWithVersion
|
||||
)
|
||||
.should.equal(true)
|
||||
return this.ProjectHistoryRedisManager.queueAddEntity
|
||||
.getCall(3)
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
'file',
|
||||
this.secondFileUpdate.id,
|
||||
this.user_id,
|
||||
secondFileUpdateWithVersion
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should not flush the history", function() {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(false);
|
||||
});
|
||||
it('should not flush the history', function () {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(false)
|
||||
})
|
||||
|
||||
return it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when adding a doc fails", function() {
|
||||
beforeEach(function() {
|
||||
this.error = new Error('error');
|
||||
this.ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(this.error);
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
describe('when adding a doc fails', function () {
|
||||
beforeEach(function () {
|
||||
this.error = new Error('error')
|
||||
this.ProjectHistoryRedisManager.queueAddEntity = sinon
|
||||
.stub()
|
||||
.yields(this.error)
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when adding a file fails", function() {
|
||||
beforeEach(function() {
|
||||
this.error = new Error('error');
|
||||
this.ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(this.error);
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
describe('when adding a file fails', function () {
|
||||
beforeEach(function () {
|
||||
this.error = new Error('error')
|
||||
this.ProjectHistoryRedisManager.queueAddEntity = sinon
|
||||
.stub()
|
||||
.yields(this.error)
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("with enough ops to flush", function() {
|
||||
beforeEach(function() {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true);
|
||||
return this.ProjectManager.updateProjectWithLocks(this.project_id, this.projectHistoryId, this.user_id, this.docUpdates, this.fileUpdates, this.version, this.callback);
|
||||
});
|
||||
return describe('with enough ops to flush', function () {
|
||||
beforeEach(function () {
|
||||
this.HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
|
||||
return this.ProjectManager.updateProjectWithLocks(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.version,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should flush the history", function() {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should flush the history', function () {
|
||||
return this.HistoryManager.flushProjectChangesAsync
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,392 +12,524 @@
|
|||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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 {
|
||||
expect
|
||||
} = chai;
|
||||
const modulePath = "../../../../app/js/RangesManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const { expect } = chai
|
||||
const modulePath = '../../../../app/js/RangesManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe("RangesManager", function() {
|
||||
beforeEach(function() {
|
||||
this.RangesManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
"logger-sharelatex": (this.logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() })
|
||||
}
|
||||
});
|
||||
describe('RangesManager', function () {
|
||||
beforeEach(function () {
|
||||
this.RangesManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'logger-sharelatex': (this.logger = {
|
||||
error: sinon.stub(),
|
||||
log: sinon.stub(),
|
||||
warn: sinon.stub()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.doc_id = "doc-id-123";
|
||||
this.project_id = "project-id-123";
|
||||
this.user_id = "user-id-123";
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
this.doc_id = 'doc-id-123'
|
||||
this.project_id = 'project-id-123'
|
||||
this.user_id = 'user-id-123'
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
describe("applyUpdate", function() {
|
||||
beforeEach(function() {
|
||||
this.updates = [{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [{
|
||||
i: "two ",
|
||||
p: 4
|
||||
}]
|
||||
}];
|
||||
this.entries = {
|
||||
comments: [{
|
||||
op: {
|
||||
c: "three ",
|
||||
p: 4
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}],
|
||||
changes: [{
|
||||
op: {
|
||||
i: "five",
|
||||
p: 15
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}]
|
||||
};
|
||||
return this.newDocLines = ["one two three four five"];}); // old is "one three four five"
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
return this.RangesManager.applyUpdate(this.project_id, this.doc_id, this.entries, this.updates, this.newDocLines, this.callback);
|
||||
});
|
||||
|
||||
return it("should return the modified the comments and changes", function() {
|
||||
this.callback.called.should.equal(true);
|
||||
const [error, entries, ranges_were_collapsed] = Array.from(this.callback.args[0]);
|
||||
expect(error).to.be.null;
|
||||
expect(ranges_were_collapsed).to.equal(false);
|
||||
entries.comments[0].op.should.deep.equal({
|
||||
c: "three ",
|
||||
p: 8
|
||||
});
|
||||
return entries.changes[0].op.should.deep.equal({
|
||||
i: "five",
|
||||
p: 19
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with empty comments", function() {
|
||||
beforeEach(function() {
|
||||
this.entries.comments = [];
|
||||
return this.RangesManager.applyUpdate(this.project_id, this.doc_id, this.entries, this.updates, this.newDocLines, this.callback);
|
||||
});
|
||||
|
||||
return it("should return an object with no comments", function() {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true);
|
||||
const [error, entries] = Array.from(this.callback.args[0]);
|
||||
expect(error).to.be.null;
|
||||
return expect(entries.comments).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe("with empty changes", function() {
|
||||
beforeEach(function() {
|
||||
this.entries.changes = [];
|
||||
return this.RangesManager.applyUpdate(this.project_id, this.doc_id, this.entries, this.updates, this.newDocLines, this.callback);
|
||||
});
|
||||
|
||||
return it("should return an object with no changes", function() {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true);
|
||||
const [error, entries] = Array.from(this.callback.args[0]);
|
||||
expect(error).to.be.null;
|
||||
return expect(entries.changes).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe("with too many comments", function() {
|
||||
beforeEach(function() {
|
||||
this.RangesManager.MAX_COMMENTS = 2;
|
||||
this.updates = [{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [{
|
||||
c: "one",
|
||||
p: 0,
|
||||
t: "thread-id-1"
|
||||
}]
|
||||
}];
|
||||
this.entries = {
|
||||
comments: [{
|
||||
op: {
|
||||
c: "three ",
|
||||
p: 4,
|
||||
t: "thread-id-2"
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}, {
|
||||
op: {
|
||||
c: "four ",
|
||||
p: 10,
|
||||
t: "thread-id-3"
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}],
|
||||
changes: []
|
||||
};
|
||||
return this.RangesManager.applyUpdate(this.project_id, this.doc_id, this.entries, this.updates, this.newDocLines, this.callback);
|
||||
});
|
||||
|
||||
return it("should return an error", function() {
|
||||
this.callback.called.should.equal(true);
|
||||
const [error, entries] = Array.from(this.callback.args[0]);
|
||||
expect(error).to.not.be.null;
|
||||
return expect(error.message).to.equal("too many comments or tracked changes");
|
||||
});
|
||||
});
|
||||
|
||||
describe("with too many changes", function() {
|
||||
beforeEach(function() {
|
||||
this.RangesManager.MAX_CHANGES = 2;
|
||||
this.updates = [{
|
||||
meta: {
|
||||
user_id: this.user_id,
|
||||
tc: "track-changes-id-yes"
|
||||
},
|
||||
op: [{
|
||||
i: "one ",
|
||||
p: 0
|
||||
}]
|
||||
}];
|
||||
this.entries = {
|
||||
changes: [{
|
||||
op: {
|
||||
i: "three",
|
||||
p: 4
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}, {
|
||||
op: {
|
||||
i: "four",
|
||||
p: 10
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}],
|
||||
comments: []
|
||||
};
|
||||
this.newDocLines = ["one two three four"];
|
||||
return this.RangesManager.applyUpdate(this.project_id, this.doc_id, this.entries, this.updates, this.newDocLines, this.callback);
|
||||
});
|
||||
|
||||
return it("should return an error", function() {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true);
|
||||
const [error, entries] = Array.from(this.callback.args[0]);
|
||||
expect(error).to.not.be.null;
|
||||
return expect(error.message).to.equal("too many comments or tracked changes");
|
||||
});
|
||||
});
|
||||
|
||||
describe("inconsistent changes", function() {
|
||||
beforeEach(function() {
|
||||
this.updates = [{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [{
|
||||
c: "doesn't match",
|
||||
p: 0
|
||||
}]
|
||||
}];
|
||||
return this.RangesManager.applyUpdate(this.project_id, this.doc_id, this.entries, this.updates, this.newDocLines, this.callback);
|
||||
});
|
||||
|
||||
return it("should return an error", function() {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true);
|
||||
const [error, entries] = Array.from(this.callback.args[0]);
|
||||
expect(error).to.not.be.null;
|
||||
return expect(error.message).to.equal("Change ({\"op\":{\"i\":\"five\",\"p\":15},\"metadata\":{\"user_id\":\"user-id-123\"}}) doesn't match text (\"our \")");
|
||||
});
|
||||
});
|
||||
describe('applyUpdate', function () {
|
||||
beforeEach(function () {
|
||||
this.updates = [
|
||||
{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [
|
||||
{
|
||||
i: 'two ',
|
||||
p: 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
this.entries = {
|
||||
comments: [
|
||||
{
|
||||
op: {
|
||||
c: 'three ',
|
||||
p: 4
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}
|
||||
],
|
||||
changes: [
|
||||
{
|
||||
op: {
|
||||
i: 'five',
|
||||
p: 15
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
return (this.newDocLines = ['one two three four five'])
|
||||
}) // old is "one three four five"
|
||||
|
||||
describe('successfully', function () {
|
||||
beforeEach(function () {
|
||||
return this.RangesManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.entries,
|
||||
this.updates,
|
||||
this.newDocLines,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return describe("with an update that collapses a range", function() {
|
||||
beforeEach(function() {
|
||||
this.updates = [{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [{
|
||||
d: "one",
|
||||
p: 0,
|
||||
t: "thread-id-1"
|
||||
}]
|
||||
}];
|
||||
this.entries = {
|
||||
comments: [{
|
||||
op: {
|
||||
c: "n",
|
||||
p: 1,
|
||||
t: "thread-id-2"
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}],
|
||||
changes: []
|
||||
};
|
||||
return this.RangesManager.applyUpdate(this.project_id, this.doc_id, this.entries, this.updates, this.newDocLines, this.callback);
|
||||
});
|
||||
return it('should return the modified the comments and changes', function () {
|
||||
this.callback.called.should.equal(true)
|
||||
const [error, entries, ranges_were_collapsed] = Array.from(
|
||||
this.callback.args[0]
|
||||
)
|
||||
expect(error).to.be.null
|
||||
expect(ranges_were_collapsed).to.equal(false)
|
||||
entries.comments[0].op.should.deep.equal({
|
||||
c: 'three ',
|
||||
p: 8
|
||||
})
|
||||
return entries.changes[0].op.should.deep.equal({
|
||||
i: 'five',
|
||||
p: 19
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return it("should return ranges_were_collapsed == true", function() {
|
||||
this.callback.called.should.equal(true);
|
||||
const [error, entries, ranges_were_collapsed] = Array.from(this.callback.args[0]);
|
||||
return expect(ranges_were_collapsed).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('with empty comments', function () {
|
||||
beforeEach(function () {
|
||||
this.entries.comments = []
|
||||
return this.RangesManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.entries,
|
||||
this.updates,
|
||||
this.newDocLines,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return describe("acceptChanges", function() {
|
||||
beforeEach(function() {
|
||||
this.RangesManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
"logger-sharelatex": (this.logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() }),
|
||||
"./RangesTracker":(this.RangesTracker = SandboxedModule.require("../../../../app/js/RangesTracker.js"))
|
||||
}
|
||||
}
|
||||
);
|
||||
return it('should return an object with no comments', function () {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true)
|
||||
const [error, entries] = Array.from(this.callback.args[0])
|
||||
expect(error).to.be.null
|
||||
return expect(entries.comments).to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
this.ranges = {
|
||||
comments: [],
|
||||
changes: [{
|
||||
id: "a1",
|
||||
op: {
|
||||
i: "lorem",
|
||||
p: 0
|
||||
}
|
||||
}, {
|
||||
id: "a2",
|
||||
op: {
|
||||
i: "ipsum",
|
||||
p: 10
|
||||
}
|
||||
}, {
|
||||
id: "a3",
|
||||
op: {
|
||||
i: "dolor",
|
||||
p: 20
|
||||
}
|
||||
}, {
|
||||
id: "a4",
|
||||
op: {
|
||||
i: "sit",
|
||||
p: 30
|
||||
}
|
||||
}, {
|
||||
id: "a5",
|
||||
op: {
|
||||
i: "amet",
|
||||
p: 40
|
||||
}
|
||||
}]
|
||||
};
|
||||
return this.removeChangeIdsSpy = sinon.spy(this.RangesTracker.prototype, "removeChangeIds");
|
||||
});
|
||||
describe('with empty changes', function () {
|
||||
beforeEach(function () {
|
||||
this.entries.changes = []
|
||||
return this.RangesManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.entries,
|
||||
this.updates,
|
||||
this.newDocLines,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
describe("successfully with a single change", function() {
|
||||
beforeEach(function(done) {
|
||||
this.change_ids = [ this.ranges.changes[1].id ];
|
||||
return this.RangesManager.acceptChanges(this.change_ids, this.ranges, (err, ranges) => {
|
||||
this.rangesResponse = ranges;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return it('should return an object with no changes', function () {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true)
|
||||
const [error, entries] = Array.from(this.callback.args[0])
|
||||
expect(error).to.be.null
|
||||
return expect(entries.changes).to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
it("should log the call with the correct number of changes", function() {
|
||||
return this.logger.log
|
||||
.calledWith("accepting 1 changes in ranges")
|
||||
.should.equal(true);
|
||||
});
|
||||
describe('with too many comments', function () {
|
||||
beforeEach(function () {
|
||||
this.RangesManager.MAX_COMMENTS = 2
|
||||
this.updates = [
|
||||
{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [
|
||||
{
|
||||
c: 'one',
|
||||
p: 0,
|
||||
t: 'thread-id-1'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
this.entries = {
|
||||
comments: [
|
||||
{
|
||||
op: {
|
||||
c: 'three ',
|
||||
p: 4,
|
||||
t: 'thread-id-2'
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
},
|
||||
{
|
||||
op: {
|
||||
c: 'four ',
|
||||
p: 10,
|
||||
t: 'thread-id-3'
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}
|
||||
],
|
||||
changes: []
|
||||
}
|
||||
return this.RangesManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.entries,
|
||||
this.updates,
|
||||
this.newDocLines,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should delegate the change removal to the ranges tracker", function() {
|
||||
return this.removeChangeIdsSpy
|
||||
.calledWith(this.change_ids)
|
||||
.should.equal(true);
|
||||
});
|
||||
return it('should return an error', function () {
|
||||
this.callback.called.should.equal(true)
|
||||
const [error, entries] = Array.from(this.callback.args[0])
|
||||
expect(error).to.not.be.null
|
||||
return expect(error.message).to.equal(
|
||||
'too many comments or tracked changes'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("should remove the change", function() {
|
||||
return expect(this.rangesResponse.changes
|
||||
.find(change => change.id === this.ranges.changes[1].id))
|
||||
.to.be.undefined;
|
||||
});
|
||||
describe('with too many changes', function () {
|
||||
beforeEach(function () {
|
||||
this.RangesManager.MAX_CHANGES = 2
|
||||
this.updates = [
|
||||
{
|
||||
meta: {
|
||||
user_id: this.user_id,
|
||||
tc: 'track-changes-id-yes'
|
||||
},
|
||||
op: [
|
||||
{
|
||||
i: 'one ',
|
||||
p: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
this.entries = {
|
||||
changes: [
|
||||
{
|
||||
op: {
|
||||
i: 'three',
|
||||
p: 4
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
},
|
||||
{
|
||||
op: {
|
||||
i: 'four',
|
||||
p: 10
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}
|
||||
],
|
||||
comments: []
|
||||
}
|
||||
this.newDocLines = ['one two three four']
|
||||
return this.RangesManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.entries,
|
||||
this.updates,
|
||||
this.newDocLines,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should return the original number of changes minus 1", function() {
|
||||
return this.rangesResponse.changes.length
|
||||
.should.equal(this.ranges.changes.length - 1);
|
||||
});
|
||||
|
||||
return it("should not touch other changes", function() {
|
||||
return [ 0, 2, 3, 4].map((i) =>
|
||||
expect(this.rangesResponse.changes
|
||||
.find(change => change.id === this.ranges.changes[i].id))
|
||||
.to.deep.equal(this.ranges.changes[i]));
|
||||
});
|
||||
});
|
||||
return it('should return an error', function () {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true)
|
||||
const [error, entries] = Array.from(this.callback.args[0])
|
||||
expect(error).to.not.be.null
|
||||
return expect(error.message).to.equal(
|
||||
'too many comments or tracked changes'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("successfully with multiple changes", function() {
|
||||
beforeEach(function(done) {
|
||||
this.change_ids = [ this.ranges.changes[1].id, this.ranges.changes[3].id, this.ranges.changes[4].id ];
|
||||
return this.RangesManager.acceptChanges(this.change_ids, this.ranges, (err, ranges) => {
|
||||
this.rangesResponse = ranges;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('inconsistent changes', function () {
|
||||
beforeEach(function () {
|
||||
this.updates = [
|
||||
{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [
|
||||
{
|
||||
c: "doesn't match",
|
||||
p: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
return this.RangesManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.entries,
|
||||
this.updates,
|
||||
this.newDocLines,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should log the call with the correct number of changes", function() {
|
||||
return this.logger.log
|
||||
.calledWith(`accepting ${ this.change_ids.length } changes in ranges`)
|
||||
.should.equal(true);
|
||||
});
|
||||
return it('should return an error', function () {
|
||||
// Save space in redis and don't store just {}
|
||||
this.callback.called.should.equal(true)
|
||||
const [error, entries] = Array.from(this.callback.args[0])
|
||||
expect(error).to.not.be.null
|
||||
return expect(error.message).to.equal(
|
||||
'Change ({"op":{"i":"five","p":15},"metadata":{"user_id":"user-id-123"}}) doesn\'t match text ("our ")'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("should delegate the change removal to the ranges tracker", function() {
|
||||
return this.removeChangeIdsSpy
|
||||
.calledWith(this.change_ids)
|
||||
.should.equal(true);
|
||||
});
|
||||
return describe('with an update that collapses a range', function () {
|
||||
beforeEach(function () {
|
||||
this.updates = [
|
||||
{
|
||||
meta: {
|
||||
user_id: this.user_id
|
||||
},
|
||||
op: [
|
||||
{
|
||||
d: 'one',
|
||||
p: 0,
|
||||
t: 'thread-id-1'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
this.entries = {
|
||||
comments: [
|
||||
{
|
||||
op: {
|
||||
c: 'n',
|
||||
p: 1,
|
||||
t: 'thread-id-2'
|
||||
},
|
||||
metadata: {
|
||||
user_id: this.user_id
|
||||
}
|
||||
}
|
||||
],
|
||||
changes: []
|
||||
}
|
||||
return this.RangesManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.entries,
|
||||
this.updates,
|
||||
this.newDocLines,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should remove the changes", function() {
|
||||
return [ 1, 3, 4].map((i) =>
|
||||
expect(this.rangesResponse.changes
|
||||
.find(change => change.id === this.ranges.changes[1].id))
|
||||
.to.be.undefined);
|
||||
});
|
||||
|
||||
it("should return the original number of changes minus the number of accepted changes", function() {
|
||||
return this.rangesResponse.changes.length
|
||||
.should.equal(this.ranges.changes.length - 3);
|
||||
});
|
||||
return it('should return ranges_were_collapsed == true', function () {
|
||||
this.callback.called.should.equal(true)
|
||||
const [error, entries, ranges_were_collapsed] = Array.from(
|
||||
this.callback.args[0]
|
||||
)
|
||||
return expect(ranges_were_collapsed).to.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return it("should not touch other changes", function() {
|
||||
return [ 0, 2 ].map((i) =>
|
||||
expect(this.rangesResponse.changes
|
||||
.find(change => change.id === this.ranges.changes[i].id))
|
||||
.to.deep.equal(this.ranges.changes[i]));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return describe('acceptChanges', function () {
|
||||
beforeEach(function () {
|
||||
this.RangesManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'logger-sharelatex': (this.logger = {
|
||||
error: sinon.stub(),
|
||||
log: sinon.stub(),
|
||||
warn: sinon.stub()
|
||||
}),
|
||||
'./RangesTracker': (this.RangesTracker = SandboxedModule.require(
|
||||
'../../../../app/js/RangesTracker.js'
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
this.ranges = {
|
||||
comments: [],
|
||||
changes: [
|
||||
{
|
||||
id: 'a1',
|
||||
op: {
|
||||
i: 'lorem',
|
||||
p: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'a2',
|
||||
op: {
|
||||
i: 'ipsum',
|
||||
p: 10
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'a3',
|
||||
op: {
|
||||
i: 'dolor',
|
||||
p: 20
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'a4',
|
||||
op: {
|
||||
i: 'sit',
|
||||
p: 30
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'a5',
|
||||
op: {
|
||||
i: 'amet',
|
||||
p: 40
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
return (this.removeChangeIdsSpy = sinon.spy(
|
||||
this.RangesTracker.prototype,
|
||||
'removeChangeIds'
|
||||
))
|
||||
})
|
||||
|
||||
describe('successfully with a single change', function () {
|
||||
beforeEach(function (done) {
|
||||
this.change_ids = [this.ranges.changes[1].id]
|
||||
return this.RangesManager.acceptChanges(
|
||||
this.change_ids,
|
||||
this.ranges,
|
||||
(err, ranges) => {
|
||||
this.rangesResponse = ranges
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should log the call with the correct number of changes', function () {
|
||||
return this.logger.log
|
||||
.calledWith('accepting 1 changes in ranges')
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should delegate the change removal to the ranges tracker', function () {
|
||||
return this.removeChangeIdsSpy
|
||||
.calledWith(this.change_ids)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should remove the change', function () {
|
||||
return expect(
|
||||
this.rangesResponse.changes.find(
|
||||
(change) => change.id === this.ranges.changes[1].id
|
||||
)
|
||||
).to.be.undefined
|
||||
})
|
||||
|
||||
it('should return the original number of changes minus 1', function () {
|
||||
return this.rangesResponse.changes.length.should.equal(
|
||||
this.ranges.changes.length - 1
|
||||
)
|
||||
})
|
||||
|
||||
return it('should not touch other changes', function () {
|
||||
return [0, 2, 3, 4].map((i) =>
|
||||
expect(
|
||||
this.rangesResponse.changes.find(
|
||||
(change) => change.id === this.ranges.changes[i].id
|
||||
)
|
||||
).to.deep.equal(this.ranges.changes[i])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('successfully with multiple changes', function () {
|
||||
beforeEach(function (done) {
|
||||
this.change_ids = [
|
||||
this.ranges.changes[1].id,
|
||||
this.ranges.changes[3].id,
|
||||
this.ranges.changes[4].id
|
||||
]
|
||||
return this.RangesManager.acceptChanges(
|
||||
this.change_ids,
|
||||
this.ranges,
|
||||
(err, ranges) => {
|
||||
this.rangesResponse = ranges
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should log the call with the correct number of changes', function () {
|
||||
return this.logger.log
|
||||
.calledWith(`accepting ${this.change_ids.length} changes in ranges`)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should delegate the change removal to the ranges tracker', function () {
|
||||
return this.removeChangeIdsSpy
|
||||
.calledWith(this.change_ids)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should remove the changes', function () {
|
||||
return [1, 3, 4].map(
|
||||
(i) =>
|
||||
expect(
|
||||
this.rangesResponse.changes.find(
|
||||
(change) => change.id === this.ranges.changes[1].id
|
||||
)
|
||||
).to.be.undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should return the original number of changes minus the number of accepted changes', function () {
|
||||
return this.rangesResponse.changes.length.should.equal(
|
||||
this.ranges.changes.length - 3
|
||||
)
|
||||
})
|
||||
|
||||
return it('should not touch other changes', function () {
|
||||
return [0, 2].map((i) =>
|
||||
expect(
|
||||
this.rangesResponse.changes.find(
|
||||
(change) => change.id === this.ranges.changes[i].id
|
||||
)
|
||||
).to.deep.equal(this.ranges.changes[i])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,129 +10,127 @@
|
|||
* 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 {
|
||||
expect
|
||||
} = chai;
|
||||
const modulePath = "../../../../app/js/RateLimitManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const { expect } = chai
|
||||
const modulePath = '../../../../app/js/RateLimitManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe("RateLimitManager", function() {
|
||||
beforeEach(function() {
|
||||
let Timer;
|
||||
this.RateLimitManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub() }),
|
||||
"settings-sharelatex": (this.settings = {}),
|
||||
"./Metrics": (this.Metrics = {
|
||||
Timer: (Timer = (function() {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub();
|
||||
}
|
||||
};
|
||||
Timer.initClass();
|
||||
return Timer;
|
||||
})()),
|
||||
gauge: sinon.stub()
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
this.callback = sinon.stub();
|
||||
return this.RateLimiter = new this.RateLimitManager(1);
|
||||
});
|
||||
describe('RateLimitManager', function () {
|
||||
beforeEach(function () {
|
||||
let Timer
|
||||
this.RateLimitManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'logger-sharelatex': (this.logger = { log: sinon.stub() }),
|
||||
'settings-sharelatex': (this.settings = {}),
|
||||
'./Metrics': (this.Metrics = {
|
||||
Timer: (Timer = (function () {
|
||||
Timer = class Timer {
|
||||
static initClass() {
|
||||
this.prototype.done = sinon.stub()
|
||||
}
|
||||
}
|
||||
Timer.initClass()
|
||||
return Timer
|
||||
})()),
|
||||
gauge: sinon.stub()
|
||||
})
|
||||
}
|
||||
})
|
||||
this.callback = sinon.stub()
|
||||
return (this.RateLimiter = new this.RateLimitManager(1))
|
||||
})
|
||||
|
||||
describe("for a single task", function() {
|
||||
beforeEach(function() {
|
||||
this.task = sinon.stub();
|
||||
return this.RateLimiter.run(this.task, this.callback);
|
||||
});
|
||||
describe('for a single task', function () {
|
||||
beforeEach(function () {
|
||||
this.task = sinon.stub()
|
||||
return this.RateLimiter.run(this.task, this.callback)
|
||||
})
|
||||
|
||||
it("should execute the task in the background", function() {
|
||||
return this.task.called.should.equal(true);
|
||||
});
|
||||
it('should execute the task in the background', function () {
|
||||
return this.task.called.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should finish with a worker count of one", function() {
|
||||
// because it's in the background
|
||||
return expect(this.RateLimiter.ActiveWorkerCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
return it('should finish with a worker count of one', function () {
|
||||
// because it's in the background
|
||||
return expect(this.RateLimiter.ActiveWorkerCount).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("for multiple tasks", function() {
|
||||
beforeEach(function(done) {
|
||||
this.task = sinon.stub();
|
||||
this.finalTask = sinon.stub();
|
||||
const task = cb => {
|
||||
this.task();
|
||||
return setTimeout(cb, 100);
|
||||
};
|
||||
const finalTask = cb => {
|
||||
this.finalTask();
|
||||
return setTimeout(cb, 100);
|
||||
};
|
||||
this.RateLimiter.run(task, this.callback);
|
||||
this.RateLimiter.run(task, this.callback);
|
||||
this.RateLimiter.run(task, this.callback);
|
||||
return this.RateLimiter.run(finalTask, err => {
|
||||
this.callback(err);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('for multiple tasks', function () {
|
||||
beforeEach(function (done) {
|
||||
this.task = sinon.stub()
|
||||
this.finalTask = sinon.stub()
|
||||
const task = (cb) => {
|
||||
this.task()
|
||||
return setTimeout(cb, 100)
|
||||
}
|
||||
const finalTask = (cb) => {
|
||||
this.finalTask()
|
||||
return setTimeout(cb, 100)
|
||||
}
|
||||
this.RateLimiter.run(task, this.callback)
|
||||
this.RateLimiter.run(task, this.callback)
|
||||
this.RateLimiter.run(task, this.callback)
|
||||
return this.RateLimiter.run(finalTask, (err) => {
|
||||
this.callback(err)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it("should execute the first three tasks", function() {
|
||||
return this.task.calledThrice.should.equal(true);
|
||||
});
|
||||
it('should execute the first three tasks', function () {
|
||||
return this.task.calledThrice.should.equal(true)
|
||||
})
|
||||
|
||||
it("should execute the final task", function() {
|
||||
return this.finalTask.called.should.equal(true);
|
||||
});
|
||||
it('should execute the final task', function () {
|
||||
return this.finalTask.called.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should finish with worker count of zero", function() {
|
||||
return expect(this.RateLimiter.ActiveWorkerCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
return it('should finish with worker count of zero', function () {
|
||||
return expect(this.RateLimiter.ActiveWorkerCount).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("for a mixture of long-running tasks", function() {
|
||||
beforeEach(function(done) {
|
||||
this.task = sinon.stub();
|
||||
this.finalTask = sinon.stub();
|
||||
const finalTask = cb => {
|
||||
this.finalTask();
|
||||
return setTimeout(cb, 100);
|
||||
};
|
||||
this.RateLimiter.run(this.task, this.callback);
|
||||
this.RateLimiter.run(this.task, this.callback);
|
||||
this.RateLimiter.run(this.task, this.callback);
|
||||
return this.RateLimiter.run(finalTask, err => {
|
||||
this.callback(err);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return describe('for a mixture of long-running tasks', function () {
|
||||
beforeEach(function (done) {
|
||||
this.task = sinon.stub()
|
||||
this.finalTask = sinon.stub()
|
||||
const finalTask = (cb) => {
|
||||
this.finalTask()
|
||||
return setTimeout(cb, 100)
|
||||
}
|
||||
this.RateLimiter.run(this.task, this.callback)
|
||||
this.RateLimiter.run(this.task, this.callback)
|
||||
this.RateLimiter.run(this.task, this.callback)
|
||||
return this.RateLimiter.run(finalTask, (err) => {
|
||||
this.callback(err)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it("should execute the first three tasks", function() {
|
||||
return this.task.calledThrice.should.equal(true);
|
||||
});
|
||||
it('should execute the first three tasks', function () {
|
||||
return this.task.calledThrice.should.equal(true)
|
||||
})
|
||||
|
||||
it("should execute the final task", function() {
|
||||
return this.finalTask.called.should.equal(true);
|
||||
});
|
||||
it('should execute the final task', function () {
|
||||
return this.finalTask.called.should.equal(true)
|
||||
})
|
||||
|
||||
it("should call the callback", function() {
|
||||
return this.callback.called.should.equal(true);
|
||||
});
|
||||
it('should call the callback', function () {
|
||||
return this.callback.called.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should finish with worker count of three", function() {
|
||||
return expect(this.RateLimiter.ActiveWorkerCount).to.equal(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should finish with worker count of three', function () {
|
||||
return expect(this.RateLimiter.ActiveWorkerCount).to.equal(3)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,127 +10,162 @@
|
|||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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/RealTimeRedisManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const Errors = require("../../../../app/js/Errors");
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/RealTimeRedisManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const Errors = require('../../../../app/js/Errors')
|
||||
|
||||
describe("RealTimeRedisManager", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient = {
|
||||
auth() {},
|
||||
exec: sinon.stub()
|
||||
};
|
||||
this.rclient.multi = () => this.rclient;
|
||||
this.pubsubClient =
|
||||
{publish: sinon.stub()};
|
||||
this.RealTimeRedisManager = SandboxedModule.require(modulePath, { requires: {
|
||||
"redis-sharelatex": { createClient: config => (config.name === 'pubsub') ? this.pubsubClient : this.rclient
|
||||
},
|
||||
"settings-sharelatex": {
|
||||
redis: {
|
||||
documentupdater: (this.settings = {
|
||||
key_schema: {
|
||||
pendingUpdates({doc_id}) { return `PendingUpdates:${doc_id}`; }
|
||||
}
|
||||
}),
|
||||
pubsub: {
|
||||
name: "pubsub"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logger-sharelatex": { log() {} },
|
||||
"crypto": (this.crypto = { randomBytes: sinon.stub().withArgs(4).returns(Buffer.from([0x1, 0x2, 0x3, 0x4])) }),
|
||||
"os": (this.os = {hostname: sinon.stub().returns("somehost")}),
|
||||
"./Metrics": (this.metrics = { summary: sinon.stub()})
|
||||
}
|
||||
});
|
||||
describe('RealTimeRedisManager', function () {
|
||||
beforeEach(function () {
|
||||
this.rclient = {
|
||||
auth() {},
|
||||
exec: sinon.stub()
|
||||
}
|
||||
this.rclient.multi = () => this.rclient
|
||||
this.pubsubClient = { publish: sinon.stub() }
|
||||
this.RealTimeRedisManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'redis-sharelatex': {
|
||||
createClient: (config) =>
|
||||
config.name === 'pubsub' ? this.pubsubClient : this.rclient
|
||||
},
|
||||
'settings-sharelatex': {
|
||||
redis: {
|
||||
documentupdater: (this.settings = {
|
||||
key_schema: {
|
||||
pendingUpdates({ doc_id }) {
|
||||
return `PendingUpdates:${doc_id}`
|
||||
}
|
||||
}
|
||||
}),
|
||||
pubsub: {
|
||||
name: 'pubsub'
|
||||
}
|
||||
}
|
||||
},
|
||||
'logger-sharelatex': { log() {} },
|
||||
crypto: (this.crypto = {
|
||||
randomBytes: sinon
|
||||
.stub()
|
||||
.withArgs(4)
|
||||
.returns(Buffer.from([0x1, 0x2, 0x3, 0x4]))
|
||||
}),
|
||||
os: (this.os = { hostname: sinon.stub().returns('somehost') }),
|
||||
'./Metrics': (this.metrics = { summary: sinon.stub() })
|
||||
}
|
||||
})
|
||||
|
||||
this.doc_id = "doc-id-123";
|
||||
this.project_id = "project-id-123";
|
||||
return this.callback = sinon.stub();
|
||||
});
|
||||
this.doc_id = 'doc-id-123'
|
||||
this.project_id = 'project-id-123'
|
||||
return (this.callback = sinon.stub())
|
||||
})
|
||||
|
||||
describe("getPendingUpdatesForDoc", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.lrange = sinon.stub();
|
||||
return this.rclient.ltrim = sinon.stub();
|
||||
});
|
||||
describe('getPendingUpdatesForDoc', function () {
|
||||
beforeEach(function () {
|
||||
this.rclient.lrange = sinon.stub()
|
||||
return (this.rclient.ltrim = sinon.stub())
|
||||
})
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
this.updates = [
|
||||
{ op: [{ i: "foo", p: 4 }] },
|
||||
{ op: [{ i: "foo", p: 4 }] }
|
||||
];
|
||||
this.jsonUpdates = this.updates.map(update => JSON.stringify(update));
|
||||
this.rclient.exec = sinon.stub().callsArgWith(0, null, [this.jsonUpdates]);
|
||||
return this.RealTimeRedisManager.getPendingUpdatesForDoc(this.doc_id, this.callback);
|
||||
});
|
||||
describe('successfully', function () {
|
||||
beforeEach(function () {
|
||||
this.updates = [
|
||||
{ op: [{ i: 'foo', p: 4 }] },
|
||||
{ op: [{ i: 'foo', p: 4 }] }
|
||||
]
|
||||
this.jsonUpdates = this.updates.map((update) => JSON.stringify(update))
|
||||
this.rclient.exec = sinon
|
||||
.stub()
|
||||
.callsArgWith(0, null, [this.jsonUpdates])
|
||||
return this.RealTimeRedisManager.getPendingUpdatesForDoc(
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it("should get the pending updates", function() {
|
||||
return this.rclient.lrange
|
||||
.calledWith(`PendingUpdates:${this.doc_id}`, 0, 7)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should get the pending updates', function () {
|
||||
return this.rclient.lrange
|
||||
.calledWith(`PendingUpdates:${this.doc_id}`, 0, 7)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should delete the pending updates", function() {
|
||||
return this.rclient.ltrim
|
||||
.calledWith(`PendingUpdates:${this.doc_id}`, 8, -1)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should delete the pending updates', function () {
|
||||
return this.rclient.ltrim
|
||||
.calledWith(`PendingUpdates:${this.doc_id}`, 8, -1)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should call the callback with the updates", function() {
|
||||
return this.callback.calledWith(null, this.updates).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the updates', function () {
|
||||
return this.callback.calledWith(null, this.updates).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("when the JSON doesn't parse", function() {
|
||||
beforeEach(function() {
|
||||
this.jsonUpdates = [
|
||||
JSON.stringify({ op: [{ i: "foo", p: 4 }] }),
|
||||
"broken json"
|
||||
];
|
||||
this.rclient.exec = sinon.stub().callsArgWith(0, null, [this.jsonUpdates]);
|
||||
return this.RealTimeRedisManager.getPendingUpdatesForDoc(this.doc_id, this.callback);
|
||||
});
|
||||
return describe("when the JSON doesn't parse", function () {
|
||||
beforeEach(function () {
|
||||
this.jsonUpdates = [
|
||||
JSON.stringify({ op: [{ i: 'foo', p: 4 }] }),
|
||||
'broken json'
|
||||
]
|
||||
this.rclient.exec = sinon
|
||||
.stub()
|
||||
.callsArgWith(0, null, [this.jsonUpdates])
|
||||
return this.RealTimeRedisManager.getPendingUpdatesForDoc(
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
return it("should return an error to the callback", function() {
|
||||
return this.callback.calledWith(new Error("JSON parse error")).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should return an error to the callback', function () {
|
||||
return this.callback
|
||||
.calledWith(new Error('JSON parse error'))
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getUpdatesLength', function () {
|
||||
beforeEach(function () {
|
||||
this.rclient.llen = sinon.stub().yields(null, (this.length = 3))
|
||||
return this.RealTimeRedisManager.getUpdatesLength(
|
||||
this.doc_id,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
describe("getUpdatesLength", function() {
|
||||
beforeEach(function() {
|
||||
this.rclient.llen = sinon.stub().yields(null, (this.length = 3));
|
||||
return this.RealTimeRedisManager.getUpdatesLength(this.doc_id, this.callback);
|
||||
});
|
||||
it('should look up the length', function () {
|
||||
return this.rclient.llen
|
||||
.calledWith(`PendingUpdates:${this.doc_id}`)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should look up the length", function() {
|
||||
return this.rclient.llen.calledWith(`PendingUpdates:${this.doc_id}`).should.equal(true);
|
||||
});
|
||||
return it('should return the length', function () {
|
||||
return this.callback.calledWith(null, this.length).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return it("should return the length", function() {
|
||||
return this.callback.calledWith(null, this.length).should.equal(true);
|
||||
});
|
||||
});
|
||||
return describe('sendData', function () {
|
||||
beforeEach(function () {
|
||||
this.message_id = 'doc:somehost:01020304-0'
|
||||
return this.RealTimeRedisManager.sendData({ op: 'thisop' })
|
||||
})
|
||||
|
||||
return describe("sendData", function() {
|
||||
beforeEach(function() {
|
||||
this.message_id = "doc:somehost:01020304-0";
|
||||
return this.RealTimeRedisManager.sendData({op: "thisop"});
|
||||
});
|
||||
it('should send the op with a message id', function () {
|
||||
return this.pubsubClient.publish
|
||||
.calledWith(
|
||||
'applied-ops',
|
||||
JSON.stringify({ op: 'thisop', _id: this.message_id })
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should send the op with a message id", function() {
|
||||
return this.pubsubClient.publish.calledWith("applied-ops", JSON.stringify({op:"thisop",_id:this.message_id})).should.equal(true);
|
||||
});
|
||||
|
||||
return it("should track the payload size", function() {
|
||||
return this.metrics.summary.calledWith("redis.publish.applied-ops", JSON.stringify({op:"thisop",_id:this.message_id}).length).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should track the payload size', function () {
|
||||
return this.metrics.summary
|
||||
.calledWith(
|
||||
'redis.publish.applied-ops',
|
||||
JSON.stringify({ op: 'thisop', _id: this.message_id }).length
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,353 +13,427 @@
|
|||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const text = require("../../../../app/js/sharejs/types/text");
|
||||
require("chai").should();
|
||||
const RangesTracker = require("../../../../app/js/RangesTracker");
|
||||
const text = require('../../../../app/js/sharejs/types/text')
|
||||
require('chai').should()
|
||||
const RangesTracker = require('../../../../app/js/RangesTracker')
|
||||
|
||||
describe("ShareJS text type", function() {
|
||||
beforeEach(function() {
|
||||
return this.t = "mock-thread-id";
|
||||
});
|
||||
|
||||
describe("transform", function() {
|
||||
describe("insert / insert", function() {
|
||||
it("with an insert before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 9 }, { i: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ i: "foo", p: 12 }]);
|
||||
});
|
||||
describe('ShareJS text type', function () {
|
||||
beforeEach(function () {
|
||||
return (this.t = 'mock-thread-id')
|
||||
})
|
||||
|
||||
it("with an insert after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 9 });
|
||||
return dest.should.deep.equal([{ i: "foo", p: 3 }]);
|
||||
});
|
||||
describe('transform', function () {
|
||||
describe('insert / insert', function () {
|
||||
it('with an insert before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 9 }, { i: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 12 }])
|
||||
})
|
||||
|
||||
it("with an insert at the same place with side == 'right'", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'right');
|
||||
return dest.should.deep.equal([{ i: "foo", p: 6 }]);
|
||||
});
|
||||
it('with an insert after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 3 }, { i: 'bar', p: 9 })
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 3 }])
|
||||
})
|
||||
|
||||
return it("with an insert at the same place with side == 'left'", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 3 }, { i: "bar", p: 3 }, 'left');
|
||||
return dest.should.deep.equal([{ i: "foo", p: 3 }]);
|
||||
});
|
||||
});
|
||||
it("with an insert at the same place with side == 'right'", function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'right')
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 6 }])
|
||||
})
|
||||
|
||||
describe("insert / delete", function() {
|
||||
it("with a delete before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 9 }, { d: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ i: "foo", p: 6 }]);
|
||||
});
|
||||
return it("with an insert at the same place with side == 'left'", function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'left')
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 3 }])
|
||||
})
|
||||
})
|
||||
|
||||
it("with a delete after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 9 });
|
||||
return dest.should.deep.equal([{ i: "foo", p: 3 }]);
|
||||
});
|
||||
describe('insert / delete', function () {
|
||||
it('with a delete before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 9 }, { d: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 6 }])
|
||||
})
|
||||
|
||||
it("with a delete at the same place with side == 'right'", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'right');
|
||||
return dest.should.deep.equal([{ i: "foo", p: 3 }]);
|
||||
});
|
||||
it('with a delete after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 3 }, { d: 'bar', p: 9 })
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 3 }])
|
||||
})
|
||||
|
||||
return it("with a delete at the same place with side == 'left'", function() {
|
||||
const dest = [];
|
||||
|
||||
text._tc(dest, { i: "foo", p: 3 }, { d: "bar", p: 3 }, 'left');
|
||||
return dest.should.deep.equal([{ i: "foo", p: 3 }]);
|
||||
});
|
||||
});
|
||||
it("with a delete at the same place with side == 'right'", function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 3 }, { d: 'bar', p: 3 }, 'right')
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 3 }])
|
||||
})
|
||||
|
||||
describe("delete / insert", function() {
|
||||
it("with an insert before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 9 }, { i: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ d: "foo", p: 12 }]);
|
||||
});
|
||||
return it("with a delete at the same place with side == 'left'", function () {
|
||||
const dest = []
|
||||
|
||||
it("with an insert after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 9 });
|
||||
return dest.should.deep.equal([{ d: "foo", p: 3 }]);
|
||||
});
|
||||
text._tc(dest, { i: 'foo', p: 3 }, { d: 'bar', p: 3 }, 'left')
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 3 }])
|
||||
})
|
||||
})
|
||||
|
||||
it("with an insert at the same place with side == 'right'", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'right');
|
||||
return dest.should.deep.equal([{ d: "foo", p: 6 }]);
|
||||
});
|
||||
describe('delete / insert', function () {
|
||||
it('with an insert before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 9 }, { i: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ d: 'foo', p: 12 }])
|
||||
})
|
||||
|
||||
it("with an insert at the same place with side == 'left'", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 3 }, 'left');
|
||||
return dest.should.deep.equal([{ d: "foo", p: 6 }]);
|
||||
});
|
||||
|
||||
return it("with a delete that overlaps the insert location", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 3 }, { i: "bar", p: 4 });
|
||||
return dest.should.deep.equal([{ d: "f", p: 3 }, { d: "oo", p: 6 }]);
|
||||
});
|
||||
});
|
||||
|
||||
it('with an insert after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 9 })
|
||||
return dest.should.deep.equal([{ d: 'foo', p: 3 }])
|
||||
})
|
||||
|
||||
describe("delete / delete", function() {
|
||||
it("with a delete before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 9 }, { d: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ d: "foo", p: 6 }]);
|
||||
});
|
||||
it("with an insert at the same place with side == 'right'", function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'right')
|
||||
return dest.should.deep.equal([{ d: 'foo', p: 6 }])
|
||||
})
|
||||
|
||||
it("with a delete after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 3 }, { d: "bar", p: 9 });
|
||||
return dest.should.deep.equal([{ d: "foo", p: 3 }]);
|
||||
});
|
||||
it("with an insert at the same place with side == 'left'", function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 3 }, 'left')
|
||||
return dest.should.deep.equal([{ d: 'foo', p: 6 }])
|
||||
})
|
||||
|
||||
it("with deleting the same content", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 3 }, { d: "foo", p: 3 }, 'right');
|
||||
return dest.should.deep.equal([]);
|
||||
});
|
||||
return it('with a delete that overlaps the insert location', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 3 }, { i: 'bar', p: 4 })
|
||||
return dest.should.deep.equal([
|
||||
{ d: 'f', p: 3 },
|
||||
{ d: 'oo', p: 6 }
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it("with the delete overlapping before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foobar", p: 3 }, { d: "abcfoo", p: 0 }, 'right');
|
||||
return dest.should.deep.equal([{ d: "bar", p: 0 }]);
|
||||
});
|
||||
describe('delete / delete', function () {
|
||||
it('with a delete before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 9 }, { d: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ d: 'foo', p: 6 }])
|
||||
})
|
||||
|
||||
it("with the delete overlapping after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "abcfoo", p: 3 }, { d: "foobar", p: 6 });
|
||||
return dest.should.deep.equal([{ d: "abc", p: 3 }]);
|
||||
});
|
||||
it('with a delete after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 3 }, { d: 'bar', p: 9 })
|
||||
return dest.should.deep.equal([{ d: 'foo', p: 3 }])
|
||||
})
|
||||
|
||||
it("with the delete overlapping the whole delete", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "abcfoo123", p: 3 }, { d: "foo", p: 6 });
|
||||
return dest.should.deep.equal([{ d: "abc123", p: 3 }]);
|
||||
});
|
||||
it('with deleting the same content', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 3 }, { d: 'foo', p: 3 }, 'right')
|
||||
return dest.should.deep.equal([])
|
||||
})
|
||||
|
||||
return it("with the delete inside the whole delete", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 6 }, { d: "abcfoo123", p: 3 });
|
||||
return dest.should.deep.equal([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("comment / insert", function() {
|
||||
it("with an insert before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 9, t: this.t }, { i: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ c: "foo", p: 12, t: this.t }]);
|
||||
});
|
||||
it('with the delete overlapping before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foobar', p: 3 }, { d: 'abcfoo', p: 0 }, 'right')
|
||||
return dest.should.deep.equal([{ d: 'bar', p: 0 }])
|
||||
})
|
||||
|
||||
it("with an insert after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 9 });
|
||||
return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]);
|
||||
});
|
||||
it('with the delete overlapping after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'abcfoo', p: 3 }, { d: 'foobar', p: 6 })
|
||||
return dest.should.deep.equal([{ d: 'abc', p: 3 }])
|
||||
})
|
||||
|
||||
it("with an insert at the left edge", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 3 });
|
||||
// RangesTracker doesn't inject inserts into comments on edges, so neither should we
|
||||
return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]);
|
||||
});
|
||||
it('with the delete overlapping the whole delete', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'abcfoo123', p: 3 }, { d: 'foo', p: 6 })
|
||||
return dest.should.deep.equal([{ d: 'abc123', p: 3 }])
|
||||
})
|
||||
|
||||
it("with an insert at the right edge", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 6 });
|
||||
// RangesTracker doesn't inject inserts into comments on edges, so neither should we
|
||||
return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]);
|
||||
});
|
||||
return it('with the delete inside the whole delete', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 6 }, { d: 'abcfoo123', p: 3 })
|
||||
return dest.should.deep.equal([])
|
||||
})
|
||||
})
|
||||
|
||||
return it("with an insert in the middle", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 5 });
|
||||
return dest.should.deep.equal([{ c: "fobaro", p: 3, t: this.t }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("comment / delete", function() {
|
||||
it("with a delete before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 9, t: this.t }, { d: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]);
|
||||
});
|
||||
describe('comment / insert', function () {
|
||||
it('with an insert before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 9, t: this.t }, { i: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 12, t: this.t }])
|
||||
})
|
||||
|
||||
it("with a delete after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 3, t: this.t }, { i: "bar", p: 9 });
|
||||
return dest.should.deep.equal([{ c: "foo", p: 3, t: this.t }]);
|
||||
});
|
||||
it('with an insert after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 9 })
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 3, t: this.t }])
|
||||
})
|
||||
|
||||
it("with a delete overlapping the comment content before", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foobar", p: 6, t: this.t }, { d: "123foo", p: 3 });
|
||||
return dest.should.deep.equal([{ c: "bar", p: 3, t: this.t }]);
|
||||
});
|
||||
it('with an insert at the left edge', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 3 })
|
||||
// RangesTracker doesn't inject inserts into comments on edges, so neither should we
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 6, t: this.t }])
|
||||
})
|
||||
|
||||
it("with a delete overlapping the comment content after", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foobar", p: 6, t: this.t }, { d: "bar123", p: 9 });
|
||||
return dest.should.deep.equal([{ c: "foo", p: 6, t: this.t }]);
|
||||
});
|
||||
it('with an insert at the right edge', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 6 })
|
||||
// RangesTracker doesn't inject inserts into comments on edges, so neither should we
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 3, t: this.t }])
|
||||
})
|
||||
|
||||
it("with a delete overlapping the comment content in the middle", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo123bar", p: 6, t: this.t }, { d: "123", p: 9 });
|
||||
return dest.should.deep.equal([{ c: "foobar", p: 6, t: this.t }]);
|
||||
});
|
||||
return it('with an insert in the middle', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 5 })
|
||||
return dest.should.deep.equal([{ c: 'fobaro', p: 3, t: this.t }])
|
||||
})
|
||||
})
|
||||
|
||||
return it("with a delete overlapping the whole comment", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 6, t: this.t }, { d: "123foo456", p: 3 });
|
||||
return dest.should.deep.equal([{ c: "", p: 3, t: this.t }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("comment / insert", function() { return it("should not do anything", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { i: "foo", p: 6 }, { c: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ i: "foo", p: 6 }]);
|
||||
}); });
|
||||
|
||||
describe("comment / delete", function() { return it("should not do anything", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { d: "foo", p: 6 }, { c: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ d: "foo", p: 6 }]);
|
||||
}); });
|
||||
|
||||
return describe("comment / comment", function() { return it("should not do anything", function() {
|
||||
const dest = [];
|
||||
text._tc(dest, { c: "foo", p: 6 }, { c: "bar", p: 3 });
|
||||
return dest.should.deep.equal([{ c: "foo", p: 6 }]);
|
||||
}); });
|
||||
});
|
||||
describe('comment / delete', function () {
|
||||
it('with a delete before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 9, t: this.t }, { d: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 6, t: this.t }])
|
||||
})
|
||||
|
||||
describe("apply", function() {
|
||||
it("should apply an insert", function() { return text.apply("foo", [{ i: "bar", p: 2 }]).should.equal("fobaro"); });
|
||||
it('with a delete after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 3, t: this.t }, { i: 'bar', p: 9 })
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 3, t: this.t }])
|
||||
})
|
||||
|
||||
it("should apply a delete", function() { return text.apply("foo123bar", [{ d: "123", p: 3 }]).should.equal("foobar"); });
|
||||
it('with a delete overlapping the comment content before', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foobar', p: 6, t: this.t }, { d: '123foo', p: 3 })
|
||||
return dest.should.deep.equal([{ c: 'bar', p: 3, t: this.t }])
|
||||
})
|
||||
|
||||
it("should do nothing with a comment", function() { return text.apply("foo123bar", [{ c: "123", p: 3 }]).should.equal("foo123bar"); });
|
||||
|
||||
it("should throw an error when deleted content does not match", function() { return ((() => text.apply("foo123bar", [{ d: "456", p: 3 }]))).should.throw(Error); });
|
||||
|
||||
return it("should throw an error when comment content does not match", function() { return ((() => text.apply("foo123bar", [{ c: "456", p: 3 }]))).should.throw(Error); });
|
||||
});
|
||||
|
||||
return describe("applying ops and comments in different orders", function() { return it("should not matter which op or comment is applied first", function() {
|
||||
let length, p;
|
||||
let asc, end;
|
||||
let asc1, end1;
|
||||
let asc3, end3;
|
||||
const transform = function(op1, op2, side) {
|
||||
const d = [];
|
||||
text._tc(d, op1, op2, side);
|
||||
return d;
|
||||
};
|
||||
|
||||
const applySnapshot = (snapshot, op) => text.apply(snapshot, op);
|
||||
|
||||
const applyRanges = function(rangesTracker, ops) {
|
||||
for (const op of Array.from(ops)) {
|
||||
rangesTracker.applyOp(op, {});
|
||||
}
|
||||
return rangesTracker;
|
||||
};
|
||||
|
||||
const commentsEqual = function(comments1, comments2) {
|
||||
if (comments1.length !== comments2.length) { return false; }
|
||||
comments1.sort((a,b) => {
|
||||
if ((a.offset - b.offset) === 0) {
|
||||
return a.length - b.length;
|
||||
it('with a delete overlapping the comment content after', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foobar', p: 6, t: this.t }, { d: 'bar123', p: 9 })
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 6, t: this.t }])
|
||||
})
|
||||
|
||||
it('with a delete overlapping the comment content in the middle', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo123bar', p: 6, t: this.t }, { d: '123', p: 9 })
|
||||
return dest.should.deep.equal([{ c: 'foobar', p: 6, t: this.t }])
|
||||
})
|
||||
|
||||
return it('with a delete overlapping the whole comment', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 6, t: this.t }, { d: '123foo456', p: 3 })
|
||||
return dest.should.deep.equal([{ c: '', p: 3, t: this.t }])
|
||||
})
|
||||
})
|
||||
|
||||
describe('comment / insert', function () {
|
||||
return it('should not do anything', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { i: 'foo', p: 6 }, { c: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ i: 'foo', p: 6 }])
|
||||
})
|
||||
})
|
||||
|
||||
describe('comment / delete', function () {
|
||||
return it('should not do anything', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { d: 'foo', p: 6 }, { c: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ d: 'foo', p: 6 }])
|
||||
})
|
||||
})
|
||||
|
||||
return describe('comment / comment', function () {
|
||||
return it('should not do anything', function () {
|
||||
const dest = []
|
||||
text._tc(dest, { c: 'foo', p: 6 }, { c: 'bar', p: 3 })
|
||||
return dest.should.deep.equal([{ c: 'foo', p: 6 }])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('apply', function () {
|
||||
it('should apply an insert', function () {
|
||||
return text.apply('foo', [{ i: 'bar', p: 2 }]).should.equal('fobaro')
|
||||
})
|
||||
|
||||
it('should apply a delete', function () {
|
||||
return text
|
||||
.apply('foo123bar', [{ d: '123', p: 3 }])
|
||||
.should.equal('foobar')
|
||||
})
|
||||
|
||||
it('should do nothing with a comment', function () {
|
||||
return text
|
||||
.apply('foo123bar', [{ c: '123', p: 3 }])
|
||||
.should.equal('foo123bar')
|
||||
})
|
||||
|
||||
it('should throw an error when deleted content does not match', function () {
|
||||
return (() => text.apply('foo123bar', [{ d: '456', p: 3 }])).should.throw(
|
||||
Error
|
||||
)
|
||||
})
|
||||
|
||||
return it('should throw an error when comment content does not match', function () {
|
||||
return (() => text.apply('foo123bar', [{ c: '456', p: 3 }])).should.throw(
|
||||
Error
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('applying ops and comments in different orders', function () {
|
||||
return it('should not matter which op or comment is applied first', function () {
|
||||
let length, p
|
||||
let asc, end
|
||||
let asc1, end1
|
||||
let asc3, end3
|
||||
const transform = function (op1, op2, side) {
|
||||
const d = []
|
||||
text._tc(d, op1, op2, side)
|
||||
return d
|
||||
}
|
||||
|
||||
const applySnapshot = (snapshot, op) => text.apply(snapshot, op)
|
||||
|
||||
const applyRanges = function (rangesTracker, ops) {
|
||||
for (const op of Array.from(ops)) {
|
||||
rangesTracker.applyOp(op, {})
|
||||
}
|
||||
return rangesTracker
|
||||
}
|
||||
|
||||
const commentsEqual = function (comments1, comments2) {
|
||||
if (comments1.length !== comments2.length) {
|
||||
return false
|
||||
}
|
||||
comments1.sort((a, b) => {
|
||||
if (a.offset - b.offset === 0) {
|
||||
return a.length - b.length
|
||||
} else {
|
||||
return a.offset - b.offset
|
||||
}
|
||||
})
|
||||
comments2.sort((a, b) => {
|
||||
if (a.offset - b.offset === 0) {
|
||||
return a.length - b.length
|
||||
} else {
|
||||
return a.offset - b.offset
|
||||
}
|
||||
})
|
||||
for (let i = 0; i < comments1.length; i++) {
|
||||
const comment1 = comments1[i]
|
||||
const comment2 = comments2[i]
|
||||
if (
|
||||
comment1.offset !== comment2.offset ||
|
||||
comment1.length !== comment2.length
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const SNAPSHOT = '123'
|
||||
|
||||
const OPS = []
|
||||
// Insert ops
|
||||
for (
|
||||
p = 0, end = SNAPSHOT.length, asc = end >= 0;
|
||||
asc ? p <= end : p >= end;
|
||||
asc ? p++ : p--
|
||||
) {
|
||||
OPS.push({ i: 'a', p })
|
||||
OPS.push({ i: 'bc', p })
|
||||
}
|
||||
for (
|
||||
p = 0, end1 = SNAPSHOT.length - 1, asc1 = end1 >= 0;
|
||||
asc1 ? p <= end1 : p >= end1;
|
||||
asc1 ? p++ : p--
|
||||
) {
|
||||
var asc2, end2
|
||||
for (
|
||||
length = 1, end2 = SNAPSHOT.length - p, asc2 = end2 >= 1;
|
||||
asc2 ? length <= end2 : length >= end2;
|
||||
asc2 ? length++ : length--
|
||||
) {
|
||||
OPS.push({ d: SNAPSHOT.slice(p, p + length), p })
|
||||
}
|
||||
}
|
||||
for (
|
||||
p = 0, end3 = SNAPSHOT.length - 1, asc3 = end3 >= 0;
|
||||
asc3 ? p <= end3 : p >= end3;
|
||||
asc3 ? p++ : p--
|
||||
) {
|
||||
var asc4, end4
|
||||
for (
|
||||
length = 1, end4 = SNAPSHOT.length - p, asc4 = end4 >= 1;
|
||||
asc4 ? length <= end4 : length >= end4;
|
||||
asc4 ? length++ : length--
|
||||
) {
|
||||
OPS.push({ c: SNAPSHOT.slice(p, p + length), p, t: this.t })
|
||||
}
|
||||
}
|
||||
|
||||
return (() => {
|
||||
const result = []
|
||||
for (var op1 of Array.from(OPS)) {
|
||||
result.push(
|
||||
(() => {
|
||||
const result1 = []
|
||||
for (const op2 of Array.from(OPS)) {
|
||||
const op1_t = transform(op1, op2, 'left')
|
||||
const op2_t = transform(op2, op1, 'right')
|
||||
|
||||
const rt12 = new RangesTracker()
|
||||
const snapshot12 = applySnapshot(
|
||||
applySnapshot(SNAPSHOT, [op1]),
|
||||
op2_t
|
||||
)
|
||||
applyRanges(rt12, [op1])
|
||||
applyRanges(rt12, op2_t)
|
||||
|
||||
const rt21 = new RangesTracker()
|
||||
const snapshot21 = applySnapshot(
|
||||
applySnapshot(SNAPSHOT, [op2]),
|
||||
op1_t
|
||||
)
|
||||
applyRanges(rt21, [op2])
|
||||
applyRanges(rt21, op1_t)
|
||||
|
||||
if (snapshot12 !== snapshot21) {
|
||||
console.error(
|
||||
{ op1, op2, op1_t, op2_t, snapshot12, snapshot21 },
|
||||
'Ops are not consistent'
|
||||
)
|
||||
throw new Error('OT is inconsistent')
|
||||
}
|
||||
|
||||
if (!commentsEqual(rt12.comments, rt21.comments)) {
|
||||
console.log(rt12.comments)
|
||||
console.log(rt21.comments)
|
||||
console.error(
|
||||
{
|
||||
op1,
|
||||
op2,
|
||||
op1_t,
|
||||
op2_t,
|
||||
rt12_comments: rt12.comments,
|
||||
rt21_comments: rt21.comments
|
||||
},
|
||||
'Comments are not consistent'
|
||||
)
|
||||
throw new Error('OT is inconsistent')
|
||||
} else {
|
||||
return a.offset - b.offset;
|
||||
result1.push(undefined)
|
||||
}
|
||||
});
|
||||
comments2.sort((a,b) => {
|
||||
if ((a.offset - b.offset) === 0) {
|
||||
return a.length - b.length;
|
||||
} else {
|
||||
return a.offset - b.offset;
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < comments1.length; i++) {
|
||||
const comment1 = comments1[i];
|
||||
const comment2 = comments2[i];
|
||||
if ((comment1.offset !== comment2.offset) || (comment1.length !== comment2.length)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const SNAPSHOT = "123";
|
||||
|
||||
const OPS = [];
|
||||
// Insert ops
|
||||
for (p = 0, end = SNAPSHOT.length, asc = end >= 0; asc ? p <= end : p >= end; asc ? p++ : p--) {
|
||||
OPS.push({i: "a", p});
|
||||
OPS.push({i: "bc", p});
|
||||
}
|
||||
return result1
|
||||
})()
|
||||
)
|
||||
}
|
||||
for (p = 0, end1 = SNAPSHOT.length-1, asc1 = end1 >= 0; asc1 ? p <= end1 : p >= end1; asc1 ? p++ : p--) {
|
||||
var asc2, end2;
|
||||
for (length = 1, end2 = SNAPSHOT.length - p, asc2 = end2 >= 1; asc2 ? length <= end2 : length >= end2; asc2 ? length++ : length--) {
|
||||
OPS.push({d: SNAPSHOT.slice(p, p+length), p});
|
||||
}
|
||||
}
|
||||
for (p = 0, end3 = SNAPSHOT.length-1, asc3 = end3 >= 0; asc3 ? p <= end3 : p >= end3; asc3 ? p++ : p--) {
|
||||
var asc4, end4;
|
||||
for (length = 1, end4 = SNAPSHOT.length - p, asc4 = end4 >= 1; asc4 ? length <= end4 : length >= end4; asc4 ? length++ : length--) {
|
||||
OPS.push({c: SNAPSHOT.slice(p, p+length), p, t: this.t});
|
||||
}
|
||||
}
|
||||
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (var op1 of Array.from(OPS)) {
|
||||
result.push((() => {
|
||||
const result1 = [];
|
||||
for (const op2 of Array.from(OPS)) {
|
||||
const op1_t = transform(op1, op2, "left");
|
||||
const op2_t = transform(op2, op1, "right");
|
||||
|
||||
const rt12 = new RangesTracker();
|
||||
const snapshot12 = applySnapshot(applySnapshot(SNAPSHOT, [op1]), op2_t);
|
||||
applyRanges(rt12, [op1]);
|
||||
applyRanges(rt12, op2_t);
|
||||
|
||||
const rt21 = new RangesTracker();
|
||||
const snapshot21 = applySnapshot(applySnapshot(SNAPSHOT, [op2]), op1_t);
|
||||
applyRanges(rt21, [op2]);
|
||||
applyRanges(rt21, op1_t);
|
||||
|
||||
if (snapshot12 !== snapshot21) {
|
||||
console.error({op1, op2, op1_t, op2_t, snapshot12, snapshot21}, "Ops are not consistent");
|
||||
throw new Error("OT is inconsistent");
|
||||
}
|
||||
|
||||
if (!commentsEqual(rt12.comments, rt21.comments)) {
|
||||
console.log(rt12.comments);
|
||||
console.log(rt21.comments);
|
||||
console.error({op1, op2, op1_t, op2_t, rt12_comments: rt12.comments, rt21_comments: rt21.comments}, "Comments are not consistent");
|
||||
throw new Error("OT is inconsistent");
|
||||
} else {
|
||||
result1.push(undefined);
|
||||
}
|
||||
}
|
||||
return result1;
|
||||
})());
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}); });
|
||||
});
|
||||
return result
|
||||
})()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,126 +10,142 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* 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 {
|
||||
expect
|
||||
} = chai;
|
||||
const modulePath = "../../../../app/js/ShareJsDB.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const Errors = require("../../../../app/js/Errors");
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const { expect } = chai
|
||||
const modulePath = '../../../../app/js/ShareJsDB.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const Errors = require('../../../../app/js/Errors')
|
||||
|
||||
describe("ShareJsDB", function() {
|
||||
beforeEach(function() {
|
||||
this.doc_id = "document-id";
|
||||
this.project_id = "project-id";
|
||||
this.doc_key = `${this.project_id}:${this.doc_id}`;
|
||||
this.callback = sinon.stub();
|
||||
this.ShareJsDB = SandboxedModule.require(modulePath, { requires: {
|
||||
"./RedisManager": (this.RedisManager = {})
|
||||
}
|
||||
});
|
||||
describe('ShareJsDB', function () {
|
||||
beforeEach(function () {
|
||||
this.doc_id = 'document-id'
|
||||
this.project_id = 'project-id'
|
||||
this.doc_key = `${this.project_id}:${this.doc_id}`
|
||||
this.callback = sinon.stub()
|
||||
this.ShareJsDB = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./RedisManager': (this.RedisManager = {})
|
||||
}
|
||||
})
|
||||
|
||||
this.version = 42;
|
||||
this.lines = ["one", "two", "three"];
|
||||
return this.db = new this.ShareJsDB(this.project_id, this.doc_id, this.lines, this.version);
|
||||
});
|
||||
this.version = 42
|
||||
this.lines = ['one', 'two', 'three']
|
||||
return (this.db = new this.ShareJsDB(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.lines,
|
||||
this.version
|
||||
))
|
||||
})
|
||||
|
||||
describe("getSnapshot", function() {
|
||||
describe("successfully", function() {
|
||||
beforeEach(function() {
|
||||
return this.db.getSnapshot(this.doc_key, this.callback);
|
||||
});
|
||||
describe('getSnapshot', function () {
|
||||
describe('successfully', function () {
|
||||
beforeEach(function () {
|
||||
return this.db.getSnapshot(this.doc_key, this.callback)
|
||||
})
|
||||
|
||||
it("should return the doc lines", function() {
|
||||
return this.callback.args[0][1].snapshot.should.equal(this.lines.join("\n"));
|
||||
});
|
||||
it('should return the doc lines', function () {
|
||||
return this.callback.args[0][1].snapshot.should.equal(
|
||||
this.lines.join('\n')
|
||||
)
|
||||
})
|
||||
|
||||
it("should return the doc version", function() {
|
||||
return this.callback.args[0][1].v.should.equal(this.version);
|
||||
});
|
||||
it('should return the doc version', function () {
|
||||
return this.callback.args[0][1].v.should.equal(this.version)
|
||||
})
|
||||
|
||||
return it("should return the type as text", function() {
|
||||
return this.callback.args[0][1].type.should.equal("text");
|
||||
});
|
||||
});
|
||||
return it('should return the type as text', function () {
|
||||
return this.callback.args[0][1].type.should.equal('text')
|
||||
})
|
||||
})
|
||||
|
||||
return describe("when the key does not match", function() {
|
||||
beforeEach(function() {
|
||||
return this.db.getSnapshot("bad:key", this.callback);
|
||||
});
|
||||
return describe('when the key does not match', function () {
|
||||
beforeEach(function () {
|
||||
return this.db.getSnapshot('bad:key', this.callback)
|
||||
})
|
||||
|
||||
return it("should return the callback with a NotFoundError", function() {
|
||||
return this.callback.calledWith(new Errors.NotFoundError("not found")).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should return the callback with a NotFoundError', function () {
|
||||
return this.callback
|
||||
.calledWith(new Errors.NotFoundError('not found'))
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("getOps", function() {
|
||||
describe("with start == end", function() {
|
||||
beforeEach(function() {
|
||||
this.start = (this.end = 42);
|
||||
return this.db.getOps(this.doc_key, this.start, this.end, this.callback);
|
||||
});
|
||||
describe('getOps', function () {
|
||||
describe('with start == end', function () {
|
||||
beforeEach(function () {
|
||||
this.start = this.end = 42
|
||||
return this.db.getOps(this.doc_key, this.start, this.end, this.callback)
|
||||
})
|
||||
|
||||
return it("should return an empty array", function() {
|
||||
return this.callback.calledWith(null, []).should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with a non empty range", function() {
|
||||
beforeEach(function() {
|
||||
this.start = 35;
|
||||
this.end = 42;
|
||||
this.RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, this.ops);
|
||||
return this.db.getOps(this.doc_key, this.start, this.end, this.callback);
|
||||
});
|
||||
return it('should return an empty array', function () {
|
||||
return this.callback.calledWith(null, []).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should get the range from redis", function() {
|
||||
return this.RedisManager.getPreviousDocOps
|
||||
.calledWith(this.doc_id, this.start, this.end-1)
|
||||
.should.equal(true);
|
||||
});
|
||||
describe('with a non empty range', function () {
|
||||
beforeEach(function () {
|
||||
this.start = 35
|
||||
this.end = 42
|
||||
this.RedisManager.getPreviousDocOps = sinon
|
||||
.stub()
|
||||
.callsArgWith(3, null, this.ops)
|
||||
return this.db.getOps(this.doc_key, this.start, this.end, this.callback)
|
||||
})
|
||||
|
||||
return it("should return the ops", function() {
|
||||
return this.callback.calledWith(null, this.ops).should.equal(true);
|
||||
});
|
||||
});
|
||||
it('should get the range from redis', function () {
|
||||
return this.RedisManager.getPreviousDocOps
|
||||
.calledWith(this.doc_id, this.start, this.end - 1)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return describe("with no specified end", function() {
|
||||
beforeEach(function() {
|
||||
this.start = 35;
|
||||
this.end = null;
|
||||
this.RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, this.ops);
|
||||
return this.db.getOps(this.doc_key, this.start, this.end, this.callback);
|
||||
});
|
||||
|
||||
return it("should get until the end of the list", function() {
|
||||
return this.RedisManager.getPreviousDocOps
|
||||
.calledWith(this.doc_id, this.start, -1)
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should return the ops', function () {
|
||||
return this.callback.calledWith(null, this.ops).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("writeOps", function() { return describe("writing an op", function() {
|
||||
beforeEach(function() {
|
||||
this.opData = {
|
||||
op: {p: 20, t: "foo"},
|
||||
meta: {source: "bar"},
|
||||
v: this.version
|
||||
};
|
||||
return this.db.writeOp(this.doc_key, this.opData, this.callback);
|
||||
});
|
||||
return describe('with no specified end', function () {
|
||||
beforeEach(function () {
|
||||
this.start = 35
|
||||
this.end = null
|
||||
this.RedisManager.getPreviousDocOps = sinon
|
||||
.stub()
|
||||
.callsArgWith(3, null, this.ops)
|
||||
return this.db.getOps(this.doc_key, this.start, this.end, this.callback)
|
||||
})
|
||||
|
||||
it("should write into appliedOps", function() {
|
||||
return expect(this.db.appliedOps[this.doc_key]).to.deep.equal([this.opData]);
|
||||
});
|
||||
return it('should get until the end of the list', function () {
|
||||
return this.RedisManager.getPreviousDocOps
|
||||
.calledWith(this.doc_id, this.start, -1)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return it("should call the callback without an error", function() {
|
||||
this.callback.called.should.equal(true);
|
||||
return (this.callback.args[0][0] != null).should.equal(false);
|
||||
});
|
||||
}); });
|
||||
});
|
||||
return describe('writeOps', function () {
|
||||
return describe('writing an op', function () {
|
||||
beforeEach(function () {
|
||||
this.opData = {
|
||||
op: { p: 20, t: 'foo' },
|
||||
meta: { source: 'bar' },
|
||||
v: this.version
|
||||
}
|
||||
return this.db.writeOp(this.doc_key, this.opData, this.callback)
|
||||
})
|
||||
|
||||
it('should write into appliedOps', function () {
|
||||
return expect(this.db.appliedOps[this.doc_key]).to.deep.equal([
|
||||
this.opData
|
||||
])
|
||||
})
|
||||
|
||||
return it('should call the callback without an error', function () {
|
||||
this.callback.called.should.equal(true)
|
||||
return (this.callback.args[0][0] != null).should.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -9,179 +9,230 @@
|
|||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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/ShareJsUpdateManager.js";
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const crypto = require('crypto');
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
const should = chai.should()
|
||||
const modulePath = '../../../../app/js/ShareJsUpdateManager.js'
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const crypto = require('crypto')
|
||||
|
||||
describe("ShareJsUpdateManager", function() {
|
||||
beforeEach(function() {
|
||||
let Model;
|
||||
this.project_id = "project-id-123";
|
||||
this.doc_id = "document-id-123";
|
||||
this.callback = sinon.stub();
|
||||
return this.ShareJsUpdateManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
"./sharejs/server/model":
|
||||
(Model = class Model {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
}),
|
||||
"./ShareJsDB" : (this.ShareJsDB = { mockDB: true }),
|
||||
"redis-sharelatex" : { createClient: () => { return this.rclient = {auth() {}}; }
|
||||
},
|
||||
"logger-sharelatex": (this.logger = { log: sinon.stub() }),
|
||||
"./RealTimeRedisManager": (this.RealTimeRedisManager = {}),
|
||||
"./Metrics": (this.metrics = { inc: sinon.stub() })
|
||||
},
|
||||
globals: {
|
||||
clearTimeout: (this.clearTimeout = sinon.stub())
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
describe('ShareJsUpdateManager', function () {
|
||||
beforeEach(function () {
|
||||
let Model
|
||||
this.project_id = 'project-id-123'
|
||||
this.doc_id = 'document-id-123'
|
||||
this.callback = sinon.stub()
|
||||
return (this.ShareJsUpdateManager = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./sharejs/server/model': (Model = class Model {
|
||||
constructor(db) {
|
||||
this.db = db
|
||||
}
|
||||
}),
|
||||
'./ShareJsDB': (this.ShareJsDB = { mockDB: true }),
|
||||
'redis-sharelatex': {
|
||||
createClient: () => {
|
||||
return (this.rclient = { auth() {} })
|
||||
}
|
||||
},
|
||||
'logger-sharelatex': (this.logger = { log: sinon.stub() }),
|
||||
'./RealTimeRedisManager': (this.RealTimeRedisManager = {}),
|
||||
'./Metrics': (this.metrics = { inc: sinon.stub() })
|
||||
},
|
||||
globals: {
|
||||
clearTimeout: (this.clearTimeout = sinon.stub())
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
describe("applyUpdate", function() {
|
||||
beforeEach(function() {
|
||||
this.lines = ["one", "two"];
|
||||
this.version = 34;
|
||||
this.updatedDocLines = ["onefoo", "two"];
|
||||
const content = this.updatedDocLines.join("\n");
|
||||
this.hash = crypto.createHash('sha1').update("blob " + content.length + "\x00").update(content, 'utf8').digest('hex');
|
||||
this.update = {p: 4, t: "foo", v:this.version, hash:this.hash};
|
||||
this.model = {
|
||||
applyOp: sinon.stub().callsArg(2),
|
||||
getSnapshot: sinon.stub(),
|
||||
db: {
|
||||
appliedOps: {}
|
||||
}
|
||||
};
|
||||
this.ShareJsUpdateManager.getNewShareJsModel = sinon.stub().returns(this.model);
|
||||
this.ShareJsUpdateManager._listenForOps = sinon.stub();
|
||||
return this.ShareJsUpdateManager.removeDocFromCache = sinon.stub().callsArg(1);
|
||||
});
|
||||
describe('applyUpdate', function () {
|
||||
beforeEach(function () {
|
||||
this.lines = ['one', 'two']
|
||||
this.version = 34
|
||||
this.updatedDocLines = ['onefoo', 'two']
|
||||
const content = this.updatedDocLines.join('\n')
|
||||
this.hash = crypto
|
||||
.createHash('sha1')
|
||||
.update('blob ' + content.length + '\x00')
|
||||
.update(content, 'utf8')
|
||||
.digest('hex')
|
||||
this.update = { p: 4, t: 'foo', v: this.version, hash: this.hash }
|
||||
this.model = {
|
||||
applyOp: sinon.stub().callsArg(2),
|
||||
getSnapshot: sinon.stub(),
|
||||
db: {
|
||||
appliedOps: {}
|
||||
}
|
||||
}
|
||||
this.ShareJsUpdateManager.getNewShareJsModel = sinon
|
||||
.stub()
|
||||
.returns(this.model)
|
||||
this.ShareJsUpdateManager._listenForOps = sinon.stub()
|
||||
return (this.ShareJsUpdateManager.removeDocFromCache = sinon
|
||||
.stub()
|
||||
.callsArg(1))
|
||||
})
|
||||
|
||||
describe("successfully", function() {
|
||||
beforeEach(function(done) {
|
||||
this.model.getSnapshot.callsArgWith(1, null, {snapshot: this.updatedDocLines.join("\n"), v: this.version});
|
||||
this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] = (this.appliedOps = ["mock-ops"]);
|
||||
return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version, appliedOps) => {
|
||||
this.callback(err, docLines, version, appliedOps);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('successfully', function () {
|
||||
beforeEach(function (done) {
|
||||
this.model.getSnapshot.callsArgWith(1, null, {
|
||||
snapshot: this.updatedDocLines.join('\n'),
|
||||
v: this.version
|
||||
})
|
||||
this.model.db.appliedOps[
|
||||
`${this.project_id}:${this.doc_id}`
|
||||
] = this.appliedOps = ['mock-ops']
|
||||
return this.ShareJsUpdateManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.update,
|
||||
this.lines,
|
||||
this.version,
|
||||
(err, docLines, version, appliedOps) => {
|
||||
this.callback(err, docLines, version, appliedOps)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a new ShareJs model", function() {
|
||||
return this.ShareJsUpdateManager.getNewShareJsModel
|
||||
.calledWith(this.project_id, this.doc_id, this.lines, this.version)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should create a new ShareJs model', function () {
|
||||
return this.ShareJsUpdateManager.getNewShareJsModel
|
||||
.calledWith(this.project_id, this.doc_id, this.lines, this.version)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should listen for ops on the model", function() {
|
||||
return this.ShareJsUpdateManager._listenForOps
|
||||
.calledWith(this.model)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should listen for ops on the model', function () {
|
||||
return this.ShareJsUpdateManager._listenForOps
|
||||
.calledWith(this.model)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should send the update to ShareJs", function() {
|
||||
return this.model.applyOp
|
||||
.calledWith(`${this.project_id}:${this.doc_id}`, this.update)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should send the update to ShareJs', function () {
|
||||
return this.model.applyOp
|
||||
.calledWith(`${this.project_id}:${this.doc_id}`, this.update)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it("should get the updated doc lines", function() {
|
||||
return this.model.getSnapshot
|
||||
.calledWith(`${this.project_id}:${this.doc_id}`)
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should get the updated doc lines', function () {
|
||||
return this.model.getSnapshot
|
||||
.calledWith(`${this.project_id}:${this.doc_id}`)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
return it("should return the updated doc lines, version and ops", function() {
|
||||
return this.callback.calledWith(null, this.updatedDocLines, this.version, this.appliedOps).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should return the updated doc lines, version and ops', function () {
|
||||
return this.callback
|
||||
.calledWith(null, this.updatedDocLines, this.version, this.appliedOps)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when applyOp fails", function() {
|
||||
beforeEach(function(done) {
|
||||
this.error = new Error("Something went wrong");
|
||||
this.model.applyOp = sinon.stub().callsArgWith(2, this.error);
|
||||
return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version) => {
|
||||
this.callback(err, docLines, version);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('when applyOp fails', function () {
|
||||
beforeEach(function (done) {
|
||||
this.error = new Error('Something went wrong')
|
||||
this.model.applyOp = sinon.stub().callsArgWith(2, this.error)
|
||||
return this.ShareJsUpdateManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.update,
|
||||
this.lines,
|
||||
this.version,
|
||||
(err, docLines, version) => {
|
||||
this.callback(err, docLines, version)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when getSnapshot fails", function() {
|
||||
beforeEach(function(done) {
|
||||
this.error = new Error("Something went wrong");
|
||||
this.model.getSnapshot.callsArgWith(1, this.error);
|
||||
return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version) => {
|
||||
this.callback(err, docLines, version);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('when getSnapshot fails', function () {
|
||||
beforeEach(function (done) {
|
||||
this.error = new Error('Something went wrong')
|
||||
this.model.getSnapshot.callsArgWith(1, this.error)
|
||||
return this.ShareJsUpdateManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.update,
|
||||
this.lines,
|
||||
this.version,
|
||||
(err, docLines, version) => {
|
||||
this.callback(err, docLines, version)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe("with an invalid hash", function() {
|
||||
beforeEach(function(done) {
|
||||
this.error = new Error("invalid hash");
|
||||
this.model.getSnapshot.callsArgWith(1, null, {snapshot: "unexpected content", v: this.version});
|
||||
this.model.db.appliedOps[`${this.project_id}:${this.doc_id}`] = (this.appliedOps = ["mock-ops"]);
|
||||
return this.ShareJsUpdateManager.applyUpdate(this.project_id, this.doc_id, this.update, this.lines, this.version, (err, docLines, version, appliedOps) => {
|
||||
this.callback(err, docLines, version, appliedOps);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return describe('with an invalid hash', function () {
|
||||
beforeEach(function (done) {
|
||||
this.error = new Error('invalid hash')
|
||||
this.model.getSnapshot.callsArgWith(1, null, {
|
||||
snapshot: 'unexpected content',
|
||||
v: this.version
|
||||
})
|
||||
this.model.db.appliedOps[
|
||||
`${this.project_id}:${this.doc_id}`
|
||||
] = this.appliedOps = ['mock-ops']
|
||||
return this.ShareJsUpdateManager.applyUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.update,
|
||||
this.lines,
|
||||
this.version,
|
||||
(err, docLines, version, appliedOps) => {
|
||||
this.callback(err, docLines, version, appliedOps)
|
||||
return done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return it("should call the callback with the error", function() {
|
||||
return this.callback.calledWith(this.error).should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
return it('should call the callback with the error', function () {
|
||||
return this.callback.calledWith(this.error).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return describe("_listenForOps", function() {
|
||||
beforeEach(function() {
|
||||
this.model = { on: (event, callback) => {
|
||||
return this.callback = callback;
|
||||
}
|
||||
};
|
||||
sinon.spy(this.model, "on");
|
||||
return this.ShareJsUpdateManager._listenForOps(this.model);
|
||||
});
|
||||
return describe('_listenForOps', function () {
|
||||
beforeEach(function () {
|
||||
this.model = {
|
||||
on: (event, callback) => {
|
||||
return (this.callback = callback)
|
||||
}
|
||||
}
|
||||
sinon.spy(this.model, 'on')
|
||||
return this.ShareJsUpdateManager._listenForOps(this.model)
|
||||
})
|
||||
|
||||
it("should listen to the model for updates", function() {
|
||||
return this.model.on.calledWith("applyOp")
|
||||
.should.equal(true);
|
||||
});
|
||||
it('should listen to the model for updates', function () {
|
||||
return this.model.on.calledWith('applyOp').should.equal(true)
|
||||
})
|
||||
|
||||
return describe("the callback", function() {
|
||||
beforeEach(function() {
|
||||
this.opData = {
|
||||
op: {t: "foo", p: 1},
|
||||
meta: { source: "bar"
|
||||
}
|
||||
};
|
||||
this.RealTimeRedisManager.sendData = sinon.stub();
|
||||
return this.callback(`${this.project_id}:${this.doc_id}`, this.opData);
|
||||
});
|
||||
|
||||
return it("should publish the op to redis", function() {
|
||||
return this.RealTimeRedisManager.sendData
|
||||
.calledWith({project_id: this.project_id, doc_id: this.doc_id, op: this.opData})
|
||||
.should.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return describe('the callback', function () {
|
||||
beforeEach(function () {
|
||||
this.opData = {
|
||||
op: { t: 'foo', p: 1 },
|
||||
meta: { source: 'bar' }
|
||||
}
|
||||
this.RealTimeRedisManager.sendData = sinon.stub()
|
||||
return this.callback(`${this.project_id}:${this.doc_id}`, this.opData)
|
||||
})
|
||||
|
||||
return it('should publish the op to redis', function () {
|
||||
return this.RealTimeRedisManager.sendData
|
||||
.calledWith({
|
||||
project_id: this.project_id,
|
||||
doc_id: this.doc_id,
|
||||
op: this.opData
|
||||
})
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue