2020-05-06 06:12:36 -04:00
|
|
|
/* eslint-disable
|
|
|
|
camelcase,
|
|
|
|
handle-callback-err,
|
|
|
|
no-return-assign,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
2020-05-06 06:12:17 -04:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* 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");
|
|
|
|
chai.should();
|
|
|
|
const Settings = require('settings-sharelatex');
|
|
|
|
const rclient_project_history = require("redis-sharelatex").createClient(Settings.redis.project_history);
|
|
|
|
const ProjectHistoryKeys = Settings.redis.project_history.key_schema;
|
|
|
|
|
|
|
|
const MockProjectHistoryApi = require("./helpers/MockProjectHistoryApi");
|
|
|
|
const MockWebApi = require("./helpers/MockWebApi");
|
|
|
|
const DocUpdaterClient = require("./helpers/DocUpdaterClient");
|
|
|
|
const DocUpdaterApp = require("./helpers/DocUpdaterApp");
|
|
|
|
|
|
|
|
describe("Applying updates to a project's structure", function() {
|
|
|
|
before(function() {
|
|
|
|
this.user_id = 'user-id-123';
|
|
|
|
return this.version = 1234;
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("renaming a file", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
|
|
|
this.fileUpdate = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/file-path',
|
2017-11-06 12:18:28 -05:00
|
|
|
newPathname: '/new-file-path'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
this.fileUpdates = [ this.fileUpdate ];
|
|
|
|
return DocUpdaterApp.ensureRunning(error => {
|
|
|
|
if (error != null) { throw error; }
|
2020-05-06 06:12:36 -04:00
|
|
|
return DocUpdaterClient.sendProjectUpdate(this.project_id, this.user_id, [], this.fileUpdates, this.version, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 200);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should push the applied file renames to the project history api", function(done) {
|
|
|
|
rclient_project_history.lrange(ProjectHistoryKeys.projectHistoryOps({project_id: this.project_id}), 0, -1, (error, updates) => {
|
|
|
|
if (error != null) { throw error; }
|
|
|
|
|
|
|
|
const update = JSON.parse(updates[0]);
|
|
|
|
update.file.should.equal(this.fileUpdate.id);
|
|
|
|
update.pathname.should.equal('/file-path');
|
|
|
|
update.new_pathname.should.equal('/new-file-path');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.0`);
|
|
|
|
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("renaming a document", function() {
|
|
|
|
before(function() {
|
|
|
|
this.docUpdate = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/doc-path',
|
2017-11-06 12:18:28 -05:00
|
|
|
newPathname: '/new-doc-path'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
return this.docUpdates = [ this.docUpdate ];});
|
|
|
|
|
|
|
|
describe("when the document is not loaded", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
2020-05-06 06:12:36 -04:00
|
|
|
DocUpdaterClient.sendProjectUpdate(this.project_id, this.user_id, this.docUpdates, [], this.version, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 200);
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should push the applied doc renames to the project history api", function(done) {
|
|
|
|
rclient_project_history.lrange(ProjectHistoryKeys.projectHistoryOps({project_id: this.project_id}), 0, -1, (error, updates) => {
|
|
|
|
if (error != null) { throw error; }
|
|
|
|
|
|
|
|
const update = JSON.parse(updates[0]);
|
|
|
|
update.doc.should.equal(this.docUpdate.id);
|
|
|
|
update.pathname.should.equal('/doc-path');
|
|
|
|
update.new_pathname.should.equal('/new-doc-path');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.0`);
|
|
|
|
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("when the document is loaded", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
|
|
|
MockWebApi.insertDoc(this.project_id, this.docUpdate.id, {});
|
|
|
|
DocUpdaterClient.preloadDoc(this.project_id, this.docUpdate.id, error => {
|
|
|
|
if (error != null) { throw error; }
|
|
|
|
sinon.spy(MockWebApi, "getDocument");
|
2020-05-06 06:12:36 -04:00
|
|
|
return DocUpdaterClient.sendProjectUpdate(this.project_id, this.user_id, this.docUpdates, [], this.version, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 200);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
2020-05-06 06:12:36 -04:00
|
|
|
after(function() { return MockWebApi.getDocument.restore(); });
|
2020-05-06 06:12:17 -04:00
|
|
|
|
|
|
|
it("should update the doc", function(done) {
|
|
|
|
DocUpdaterClient.getDoc(this.project_id, this.docUpdate.id, (error, res, doc) => {
|
|
|
|
doc.pathname.should.equal(this.docUpdate.newPathname);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should push the applied doc renames to the project history api", function(done) {
|
|
|
|
rclient_project_history.lrange(ProjectHistoryKeys.projectHistoryOps({project_id: this.project_id}), 0, -1, (error, updates) => {
|
|
|
|
if (error != null) { throw error; }
|
|
|
|
|
|
|
|
const update = JSON.parse(updates[0]);
|
|
|
|
update.doc.should.equal(this.docUpdate.id);
|
|
|
|
update.pathname.should.equal('/doc-path');
|
|
|
|
update.new_pathname.should.equal('/new-doc-path');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.0`);
|
|
|
|
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("renaming multiple documents and files", function() {
|
|
|
|
before(function() {
|
|
|
|
this.docUpdate0 = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/doc-path0',
|
2018-03-08 04:38:24 -05:00
|
|
|
newPathname: '/new-doc-path0'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
this.docUpdate1 = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/doc-path1',
|
2018-03-08 04:38:24 -05:00
|
|
|
newPathname: '/new-doc-path1'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
this.docUpdates = [ this.docUpdate0, this.docUpdate1 ];
|
|
|
|
this.fileUpdate0 = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/file-path0',
|
2018-03-08 04:38:24 -05:00
|
|
|
newPathname: '/new-file-path0'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
this.fileUpdate1 = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/file-path1',
|
2018-03-08 04:38:24 -05:00
|
|
|
newPathname: '/new-file-path1'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
return this.fileUpdates = [ this.fileUpdate0, this.fileUpdate1 ];});
|
|
|
|
|
|
|
|
return describe("when the documents are not loaded", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
2020-05-06 06:12:36 -04:00
|
|
|
DocUpdaterClient.sendProjectUpdate(this.project_id, this.user_id, this.docUpdates, this.fileUpdates, this.version, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 200);
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should push the applied doc renames to the project history api", function(done) {
|
|
|
|
rclient_project_history.lrange(ProjectHistoryKeys.projectHistoryOps({project_id: this.project_id}), 0, -1, (error, updates) => {
|
|
|
|
if (error != null) { throw error; }
|
|
|
|
|
|
|
|
let update = JSON.parse(updates[0]);
|
|
|
|
update.doc.should.equal(this.docUpdate0.id);
|
|
|
|
update.pathname.should.equal('/doc-path0');
|
|
|
|
update.new_pathname.should.equal('/new-doc-path0');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.0`);
|
|
|
|
|
|
|
|
update = JSON.parse(updates[1]);
|
|
|
|
update.doc.should.equal(this.docUpdate1.id);
|
|
|
|
update.pathname.should.equal('/doc-path1');
|
|
|
|
update.new_pathname.should.equal('/new-doc-path1');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.1`);
|
|
|
|
|
|
|
|
update = JSON.parse(updates[2]);
|
|
|
|
update.file.should.equal(this.fileUpdate0.id);
|
|
|
|
update.pathname.should.equal('/file-path0');
|
|
|
|
update.new_pathname.should.equal('/new-file-path0');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.2`);
|
|
|
|
|
|
|
|
update = JSON.parse(updates[3]);
|
|
|
|
update.file.should.equal(this.fileUpdate1.id);
|
|
|
|
update.pathname.should.equal('/file-path1');
|
|
|
|
update.new_pathname.should.equal('/new-file-path1');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.3`);
|
|
|
|
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("adding a file", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
|
|
|
this.fileUpdate = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/file-path',
|
2017-11-10 10:01:37 -05:00
|
|
|
url: 'filestore.example.com'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
this.fileUpdates = [ this.fileUpdate ];
|
2020-05-06 06:12:36 -04:00
|
|
|
DocUpdaterClient.sendProjectUpdate(this.project_id, this.user_id, [], this.fileUpdates, this.version, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 200);
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should push the file addition to the project history api", function(done) {
|
|
|
|
rclient_project_history.lrange(ProjectHistoryKeys.projectHistoryOps({project_id: this.project_id}), 0, -1, (error, updates) => {
|
|
|
|
if (error != null) { throw error; }
|
|
|
|
|
|
|
|
const update = JSON.parse(updates[0]);
|
|
|
|
update.file.should.equal(this.fileUpdate.id);
|
|
|
|
update.pathname.should.equal('/file-path');
|
|
|
|
update.url.should.equal('filestore.example.com');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.0`);
|
|
|
|
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("adding a doc", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
|
|
|
this.docUpdate = {
|
|
|
|
id: DocUpdaterClient.randomId(),
|
|
|
|
pathname: '/file-path',
|
2017-11-10 10:01:37 -05:00
|
|
|
docLines: 'a\nb'
|
2020-05-06 06:12:17 -04:00
|
|
|
};
|
|
|
|
this.docUpdates = [ this.docUpdate ];
|
2020-05-06 06:12:36 -04:00
|
|
|
DocUpdaterClient.sendProjectUpdate(this.project_id, this.user_id, this.docUpdates, [], this.version, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 200);
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should push the doc addition to the project history api", function(done) {
|
|
|
|
rclient_project_history.lrange(ProjectHistoryKeys.projectHistoryOps({project_id: this.project_id}), 0, -1, (error, updates) => {
|
|
|
|
if (error != null) { throw error; }
|
|
|
|
|
|
|
|
const update = JSON.parse(updates[0]);
|
|
|
|
update.doc.should.equal(this.docUpdate.id);
|
|
|
|
update.pathname.should.equal('/file-path');
|
|
|
|
update.docLines.should.equal('a\nb');
|
|
|
|
update.meta.user_id.should.equal(this.user_id);
|
|
|
|
update.meta.ts.should.be.a('string');
|
|
|
|
update.version.should.equal(`${this.version}.0`);
|
|
|
|
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("with enough updates to flush to the history service", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
|
|
|
this.user_id = DocUpdaterClient.randomId();
|
|
|
|
this.version0 = 12345;
|
|
|
|
this.version1 = this.version0 + 1;
|
|
|
|
const updates = [];
|
|
|
|
for (let v = 0; v <= 599; v++) { // Should flush after 500 ops
|
|
|
|
updates.push({
|
2018-01-26 06:53:49 -05:00
|
|
|
id: DocUpdaterClient.randomId(),
|
2020-05-06 06:12:17 -04:00
|
|
|
pathname: '/file-' + v,
|
2018-01-26 06:53:49 -05:00
|
|
|
docLines: 'a\nb'
|
2020-05-06 06:12:17 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
sinon.spy(MockProjectHistoryApi, "flushProject");
|
|
|
|
|
|
|
|
// Send updates in chunks to causes multiple flushes
|
|
|
|
const projectId = this.project_id;
|
|
|
|
const userId = this.project_id;
|
|
|
|
DocUpdaterClient.sendProjectUpdate(projectId, userId, updates.slice(0, 250), [], this.version0, function(error) {
|
|
|
|
if (error != null) { throw error; }
|
2020-05-06 06:12:36 -04:00
|
|
|
return DocUpdaterClient.sendProjectUpdate(projectId, userId, updates.slice(250), [], this.version1, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 2000);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
2020-05-06 06:12:36 -04:00
|
|
|
after(function() { return MockProjectHistoryApi.flushProject.restore(); });
|
2020-05-06 06:12:17 -04:00
|
|
|
|
|
|
|
return it("should flush project history", function() {
|
|
|
|
return MockProjectHistoryApi.flushProject.calledWith(this.project_id).should.equal(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("with too few updates to flush to the history service", function() {
|
|
|
|
before(function(done) {
|
|
|
|
this.project_id = DocUpdaterClient.randomId();
|
|
|
|
this.user_id = DocUpdaterClient.randomId();
|
|
|
|
this.version0 = 12345;
|
|
|
|
this.version1 = this.version0 + 1;
|
|
|
|
|
|
|
|
const updates = [];
|
|
|
|
for (let v = 0; v <= 42; v++) { // Should flush after 500 ops
|
|
|
|
updates.push({
|
2018-01-31 06:41:08 -05:00
|
|
|
id: DocUpdaterClient.randomId(),
|
2020-05-06 06:12:17 -04:00
|
|
|
pathname: '/file-' + v,
|
2018-01-31 06:41:08 -05:00
|
|
|
docLines: 'a\nb'
|
2020-05-06 06:12:17 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
sinon.spy(MockProjectHistoryApi, "flushProject");
|
|
|
|
|
|
|
|
// Send updates in chunks
|
|
|
|
const projectId = this.project_id;
|
|
|
|
const userId = this.project_id;
|
|
|
|
DocUpdaterClient.sendProjectUpdate(projectId, userId, updates.slice(0, 10), [], this.version0, function(error) {
|
|
|
|
if (error != null) { throw error; }
|
2020-05-06 06:12:36 -04:00
|
|
|
return DocUpdaterClient.sendProjectUpdate(projectId, userId, updates.slice(10), [], this.version1, (error) => {
|
2020-05-06 06:12:17 -04:00
|
|
|
if (error != null) { throw error; }
|
|
|
|
return setTimeout(done, 2000);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
|
2020-05-06 06:12:36 -04:00
|
|
|
after(function() { return MockProjectHistoryApi.flushProject.restore(); });
|
2020-05-06 06:12:17 -04:00
|
|
|
|
|
|
|
return it("should not flush project history", function() {
|
|
|
|
return MockProjectHistoryApi.flushProject.calledWith(this.project_id).should.equal(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|