decaffeinate: Convert AppendingUpdatesTests.coffee and 11 other files to JS

This commit is contained in:
decaffeinate 2020-02-17 18:35:39 +01:00 committed by mserranom
parent c97a2a3c07
commit 11c39cde65
12 changed files with 1331 additions and 952 deletions

View file

@ -1,311 +1,387 @@
sinon = require "sinon" /*
chai = require("chai") * decaffeinate suggestions:
chai.should() * DS102: Remove unnecessary code created because of implicit returns
expect = chai.expect * DS207: Consider shorter variations of null checks
mongojs = require "../../../app/js/mongojs" * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
ObjectId = mongojs.ObjectId */
Settings = require "settings-sharelatex" const sinon = require("sinon");
request = require "request" const chai = require("chai");
rclient = require("redis").createClient(Settings.redis.history) # Only works locally for now chai.should();
const { expect } = chai;
const mongojs = require("../../../app/js/mongojs");
const { ObjectId } = mongojs;
const Settings = require("settings-sharelatex");
const request = require("request");
const rclient = require("redis").createClient(Settings.redis.history); // Only works locally for now
TrackChangesApp = require "./helpers/TrackChangesApp" const TrackChangesApp = require("./helpers/TrackChangesApp");
TrackChangesClient = require "./helpers/TrackChangesClient" const TrackChangesClient = require("./helpers/TrackChangesClient");
MockWebApi = require "./helpers/MockWebApi" const MockWebApi = require("./helpers/MockWebApi");
describe "Appending doc ops to the history", -> describe("Appending doc ops to the history", function() {
before (done)-> before(done=> TrackChangesApp.ensureRunning(done));
TrackChangesApp.ensureRunning done
describe "when the history does not exist yet", -> describe("when the history does not exist yet", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: false MockWebApi.projects[this.project_id] = {features: {versioning: false}};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}, { }, {
op: [{ i: "o", p: 4 }] op: [{ i: "o", p: 4 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 4 v: 4
}, { }, {
op: [{ i: "o", p: 5 }] op: [{ i: "o", p: 5 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 5 v: 5
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should insert the compressed op into mongo", -> it("should insert the compressed op into mongo", function() {
expect(@updates[0].pack[0].op).to.deep.equal [{ return expect(this.updates[0].pack[0].op).to.deep.equal([{
p: 3, i: "foo" p: 3, i: "foo"
}] }]);
});
it "should insert the correct version number into mongo", -> it("should insert the correct version number into mongo", function() {
expect(@updates[0].v).to.equal 5 return expect(this.updates[0].v).to.equal(5);
});
it "should store the doc id", -> it("should store the doc id", function() {
expect(@updates[0].doc_id.toString()).to.equal @doc_id return expect(this.updates[0].doc_id.toString()).to.equal(this.doc_id);
});
it "should store the project id", -> it("should store the project id", function() {
expect(@updates[0].project_id.toString()).to.equal @project_id return expect(this.updates[0].project_id.toString()).to.equal(this.project_id);
});
it "should clear the doc from the DocsWithHistoryOps set", (done) -> return it("should clear the doc from the DocsWithHistoryOps set", function(done) {
rclient.sismember "DocsWithHistoryOps:#{@project_id}", @doc_id, (error, member) -> rclient.sismember(`DocsWithHistoryOps:${this.project_id}`, this.doc_id, function(error, member) {
member.should.equal 0 member.should.equal(0);
done() return done();
return null });
return null;
});
});
describe "when the history has already been started", -> describe("when the history has already been started", function() {
beforeEach (done) -> beforeEach(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: false MockWebApi.projects[this.project_id] = {features: {versioning: false}};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}, { }, {
op: [{ i: "o", p: 4 }] op: [{ i: "o", p: 4 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 4 v: 4
}, { }, {
op: [{ i: "o", p: 5 }] op: [{ i: "o", p: 5 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 5 v: 5
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? if (error != null) { throw error; }
done() return done();
return null });
});
return null;
});
describe "when the updates are recent and from the same user", -> describe("when the updates are recent and from the same user", function() {
beforeEach (done) -> beforeEach(function(done) {
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "b", p: 6 }] op: [{ i: "b", p: 6 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 6 v: 6
}, { }, {
op: [{ i: "a", p: 7 }] op: [{ i: "a", p: 7 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 7 v: 7
}, { }, {
op: [{ i: "r", p: 8 }] op: [{ i: "r", p: 8 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 8 v: 8
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should combine all the updates into one pack", -> it("should combine all the updates into one pack", function() {
expect(@updates[0].pack[1].op).to.deep.equal [{ return expect(this.updates[0].pack[1].op).to.deep.equal([{
p: 6, i: "bar" p: 6, i: "bar"
}] }]);
});
it "should insert the correct version number into mongo", -> return it("should insert the correct version number into mongo", function() {
expect(@updates[0].v_end).to.equal 8 return expect(this.updates[0].v_end).to.equal(8);
});
});
describe "when the updates are far apart", -> return describe("when the updates are far apart", function() {
beforeEach (done) -> beforeEach(function(done) {
oneDay = 24 * 60 * 60 * 1000 const oneDay = 24 * 60 * 60 * 1000;
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "b", p: 6 }] op: [{ i: "b", p: 6 }],
meta: { ts: Date.now() + oneDay, user_id: @user_id } meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 6 v: 6
}, { }, {
op: [{ i: "a", p: 7 }] op: [{ i: "a", p: 7 }],
meta: { ts: Date.now() + oneDay, user_id: @user_id } meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 7 v: 7
}, { }, {
op: [{ i: "r", p: 8 }] op: [{ i: "r", p: 8 }],
meta: { ts: Date.now() + oneDay, user_id: @user_id } meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 8 v: 8
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should combine the updates into one pack", -> return it("should combine the updates into one pack", function() {
expect(@updates[0].pack[0].op).to.deep.equal [{ expect(this.updates[0].pack[0].op).to.deep.equal([{
p: 3, i: "foo" p: 3, i: "foo"
}] }]);
expect(@updates[0].pack[1].op).to.deep.equal [{ return expect(this.updates[0].pack[1].op).to.deep.equal([{
p: 6, i: "bar" p: 6, i: "bar"
}] }]);
});
});
});
describe "when the updates need processing in batches", -> describe("when the updates need processing in batches", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: false MockWebApi.projects[this.project_id] = {features: {versioning: false}};
updates = [] const updates = [];
@expectedOp = [{ p:0, i: "" }] this.expectedOp = [{ p:0, i: "" }];
for i in [0..250] for (let i = 0; i <= 250; i++) {
updates.push { updates.push({
op: [{i: "a", p: 0}] op: [{i: "a", p: 0}],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: i v: i
} });
@expectedOp[0].i = "a" + @expectedOp[0].i this.expectedOp[0].i = `a${this.expectedOp[0].i}`;
}
TrackChangesClient.pushRawUpdates @project_id, @doc_id, updates, (error) => TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, updates, error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates1) => {
throw error if error? this.updates = updates1;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should concat the compressed op into mongo", -> it("should concat the compressed op into mongo", function() {
expect(@updates[0].pack.length).to.deep.equal 3 # batch size is 100 return expect(this.updates[0].pack.length).to.deep.equal(3);
}); // batch size is 100
it "should insert the correct version number into mongo", -> return it("should insert the correct version number into mongo", function() {
expect(@updates[0].v_end).to.equal 250 return expect(this.updates[0].v_end).to.equal(250);
});
});
describe "when there are multiple ops in each update", -> describe("when there are multiple ops in each update", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: false MockWebApi.projects[this.project_id] = {features: {versioning: false}};
oneDay = 24 * 60 * 60 * 1000 const oneDay = 24 * 60 * 60 * 1000;
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "f", p: 3 }, { i: "o", p: 4 }, { i: "o", p: 5 }] op: [{ i: "f", p: 3 }, { i: "o", p: 4 }, { i: "o", p: 5 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}, { }, {
op: [{ i: "b", p: 6 }, { i: "a", p: 7 }, { i: "r", p: 8 }] op: [{ i: "b", p: 6 }, { i: "a", p: 7 }, { i: "r", p: 8 }],
meta: { ts: Date.now() + oneDay, user_id: @user_id } meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 4 v: 4
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should insert the compressed ops into mongo", -> it("should insert the compressed ops into mongo", function() {
expect(@updates[0].pack[0].op).to.deep.equal [{ expect(this.updates[0].pack[0].op).to.deep.equal([{
p: 3, i: "foo" p: 3, i: "foo"
}] }]);
expect(@updates[0].pack[1].op).to.deep.equal [{ return expect(this.updates[0].pack[1].op).to.deep.equal([{
p: 6, i: "bar" p: 6, i: "bar"
}] }]);
});
it "should insert the correct version numbers into mongo", -> return it("should insert the correct version numbers into mongo", function() {
expect(@updates[0].pack[0].v).to.equal 3 expect(this.updates[0].pack[0].v).to.equal(3);
expect(@updates[0].pack[1].v).to.equal 4 return expect(this.updates[0].pack[1].v).to.equal(4);
});
});
describe "when there is a no-op update", -> describe("when there is a no-op update", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: false MockWebApi.projects[this.project_id] = {features: {versioning: false}};
oneDay = 24 * 60 * 60 * 1000 const oneDay = 24 * 60 * 60 * 1000;
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [] op: [],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}, { }, {
op: [{ i: "foo", p: 3 }] op: [{ i: "foo", p: 3 }],
meta: { ts: Date.now() + oneDay, user_id: @user_id } meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 4 v: 4
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should insert the compressed no-op into mongo", -> it("should insert the compressed no-op into mongo", function() {
expect(@updates[0].pack[0].op).to.deep.equal [] return expect(this.updates[0].pack[0].op).to.deep.equal([]);
});
it "should insert the compressed next update into mongo", -> it("should insert the compressed next update into mongo", function() {
expect(@updates[0].pack[1].op).to.deep.equal [{ return expect(this.updates[0].pack[1].op).to.deep.equal([{
p: 3, i: "foo" p: 3, i: "foo"
}] }]);
});
it "should insert the correct version numbers into mongo", -> return it("should insert the correct version numbers into mongo", function() {
expect(@updates[0].pack[0].v).to.equal 3 expect(this.updates[0].pack[0].v).to.equal(3);
expect(@updates[0].pack[1].v).to.equal 4 return expect(this.updates[0].pack[1].v).to.equal(4);
});
});
describe "when there is a comment update", -> describe("when there is a comment update", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: false MockWebApi.projects[this.project_id] = {features: {versioning: false}};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ c: "foo", p: 3 }, {d: "bar", p: 6}] op: [{ c: "foo", p: 3 }, {d: "bar", p: 6}],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should ignore the comment op", -> it("should ignore the comment op", function() {
expect(@updates[0].pack[0].op).to.deep.equal [{d: "bar", p: 6}] return expect(this.updates[0].pack[0].op).to.deep.equal([{d: "bar", p: 6}]);
});
it "should insert the correct version numbers into mongo", -> return it("should insert the correct version numbers into mongo", function() {
expect(@updates[0].pack[0].v).to.equal 3 return expect(this.updates[0].pack[0].v).to.equal(3);
});
});
describe "when the project has versioning enabled", -> describe("when the project has versioning enabled", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: true MockWebApi.projects[this.project_id] = {features: {versioning: true}};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should not add a expiresAt entry in the update in mongo", -> return it("should not add a expiresAt entry in the update in mongo", function() {
expect(@updates[0].expiresAt).to.be.undefined return expect(this.updates[0].expiresAt).to.be.undefined;
});
});
describe "when the project does not have versioning enabled", -> return describe("when the project does not have versioning enabled", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: false MockWebApi.projects[this.project_id] = {features: {versioning: false}};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushAndGetCompressedUpdates @project_id, @doc_id, (error, @updates) => return TrackChangesClient.flushAndGetCompressedUpdates(this.project_id, this.doc_id, (error, updates) => {
throw error if error? this.updates = updates;
done() if (error != null) { throw error; }
return null return done();
});
});
return null;
});
it "should add a expiresAt entry in the update in mongo", -> return it("should add a expiresAt entry in the update in mongo", function() {
expect(@updates[0].expiresAt).to.exist return expect(this.updates[0].expiresAt).to.exist;
});
});
});

View file

@ -1,134 +1,181 @@
sinon = require "sinon" /*
chai = require("chai") * decaffeinate suggestions:
chai.should() * DS102: Remove unnecessary code created because of implicit returns
expect = chai.expect * DS103: Rewrite code to no longer use __guard__
mongojs = require "../../../app/js/mongojs" * DS202: Simplify dynamic range loops
db = mongojs.db * DS207: Consider shorter variations of null checks
ObjectId = mongojs.ObjectId * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
Settings = require "settings-sharelatex" */
request = require "request" const sinon = require("sinon");
rclient = require("redis").createClient(Settings.redis.history) # Only works locally for now const chai = require("chai");
chai.should();
const { expect } = chai;
const mongojs = require("../../../app/js/mongojs");
const { db } = mongojs;
const { ObjectId } = mongojs;
const Settings = require("settings-sharelatex");
const request = require("request");
const rclient = require("redis").createClient(Settings.redis.history); // Only works locally for now
TrackChangesApp = require "./helpers/TrackChangesApp" const TrackChangesApp = require("./helpers/TrackChangesApp");
TrackChangesClient = require "./helpers/TrackChangesClient" const TrackChangesClient = require("./helpers/TrackChangesClient");
MockDocStoreApi = require "./helpers/MockDocStoreApi" const MockDocStoreApi = require("./helpers/MockDocStoreApi");
MockWebApi = require "./helpers/MockWebApi" const MockWebApi = require("./helpers/MockWebApi");
describe "Archiving updates", -> describe("Archiving updates", function() {
before (done) -> before(function(done) {
if Settings?.trackchanges?.s3?.key.length < 1 if (__guard__(__guard__(Settings != null ? Settings.trackchanges : undefined, x1 => x1.s3), x => x.key.length) < 1) {
message = new Error("s3 keys not setup, this test setup will fail") const message = new Error("s3 keys not setup, this test setup will fail");
return done(message) return done(message);
}
TrackChangesClient.waitForS3 done return TrackChangesClient.waitForS3(done);
});
before (done) -> before(function(done) {
@now = Date.now() this.now = Date.now();
@to = @now this.to = this.now;
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@minutes = 60 * 1000 this.minutes = 60 * 1000;
@hours = 60 * @minutes this.hours = 60 * this.minutes;
MockWebApi.projects[@project_id] = MockWebApi.projects[this.project_id] = {
features: features: {
versioning: true versioning: true
sinon.spy MockWebApi, "getProjectDetails"
MockWebApi.users[@user_id] = @user =
email: "user@sharelatex.com"
first_name: "Leo"
last_name: "Lion"
id: @user_id
sinon.spy MockWebApi, "getUserInfo"
MockDocStoreApi.docs[@doc_id] = @doc =
_id: @doc_id
project_id: @project_id
sinon.spy MockDocStoreApi, "getAllDoc"
@updates = []
for i in [0..512+10]
@updates.push {
op: [{ i: "a", p: 0 }]
meta: { ts: @now + (i-2048) * @hours, user_id: @user_id }
v: 2 * i + 1
} }
@updates.push { };
op: [{ i: "b", p: 0 }] sinon.spy(MockWebApi, "getProjectDetails");
meta: { ts: @now + (i-2048) * @hours + 10*@minutes, user_id: @user_id }
v: 2 * i + 2
}
TrackChangesApp.ensureRunning =>
TrackChangesClient.pushRawUpdates @project_id, @doc_id, @updates, (error) =>
throw error if error?
TrackChangesClient.flushDoc @project_id, @doc_id, (error) ->
throw error if error?
done()
return null
after (done) -> MockWebApi.users[this.user_id] = (this.user = {
MockWebApi.getUserInfo.restore() email: "user@sharelatex.com",
db.docHistory.remove {project_id: ObjectId(@project_id)}, () => first_name: "Leo",
db.docHistoryIndex.remove {project_id: ObjectId(@project_id)}, () => last_name: "Lion",
TrackChangesClient.removeS3Doc @project_id, @doc_id, done id: this.user_id
});
sinon.spy(MockWebApi, "getUserInfo");
describe "archiving a doc's updates", -> MockDocStoreApi.docs[this.doc_id] = (this.doc = {
before (done) -> _id: this.doc_id,
TrackChangesClient.pushDocHistory @project_id, @doc_id, (error) -> project_id: this.project_id
throw error if error? });
done() sinon.spy(MockDocStoreApi, "getAllDoc");
return null
it "should have one cached pack", (done) -> this.updates = [];
db.docHistory.count { doc_id: ObjectId(@doc_id), expiresAt:{$exists:true}}, (error, count) -> for (let i = 0, end = 512+10, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
throw error if error? this.updates.push({
count.should.equal 1 op: [{ i: "a", p: 0 }],
done() meta: { ts: this.now + ((i-2048) * this.hours), user_id: this.user_id },
v: (2 * i) + 1
});
this.updates.push({
op: [{ i: "b", p: 0 }],
meta: { ts: this.now + ((i-2048) * this.hours) + (10*this.minutes), user_id: this.user_id },
v: (2 * i) + 2
});
}
TrackChangesApp.ensureRunning(() => {
return TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, this.updates, error => {
if (error != null) { throw error; }
return TrackChangesClient.flushDoc(this.project_id, this.doc_id, function(error) {
if (error != null) { throw error; }
return done();
});
});
});
return null;
});
it "should have one remaining pack after cache is expired", (done) -> after(function(done) {
db.docHistory.remove { MockWebApi.getUserInfo.restore();
doc_id: ObjectId(@doc_id), return db.docHistory.remove({project_id: ObjectId(this.project_id)}, () => {
return db.docHistoryIndex.remove({project_id: ObjectId(this.project_id)}, () => {
return TrackChangesClient.removeS3Doc(this.project_id, this.doc_id, done);
});
});
});
describe("archiving a doc's updates", function() {
before(function(done) {
TrackChangesClient.pushDocHistory(this.project_id, this.doc_id, function(error) {
if (error != null) { throw error; }
return done();
});
return null;
});
it("should have one cached pack", function(done) {
return db.docHistory.count({ doc_id: ObjectId(this.doc_id), expiresAt:{$exists:true}}, function(error, count) {
if (error != null) { throw error; }
count.should.equal(1);
return done();
});
});
it("should have one remaining pack after cache is expired", function(done) {
return db.docHistory.remove({
doc_id: ObjectId(this.doc_id),
expiresAt:{$exists:true} expiresAt:{$exists:true}
}, (err, result) => }, (err, result) => {
throw error if error? if (typeof error !== 'undefined' && error !== null) { throw error; }
db.docHistory.count { doc_id: ObjectId(@doc_id)}, (error, count) -> return db.docHistory.count({ doc_id: ObjectId(this.doc_id)}, function(error, count) {
throw error if error? if (error != null) { throw error; }
count.should.equal 1 count.should.equal(1);
done() return done();
});
});
});
it "should have a docHistoryIndex entry marked as inS3", (done) -> it("should have a docHistoryIndex entry marked as inS3", function(done) {
db.docHistoryIndex.findOne { _id: ObjectId(@doc_id) }, (error, index) -> return db.docHistoryIndex.findOne({ _id: ObjectId(this.doc_id) }, function(error, index) {
throw error if error? if (error != null) { throw error; }
index.packs[0].inS3.should.equal true index.packs[0].inS3.should.equal(true);
done() return done();
});
});
it "should have a docHistoryIndex entry with the last version", (done) -> it("should have a docHistoryIndex entry with the last version", function(done) {
db.docHistoryIndex.findOne { _id: ObjectId(@doc_id) }, (error, index) -> return db.docHistoryIndex.findOne({ _id: ObjectId(this.doc_id) }, function(error, index) {
throw error if error? if (error != null) { throw error; }
index.packs[0].v_end.should.equal 1024 index.packs[0].v_end.should.equal(1024);
done() return done();
});
});
it "should store 1024 doc changes in S3 in one pack", (done) -> return it("should store 1024 doc changes in S3 in one pack", function(done) {
db.docHistoryIndex.findOne { _id: ObjectId(@doc_id) }, (error, index) => return db.docHistoryIndex.findOne({ _id: ObjectId(this.doc_id) }, (error, index) => {
throw error if error? if (error != null) { throw error; }
pack_id = index.packs[0]._id const pack_id = index.packs[0]._id;
TrackChangesClient.getS3Doc @project_id, @doc_id, pack_id, (error, doc) => return TrackChangesClient.getS3Doc(this.project_id, this.doc_id, pack_id, (error, doc) => {
doc.n.should.equal 1024 doc.n.should.equal(1024);
doc.pack.length.should.equal 1024 doc.pack.length.should.equal(1024);
done() return done();
});
});
});
});
describe "unarchiving a doc's updates", -> return describe("unarchiving a doc's updates", function() {
before (done) -> before(function(done) {
TrackChangesClient.pullDocHistory @project_id, @doc_id, (error) -> TrackChangesClient.pullDocHistory(this.project_id, this.doc_id, function(error) {
throw error if error? if (error != null) { throw error; }
done() return done();
return null });
return null;
});
it "should restore both packs", (done) -> return it("should restore both packs", function(done) {
db.docHistory.count { doc_id: ObjectId(@doc_id) }, (error, count) -> return db.docHistory.count({ doc_id: ObjectId(this.doc_id) }, function(error, count) {
throw error if error? if (error != null) { throw error; }
count.should.equal 2 count.should.equal(2);
done() return done();
});
});
});
});
function __guard__(value, transform) {
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
}

View file

@ -1,151 +1,191 @@
sinon = require "sinon" /*
chai = require("chai") * decaffeinate suggestions:
chai.should() * DS102: Remove unnecessary code created because of implicit returns
expect = chai.expect * DS207: Consider shorter variations of null checks
mongojs = require "../../../app/js/mongojs" * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
ObjectId = mongojs.ObjectId */
Settings = require "settings-sharelatex" const sinon = require("sinon");
request = require "request" const chai = require("chai");
rclient = require("redis").createClient(Settings.redis.history) # Only works locally for now chai.should();
const { expect } = chai;
const mongojs = require("../../../app/js/mongojs");
const { ObjectId } = mongojs;
const Settings = require("settings-sharelatex");
const request = require("request");
const rclient = require("redis").createClient(Settings.redis.history); // Only works locally for now
TrackChangesApp = require "./helpers/TrackChangesApp" const TrackChangesApp = require("./helpers/TrackChangesApp");
TrackChangesClient = require "./helpers/TrackChangesClient" const TrackChangesClient = require("./helpers/TrackChangesClient");
MockWebApi = require "./helpers/MockWebApi" const MockWebApi = require("./helpers/MockWebApi");
describe "Flushing updates", -> describe("Flushing updates", function() {
before (done)-> before(done=> TrackChangesApp.ensureRunning(done));
TrackChangesApp.ensureRunning done
describe "flushing a doc's updates", -> describe("flushing a doc's updates", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: true MockWebApi.projects[this.project_id] = {features: {versioning: true}};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushDoc @project_id, @doc_id, (error) -> return TrackChangesClient.flushDoc(this.project_id, this.doc_id, function(error) {
throw error if error? if (error != null) { throw error; }
done() return done();
return null });
});
return null;
});
it "should flush the op into mongo", (done) -> return it("should flush the op into mongo", function(done) {
TrackChangesClient.getCompressedUpdates @doc_id, (error, updates) -> TrackChangesClient.getCompressedUpdates(this.doc_id, function(error, updates) {
expect(updates[0].pack[0].op).to.deep.equal [{ expect(updates[0].pack[0].op).to.deep.equal([{
p: 3, i: "f" p: 3, i: "f"
}] }]);
done() return done();
return null });
return null;
});
});
describe "flushing a project's updates", -> return describe("flushing a project's updates", function() {
describe "with versioning enabled", -> describe("with versioning enabled", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
@weeks = 7 * 24 * 60 * 60 * 1000 this.weeks = 7 * 24 * 60 * 60 * 1000;
MockWebApi.projects[@project_id] = MockWebApi.projects[this.project_id] = {
features: features: {
versioning: true versioning: true
}
};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "g", p: 2 }] op: [{ i: "g", p: 2 }],
meta: { ts: Date.now() - 2 * @weeks, user_id: @user_id } meta: { ts: Date.now() - (2 * this.weeks), user_id: this.user_id },
v: 2 v: 2
}, { }, {
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushProject @project_id, (error) -> return TrackChangesClient.flushProject(this.project_id, function(error) {
throw error if error? if (error != null) { throw error; }
done() return done();
return null });
});
return null;
});
it "should not mark the updates for deletion", (done) -> it("should not mark the updates for deletion", function(done) {
TrackChangesClient.getCompressedUpdates @doc_id, (error, updates) -> TrackChangesClient.getCompressedUpdates(this.doc_id, function(error, updates) {
expect(updates[0].expiresAt).to.not.exist expect(updates[0].expiresAt).to.not.exist;
done() return done();
return null });
return null;
});
it "should preserve history forever", (done) -> return it("should preserve history forever", function(done) {
TrackChangesClient.getProjectMetaData @project_id, (error, project) -> TrackChangesClient.getProjectMetaData(this.project_id, function(error, project) {
expect(project.preserveHistory).to.equal true expect(project.preserveHistory).to.equal(true);
done() return done();
return null });
return null;
});
});
describe "without versioning enabled", -> describe("without versioning enabled", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
@weeks = 7 * 24 * 60 * 60 * 1000 this.weeks = 7 * 24 * 60 * 60 * 1000;
MockWebApi.projects[@project_id] = MockWebApi.projects[this.project_id] = {
features: features: {
versioning: false versioning: false
}
};
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "g", p: 2 }] op: [{ i: "g", p: 2 }],
meta: { ts: Date.now() - 2 * @weeks, user_id: @user_id } meta: { ts: Date.now() - (2 * this.weeks), user_id: this.user_id },
v: 2 v: 2
}, { }, {
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushProject @project_id, (error) -> return TrackChangesClient.flushProject(this.project_id, function(error) {
throw error if error? if (error != null) { throw error; }
done() return done();
return null });
});
return null;
});
it "should mark the updates for deletion", (done) -> return it("should mark the updates for deletion", function(done) {
TrackChangesClient.getCompressedUpdates @doc_id, (error, updates) -> TrackChangesClient.getCompressedUpdates(this.doc_id, function(error, updates) {
expect(updates[0].expiresAt).to.exist expect(updates[0].expiresAt).to.exist;
done() return done();
return null });
return null;
});
});
describe "without versioning enabled but with preserveHistory set to true", -> return describe("without versioning enabled but with preserveHistory set to true", function() {
before (done) -> before(function(done) {
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
@weeks = 7 * 24 * 60 * 60 * 1000 this.weeks = 7 * 24 * 60 * 60 * 1000;
MockWebApi.projects[@project_id] = MockWebApi.projects[this.project_id] = {
features: features: {
versioning: false versioning: false
}
};
TrackChangesClient.setPreserveHistoryForProject @project_id, (error) => TrackChangesClient.setPreserveHistoryForProject(this.project_id, error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.pushRawUpdates @project_id, @doc_id, [{ return TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, [{
op: [{ i: "g", p: 2 }] op: [{ i: "g", p: 2 }],
meta: { ts: Date.now() - 2 * @weeks, user_id: @user_id } meta: { ts: Date.now() - (2 * this.weeks), user_id: this.user_id },
v: 2 v: 2
}, { }, {
op: [{ i: "f", p: 3 }] op: [{ i: "f", p: 3 }],
meta: { ts: Date.now(), user_id: @user_id } meta: { ts: Date.now(), user_id: this.user_id },
v: 3 v: 3
}], (error) => }], error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.flushProject @project_id, (error) -> return TrackChangesClient.flushProject(this.project_id, function(error) {
throw error if error? if (error != null) { throw error; }
done() return done();
return null });
});
});
return null;
});
it "should not mark the updates for deletion", (done) -> return it("should not mark the updates for deletion", function(done) {
TrackChangesClient.getCompressedUpdates @doc_id, (error, updates) -> TrackChangesClient.getCompressedUpdates(this.doc_id, function(error, updates) {
expect(updates[0].expiresAt).to.not.exist expect(updates[0].expiresAt).to.not.exist;
done() return done();
return null });
return null;
});
});
});
});

View file

@ -1,84 +1,100 @@
sinon = require "sinon" /*
chai = require("chai") * decaffeinate suggestions:
chai.should() * DS102: Remove unnecessary code created because of implicit returns
expect = chai.expect * DS207: Consider shorter variations of null checks
mongojs = require "../../../app/js/mongojs" * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
db = mongojs.db */
ObjectId = mongojs.ObjectId const sinon = require("sinon");
Settings = require "settings-sharelatex" const chai = require("chai");
chai.should();
const { expect } = chai;
const mongojs = require("../../../app/js/mongojs");
const { db } = mongojs;
const { ObjectId } = mongojs;
const Settings = require("settings-sharelatex");
TrackChangesApp = require "./helpers/TrackChangesApp" const TrackChangesApp = require("./helpers/TrackChangesApp");
TrackChangesClient = require "./helpers/TrackChangesClient" const TrackChangesClient = require("./helpers/TrackChangesClient");
MockDocUpdaterApi = require "./helpers/MockDocUpdaterApi" const MockDocUpdaterApi = require("./helpers/MockDocUpdaterApi");
MockWebApi = require "./helpers/MockWebApi" const MockWebApi = require("./helpers/MockWebApi");
describe "Getting a diff", -> describe("Getting a diff", function() {
beforeEach (done) -> beforeEach(function(done) {
sinon.spy MockDocUpdaterApi, "getDoc" sinon.spy(MockDocUpdaterApi, "getDoc");
@now = Date.now() this.now = Date.now();
@from = @now - 100000000 this.from = this.now - 100000000;
@to = @now this.to = this.now;
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: true MockWebApi.projects[this.project_id] = {features: {versioning: true}};
MockWebApi.users[@user_id] = @user = MockWebApi.users[this.user_id] = (this.user = {
email: "user@sharelatex.com" email: "user@sharelatex.com",
first_name: "Leo" first_name: "Leo",
last_name: "Lion" last_name: "Lion",
id: @user_id id: this.user_id
sinon.spy MockWebApi, "getUserInfo" });
sinon.spy(MockWebApi, "getUserInfo");
twoMinutes = 2 * 60 * 1000 const twoMinutes = 2 * 60 * 1000;
@updates = [{ this.updates = [{
op: [{ i: "one ", p: 0 }] op: [{ i: "one ", p: 0 }],
meta: { ts: @from - twoMinutes, user_id: @user_id } meta: { ts: this.from - twoMinutes, user_id: this.user_id },
v: 3 v: 3
}, { }, {
op: [{ i: "two ", p: 4 }] op: [{ i: "two ", p: 4 }],
meta: { ts: @from + twoMinutes, user_id: @user_id } meta: { ts: this.from + twoMinutes, user_id: this.user_id },
v: @fromVersion = 4 v: (this.fromVersion = 4)
}, { }, {
op: [{ i: "three ", p: 8 }] op: [{ i: "three ", p: 8 }],
meta: { ts: @to - twoMinutes, user_id: @user_id } meta: { ts: this.to - twoMinutes, user_id: this.user_id },
v: @toVersion = 5 v: (this.toVersion = 5)
}, { }, {
op: [{ i: "four", p: 14 }] op: [{ i: "four", p: 14 }],
meta: { ts: @to + twoMinutes, user_id: @user_id } meta: { ts: this.to + twoMinutes, user_id: this.user_id },
v: 6 v: 6
}] }];
@lines = ["one two three four"] this.lines = ["one two three four"];
@expected_diff = [ this.expected_diff = [
{ u: "one " } { u: "one " },
{ i: "two three ", meta: { start_ts: @from + twoMinutes, end_ts: @to - twoMinutes, user: @user } } { i: "two three ", meta: { start_ts: this.from + twoMinutes, end_ts: this.to - twoMinutes, user: this.user } }
] ];
MockDocUpdaterApi.docs[@doc_id] = MockDocUpdaterApi.docs[this.doc_id] = {
lines: @lines lines: this.lines,
version: 7 version: 7
TrackChangesApp.ensureRunning => };
TrackChangesClient.pushRawUpdates @project_id, @doc_id, @updates, (error) => TrackChangesApp.ensureRunning(() => {
throw error if error? return TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, this.updates, error => {
TrackChangesClient.getDiff @project_id, @doc_id, @fromVersion, @toVersion, (error, diff) => if (error != null) { throw error; }
throw error if error? return TrackChangesClient.getDiff(this.project_id, this.doc_id, this.fromVersion, this.toVersion, (error, diff) => {
@diff = diff.diff if (error != null) { throw error; }
done() this.diff = diff.diff;
return null return done();
});
});
});
return null;
});
afterEach () -> afterEach(function() {
MockDocUpdaterApi.getDoc.restore() MockDocUpdaterApi.getDoc.restore();
MockWebApi.getUserInfo.restore() MockWebApi.getUserInfo.restore();
return null return null;
});
it "should return the diff", -> it("should return the diff", function() {
expect(@diff).to.deep.equal @expected_diff return expect(this.diff).to.deep.equal(this.expected_diff);
});
it "should get the doc from the doc updater", -> return it("should get the doc from the doc updater", function() {
MockDocUpdaterApi.getDoc MockDocUpdaterApi.getDoc
.calledWith(@project_id, @doc_id) .calledWith(this.project_id, this.doc_id)
.should.equal true .should.equal(true);
return null return null;
});
});

View file

@ -1,126 +1,157 @@
sinon = require "sinon" /*
chai = require("chai") * decaffeinate suggestions:
chai.should() * DS102: Remove unnecessary code created because of implicit returns
expect = chai.expect * DS207: Consider shorter variations of null checks
mongojs = require "../../../app/js/mongojs" * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
db = mongojs.db */
ObjectId = mongojs.ObjectId const sinon = require("sinon");
Settings = require "settings-sharelatex" const chai = require("chai");
chai.should();
const { expect } = chai;
const mongojs = require("../../../app/js/mongojs");
const { db } = mongojs;
const { ObjectId } = mongojs;
const Settings = require("settings-sharelatex");
TrackChangesApp = require "./helpers/TrackChangesApp" const TrackChangesApp = require("./helpers/TrackChangesApp");
TrackChangesClient = require "./helpers/TrackChangesClient" const TrackChangesClient = require("./helpers/TrackChangesClient");
MockWebApi = require "./helpers/MockWebApi" const MockWebApi = require("./helpers/MockWebApi");
describe "Getting updates", -> describe("Getting updates", function() {
before (done) -> before(function(done) {
@now = Date.now() this.now = Date.now();
@to = @now this.to = this.now;
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
@deleted_user_id = 'deleted_user' this.deleted_user_id = 'deleted_user';
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
@minutes = 60 * 1000 this.minutes = 60 * 1000;
@hours = 60 * @minutes this.hours = 60 * this.minutes;
MockWebApi.projects[@project_id] = MockWebApi.projects[this.project_id] = {
features: features: {
versioning: true versioning: true
MockWebApi.users[@user_id] = @user =
email: "user@sharelatex.com"
first_name: "Leo"
last_name: "Lion"
id: @user_id
sinon.spy MockWebApi, "getUserInfo"
@updates = []
for i in [0..9]
@updates.push {
op: [{ i: "a", p: 0 }]
meta: { ts: @now - (9 - i) * @hours - 2 * @minutes, user_id: @user_id }
v: 2 * i + 1
} }
@updates.push { };
op: [{ i: "b", p: 0 }]
meta: { ts: @now - (9 - i) * @hours, user_id: @user_id }
v: 2 * i + 2
}
@updates[0].meta.user_id = @deleted_user_id
TrackChangesApp.ensureRunning => MockWebApi.users[this.user_id] = (this.user = {
TrackChangesClient.pushRawUpdates @project_id, @doc_id, @updates, (error) => email: "user@sharelatex.com",
throw error if error? first_name: "Leo",
done() last_name: "Lion",
return null id: this.user_id
});
sinon.spy(MockWebApi, "getUserInfo");
after: () -> this.updates = [];
MockWebApi.getUserInfo.restore() for (let i = 0; i <= 9; i++) {
return null this.updates.push({
op: [{ i: "a", p: 0 }],
meta: { ts: this.now - ((9 - i) * this.hours) - (2 * this.minutes), user_id: this.user_id },
v: (2 * i) + 1
});
this.updates.push({
op: [{ i: "b", p: 0 }],
meta: { ts: this.now - ((9 - i) * this.hours), user_id: this.user_id },
v: (2 * i) + 2
});
}
this.updates[0].meta.user_id = this.deleted_user_id;
describe "getting updates up to the limit", -> TrackChangesApp.ensureRunning(() => {
before (done) -> return TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, this.updates, error => {
TrackChangesClient.getUpdates @project_id, { before: @to + 1, min_count: 3 }, (error, body) => if (error != null) { throw error; }
throw error if error? return done();
@updates = body.updates });
done() });
return null return null;
});
it "should fetch the user details from the web api", -> ({
MockWebApi.getUserInfo after() {
.calledWith(@user_id) MockWebApi.getUserInfo.restore();
.should.equal true return null;
}
});
it "should return at least the min_count number of summarized updates", -> describe("getting updates up to the limit", function() {
docs1 = {} before(function(done) {
docs1[@doc_id] = toV: 20, fromV: 19 TrackChangesClient.getUpdates(this.project_id, { before: this.to + 1, min_count: 3 }, (error, body) => {
docs2 = {} if (error != null) { throw error; }
docs2[@doc_id] = toV: 18, fromV: 17 this.updates = body.updates;
docs3 = {} return done();
docs3[@doc_id] = toV: 16, fromV: 15 });
expect(@updates.slice(0,3)).to.deep.equal [{ return null;
docs: docs1 });
meta:
start_ts: @to - 2 * @minutes it("should fetch the user details from the web api", function() {
end_ts: @to return MockWebApi.getUserInfo
users: [@user] .calledWith(this.user_id)
.should.equal(true);
});
return it("should return at least the min_count number of summarized updates", function() {
const docs1 = {};
docs1[this.doc_id] = {toV: 20, fromV: 19};
const docs2 = {};
docs2[this.doc_id] = {toV: 18, fromV: 17};
const docs3 = {};
docs3[this.doc_id] = {toV: 16, fromV: 15};
return expect(this.updates.slice(0,3)).to.deep.equal([{
docs: docs1,
meta: {
start_ts: this.to - (2 * this.minutes),
end_ts: this.to,
users: [this.user]
}
}, { }, {
docs: docs2 docs: docs2,
meta: meta: {
start_ts: @to - 1 * @hours - 2 * @minutes start_ts: this.to - (1 * this.hours) - (2 * this.minutes),
end_ts: @to - 1 * @hours end_ts: this.to - (1 * this.hours),
users: [@user] users: [this.user]
}
}, { }, {
docs: docs3 docs: docs3,
meta: meta: {
start_ts: @to - 2 * @hours - 2 * @minutes start_ts: this.to - (2 * this.hours) - (2 * this.minutes),
end_ts: @to - 2 * @hours end_ts: this.to - (2 * this.hours),
users: [@user] users: [this.user]
}] }
}]);
});
});
describe "getting updates beyond the end of the database", -> return describe("getting updates beyond the end of the database", function() {
before (done) -> before(function(done) {
TrackChangesClient.getUpdates @project_id, { before: @to - 8 * @hours + 1, min_count: 30 }, (error, body) => TrackChangesClient.getUpdates(this.project_id, { before: (this.to - (8 * this.hours)) + 1, min_count: 30 }, (error, body) => {
throw error if error? if (error != null) { throw error; }
@updates = body.updates this.updates = body.updates;
done() return done();
return null });
return null;
});
it "should return as many updates as it can", -> return it("should return as many updates as it can", function() {
docs1 = {} const docs1 = {};
docs1[@doc_id] = toV: 4, fromV: 3 docs1[this.doc_id] = {toV: 4, fromV: 3};
docs2 = {} const docs2 = {};
docs2[@doc_id] = toV: 2, fromV: 1 docs2[this.doc_id] = {toV: 2, fromV: 1};
expect(@updates).to.deep.equal [{ return expect(this.updates).to.deep.equal([{
docs: docs1 docs: docs1,
meta: meta: {
start_ts: @to - 8 * @hours - 2 * @minutes start_ts: this.to - (8 * this.hours) - (2 * this.minutes),
end_ts: @to - 8 * @hours end_ts: this.to - (8 * this.hours),
users: [@user] users: [this.user]
}
}, { }, {
docs: docs2 docs: docs2,
meta: meta: {
start_ts: @to - 9 * @hours - 2 * @minutes start_ts: this.to - (9 * this.hours) - (2 * this.minutes),
end_ts: @to - 9 * @hours end_ts: this.to - (9 * this.hours),
users: [@user, null] users: [this.user, null]
}] }
}]);
});
});
});

View file

@ -1,39 +1,54 @@
sinon = require "sinon" /*
chai = require("chai") * decaffeinate suggestions:
chai.should() * DS102: Remove unnecessary code created because of implicit returns
expect = chai.expect * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
mongojs = require "../../../app/js/mongojs" */
ObjectId = mongojs.ObjectId const sinon = require("sinon");
Settings = require "settings-sharelatex" const chai = require("chai");
LockManager = require "../../../app/js/LockManager" chai.should();
rclient = require("redis").createClient(Settings.redis.history) # Only works locally for now const { expect } = chai;
TrackChangesApp = require "./helpers/TrackChangesApp" const mongojs = require("../../../app/js/mongojs");
const { ObjectId } = mongojs;
const Settings = require("settings-sharelatex");
const LockManager = require("../../../app/js/LockManager");
const rclient = require("redis").createClient(Settings.redis.history); // Only works locally for now
const TrackChangesApp = require("./helpers/TrackChangesApp");
describe "Locking document", -> describe("Locking document", function() {
before (done)-> before(function(done){
TrackChangesApp.ensureRunning done TrackChangesApp.ensureRunning(done);
return null return null;
});
describe "when the lock has expired in redis", -> return describe("when the lock has expired in redis", function() {
before (done) -> before(function(done) {
LockManager.LOCK_TTL = 1 # second LockManager.LOCK_TTL = 1; // second
LockManager.runWithLock "doc123", (releaseA) => LockManager.runWithLock("doc123", releaseA => {
# we create a lock A and allow it to expire in redis // we create a lock A and allow it to expire in redis
setTimeout () -> return setTimeout(() =>
# now we create a new lock B and try to release A // now we create a new lock B and try to release A
LockManager.runWithLock "doc123", (releaseB) => LockManager.runWithLock("doc123", releaseB => {
releaseA() # try to release lock A to see if it wipes out lock B return releaseA();
, (error) -> } // try to release lock A to see if it wipes out lock B
# we never release lock B so nothing should happen here , function(error) {})
, 1500 # enough time to wait until the lock has expired
, (error) -> // we never release lock B so nothing should happen here
# we get here after trying to release lock A , 1500);
} // enough time to wait until the lock has expired
, error =>
// we get here after trying to release lock A
done() done()
return null );
return null;
});
it "the new lock should not be removed by the expired locker", (done) -> return it("the new lock should not be removed by the expired locker", function(done) {
LockManager.checkLock "doc123", (err, isFree) -> LockManager.checkLock("doc123", function(err, isFree) {
expect(isFree).to.equal false expect(isFree).to.equal(false);
done() return done();
return null });
return null;
});
});
});

View file

@ -1,74 +1,89 @@
sinon = require "sinon" /*
chai = require("chai") * decaffeinate suggestions:
chai.should() * DS102: Remove unnecessary code created because of implicit returns
expect = chai.expect * DS207: Consider shorter variations of null checks
mongojs = require "../../../app/js/mongojs" * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
db = mongojs.db */
ObjectId = mongojs.ObjectId const sinon = require("sinon");
Settings = require "settings-sharelatex" const chai = require("chai");
chai.should();
const { expect } = chai;
const mongojs = require("../../../app/js/mongojs");
const { db } = mongojs;
const { ObjectId } = mongojs;
const Settings = require("settings-sharelatex");
TrackChangesApp = require "./helpers/TrackChangesApp" const TrackChangesApp = require("./helpers/TrackChangesApp");
TrackChangesClient = require "./helpers/TrackChangesClient" const TrackChangesClient = require("./helpers/TrackChangesClient");
MockDocUpdaterApi = require "./helpers/MockDocUpdaterApi" const MockDocUpdaterApi = require("./helpers/MockDocUpdaterApi");
MockWebApi = require "./helpers/MockWebApi" const MockWebApi = require("./helpers/MockWebApi");
describe "Restoring a version", -> describe("Restoring a version", function() {
before (done) -> before(function(done) {
sinon.spy MockDocUpdaterApi, "setDoc" sinon.spy(MockDocUpdaterApi, "setDoc");
@now = Date.now() this.now = Date.now();
@user_id = ObjectId().toString() this.user_id = ObjectId().toString();
@doc_id = ObjectId().toString() this.doc_id = ObjectId().toString();
@project_id = ObjectId().toString() this.project_id = ObjectId().toString();
MockWebApi.projects[@project_id] = features: versioning: true MockWebApi.projects[this.project_id] = {features: {versioning: true}};
minutes = 60 * 1000 const minutes = 60 * 1000;
@updates = [{ this.updates = [{
op: [{ i: "one ", p: 0 }] op: [{ i: "one ", p: 0 }],
meta: { ts: @now - 6 * minutes, user_id: @user_id } meta: { ts: this.now - (6 * minutes), user_id: this.user_id },
v: 3 v: 3
}, { }, {
op: [{ i: "two ", p: 4 }] op: [{ i: "two ", p: 4 }],
meta: { ts: @now - 4 * minutes, user_id: @user_id } meta: { ts: this.now - (4 * minutes), user_id: this.user_id },
v: 4 v: 4
}, { }, {
op: [{ i: "three ", p: 8 }] op: [{ i: "three ", p: 8 }],
meta: { ts: @now - 2 * minutes, user_id: @user_id } meta: { ts: this.now - (2 * minutes), user_id: this.user_id },
v: 5 v: 5
}, { }, {
op: [{ i: "four", p: 14 }] op: [{ i: "four", p: 14 }],
meta: { ts: @now, user_id: @user_id } meta: { ts: this.now, user_id: this.user_id },
v: 6 v: 6
}] }];
@lines = ["one two three four"] this.lines = ["one two three four"];
@restored_lines = ["one two "] this.restored_lines = ["one two "];
@beforeVersion = 5 this.beforeVersion = 5;
MockWebApi.users[@user_id] = @user = MockWebApi.users[this.user_id] = (this.user = {
email: "user@sharelatex.com" email: "user@sharelatex.com",
first_name: "Leo" first_name: "Leo",
last_name: "Lion" last_name: "Lion",
id: @user_id id: this.user_id
});
MockDocUpdaterApi.docs[@doc_id] = MockDocUpdaterApi.docs[this.doc_id] = {
lines: @lines lines: this.lines,
version: 7 version: 7
};
TrackChangesApp.ensureRunning => TrackChangesApp.ensureRunning(() => {
TrackChangesClient.pushRawUpdates @project_id, @doc_id, @updates, (error) => return TrackChangesClient.pushRawUpdates(this.project_id, this.doc_id, this.updates, error => {
throw error if error? if (error != null) { throw error; }
TrackChangesClient.restoreDoc @project_id, @doc_id, @beforeVersion, @user_id, (error) => return TrackChangesClient.restoreDoc(this.project_id, this.doc_id, this.beforeVersion, this.user_id, error => {
throw error if error? if (error != null) { throw error; }
done() return done();
return null });
});
});
return null;
});
after () -> after(function() {
MockDocUpdaterApi.setDoc.restore() MockDocUpdaterApi.setDoc.restore();
return null return null;
});
it "should set the doc in the doc updater", -> return it("should set the doc in the doc updater", function() {
MockDocUpdaterApi.setDoc MockDocUpdaterApi.setDoc
.calledWith(@project_id, @doc_id, @restored_lines, @user_id, true) .calledWith(this.project_id, this.doc_id, this.restored_lines, this.user_id, true)
.should.equal true .should.equal(true);
return null return null;
});
});

View file

@ -1,27 +1,43 @@
express = require("express") /*
app = express() * 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
*/
let MockDocUpdaterApi;
const express = require("express");
const app = express();
module.exports = MockDocUpdaterApi = module.exports = (MockDocUpdaterApi = {
docs: {} docs: {},
getAllDoc: (project_id, callback = (error) ->) -> getAllDoc(project_id, callback) {
callback null, @docs if (callback == null) { callback = function(error) {}; }
return callback(null, this.docs);
},
run: () -> run() {
app.get "/project/:project_id/doc", (req, res, next) => app.get("/project/:project_id/doc", (req, res, next) => {
@getAllDoc req.params.project_id, (error, docs) -> return this.getAllDoc(req.params.project_id, function(error, docs) {
if error? if (error != null) {
res.send 500 res.send(500);
if !docs? }
res.send 404 if ((docs == null)) {
else return res.send(404);
res.send JSON.stringify docs } else {
return res.send(JSON.stringify(docs));
}
});
});
app.listen 3016, (error) -> return app.listen(3016, function(error) {
throw error if error? if (error != null) { throw error; }
.on "error", (error) -> }).on("error", function(error) {
console.error "error starting MockDocStoreApi:", error.message console.error("error starting MockDocStoreApi:", error.message);
process.exit(1) return process.exit(1);
});
}
});
MockDocUpdaterApi.run() MockDocUpdaterApi.run();

View file

@ -1,40 +1,61 @@
express = require("express") /*
app = express() * 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
*/
let MockDocUpdaterApi;
const express = require("express");
const app = express();
module.exports = MockDocUpdaterApi = module.exports = (MockDocUpdaterApi = {
docs: {} docs: {},
getDoc: (project_id, doc_id, callback = (error) ->) -> getDoc(project_id, doc_id, callback) {
callback null, @docs[doc_id] if (callback == null) { callback = function(error) {}; }
return callback(null, this.docs[doc_id]);
},
setDoc: (project_id, doc_id, lines, user_id, undoing, callback = (error) ->) -> setDoc(project_id, doc_id, lines, user_id, undoing, callback) {
@docs[doc_id] ||= {} if (callback == null) { callback = function(error) {}; }
@docs[doc_id].lines = lines if (!this.docs[doc_id]) { this.docs[doc_id] = {}; }
callback() this.docs[doc_id].lines = lines;
return callback();
},
run: () -> run() {
app.get "/project/:project_id/doc/:doc_id", (req, res, next) => app.get("/project/:project_id/doc/:doc_id", (req, res, next) => {
@getDoc req.params.project_id, req.params.doc_id, (error, doc) -> return this.getDoc(req.params.project_id, req.params.doc_id, function(error, doc) {
if error? if (error != null) {
res.send 500 res.send(500);
if !doc? }
res.send 404 if ((doc == null)) {
else return res.send(404);
res.send JSON.stringify doc } else {
return res.send(JSON.stringify(doc));
}
});
});
app.post "/project/:project_id/doc/:doc_id", express.bodyParser(), (req, res, next) => app.post("/project/:project_id/doc/:doc_id", express.bodyParser(), (req, res, next) => {
@setDoc req.params.project_id, req.params.doc_id, req.body.lines, req.body.user_id, req.body.undoing, (errr, doc) -> return this.setDoc(req.params.project_id, req.params.doc_id, req.body.lines, req.body.user_id, req.body.undoing, function(errr, doc) {
if error? if (typeof error !== 'undefined' && error !== null) {
res.send 500 return res.send(500);
else } else {
res.send 204 return res.send(204);
}
});
});
app.listen 3003, (error) -> return app.listen(3003, function(error) {
throw error if error? if (error != null) { throw error; }
.on "error", (error) -> }).on("error", function(error) {
console.error "error starting MockDocUpdaterApi:", error.message console.error("error starting MockDocUpdaterApi:", error.message);
process.exit(1) return process.exit(1);
});
}
});
MockDocUpdaterApi.run() MockDocUpdaterApi.run();

View file

@ -1,41 +1,63 @@
express = require("express") /*
app = express() * 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
*/
let MockWebApi;
const express = require("express");
const app = express();
module.exports = MockWebApi = module.exports = (MockWebApi = {
users: {} users: {},
projects: {} projects: {},
getUserInfo: (user_id, callback = (error) ->) -> getUserInfo(user_id, callback) {
callback null, @users[user_id] or null if (callback == null) { callback = function(error) {}; }
return callback(null, this.users[user_id] || null);
},
getProjectDetails: (project_id, callback = (error, project) ->) -> getProjectDetails(project_id, callback) {
callback null, @projects[project_id] if (callback == null) { callback = function(error, project) {}; }
return callback(null, this.projects[project_id]);
},
run: () -> run() {
app.get "/user/:user_id/personal_info", (req, res, next) => app.get("/user/:user_id/personal_info", (req, res, next) => {
@getUserInfo req.params.user_id, (error, user) -> return this.getUserInfo(req.params.user_id, function(error, user) {
if error? if (error != null) {
res.send 500 res.send(500);
if !user? }
res.send 404 if ((user == null)) {
else return res.send(404);
res.send JSON.stringify user } else {
return res.send(JSON.stringify(user));
}
});
});
app.get "/project/:project_id/details", (req, res, next) => app.get("/project/:project_id/details", (req, res, next) => {
@getProjectDetails req.params.project_id, (error, project) -> return this.getProjectDetails(req.params.project_id, function(error, project) {
if error? if (error != null) {
res.send 500 res.send(500);
if !project? }
res.send 404 if ((project == null)) {
else return res.send(404);
res.send JSON.stringify project } else {
return res.send(JSON.stringify(project));
}
});
});
app.listen 3000, (error) -> return app.listen(3000, function(error) {
throw error if error? if (error != null) { throw error; }
.on "error", (error) -> }).on("error", function(error) {
console.error "error starting MockWebApiServer:", error.message console.error("error starting MockWebApiServer:", error.message);
process.exit(1) return process.exit(1);
});
}
});
MockWebApi.run() MockWebApi.run();

View file

@ -1,24 +1,46 @@
app = require('../../../../app') /*
require("logger-sharelatex") * decaffeinate suggestions:
logger = require("logger-sharelatex") * DS101: Remove unnecessary use of Array.from
Settings = require("settings-sharelatex") * DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const app = require('../../../../app');
require("logger-sharelatex");
const logger = require("logger-sharelatex");
const Settings = require("settings-sharelatex");
module.exports = module.exports = {
running: false running: false,
initing: false initing: false,
callbacks: [] callbacks: [],
ensureRunning: (callback = (error) ->) -> ensureRunning(callback) {
if @running if (callback == null) { callback = function(error) {}; }
return callback() if (this.running) {
else if @initing return callback();
@callbacks.push callback } else if (this.initing) {
else return this.callbacks.push(callback);
@initing = true } else {
@callbacks.push callback this.initing = true;
app.listen Settings.internal?.trackchanges?.port, "localhost", (error) => this.callbacks.push(callback);
throw error if error? return app.listen(__guard__(Settings.internal != null ? Settings.internal.trackchanges : undefined, x => x.port), "localhost", error => {
@running = true if (error != null) { throw error; }
logger.log("track changes running in dev mode") this.running = true;
logger.log("track changes running in dev mode");
for callback in @callbacks return (() => {
callback() const result = [];
for (callback of Array.from(this.callbacks)) {
result.push(callback());
}
return result;
})();
});
}
}
};
function __guard__(value, transform) {
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
}

View file

@ -1,144 +1,202 @@
async = require 'async' /*
zlib = require 'zlib' * decaffeinate suggestions:
request = require "request" * DS101: Remove unnecessary use of Array.from
Settings = require "settings-sharelatex" * DS102: Remove unnecessary code created because of implicit returns
rclient = require("redis-sharelatex").createClient(Settings.redis.history) # Only works locally for now * DS207: Consider shorter variations of null checks
Keys = Settings.redis.history.key_schema * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
{db, ObjectId} = require "../../../../app/js/mongojs" */
let TrackChangesClient;
const async = require('async');
const zlib = require('zlib');
const request = require("request");
const Settings = require("settings-sharelatex");
const rclient = require("redis-sharelatex").createClient(Settings.redis.history); // Only works locally for now
const Keys = Settings.redis.history.key_schema;
const {db, ObjectId} = require("../../../../app/js/mongojs");
aws = require "aws-sdk" const aws = require("aws-sdk");
s3 = new aws.S3( const s3 = new aws.S3({
accessKeyId: Settings.trackchanges.s3.key accessKeyId: Settings.trackchanges.s3.key,
secretAccessKey: Settings.trackchanges.s3.secret secretAccessKey: Settings.trackchanges.s3.secret,
endpoint: Settings.trackchanges.s3.endpoint endpoint: Settings.trackchanges.s3.endpoint,
s3ForcePathStyle: Settings.trackchanges.s3.pathStyle s3ForcePathStyle: Settings.trackchanges.s3.pathStyle
) });
S3_BUCKET = Settings.trackchanges.stores.doc_history const S3_BUCKET = Settings.trackchanges.stores.doc_history;
module.exports = TrackChangesClient = module.exports = (TrackChangesClient = {
flushAndGetCompressedUpdates: (project_id, doc_id, callback = (error, updates) ->) -> flushAndGetCompressedUpdates(project_id, doc_id, callback) {
TrackChangesClient.flushDoc project_id, doc_id, (error) -> if (callback == null) { callback = function(error, updates) {}; }
return callback(error) if error? return TrackChangesClient.flushDoc(project_id, doc_id, function(error) {
TrackChangesClient.getCompressedUpdates doc_id, callback if (error != null) { return callback(error); }
return TrackChangesClient.getCompressedUpdates(doc_id, callback);
});
},
flushDoc: (project_id, doc_id, callback = (error) ->) -> flushDoc(project_id, doc_id, callback) {
request.post { if (callback == null) { callback = function(error) {}; }
url: "http://localhost:3015/project/#{project_id}/doc/#{doc_id}/flush" return request.post({
}, (error, response, body) => url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/flush`
response.statusCode.should.equal 204 }, (error, response, body) => {
callback(error) response.statusCode.should.equal(204);
return callback(error);
});
},
flushProject: (project_id, callback = (error) ->) -> flushProject(project_id, callback) {
request.post { if (callback == null) { callback = function(error) {}; }
url: "http://localhost:3015/project/#{project_id}/flush" return request.post({
}, (error, response, body) => url: `http://localhost:3015/project/${project_id}/flush`
response.statusCode.should.equal 204 }, (error, response, body) => {
callback(error) response.statusCode.should.equal(204);
return callback(error);
});
},
getCompressedUpdates: (doc_id, callback = (error, updates) ->) -> getCompressedUpdates(doc_id, callback) {
db.docHistory if (callback == null) { callback = function(error, updates) {}; }
.find(doc_id: ObjectId(doc_id)) return db.docHistory
.sort("meta.end_ts": 1) .find({doc_id: ObjectId(doc_id)})
.toArray callback .sort({"meta.end_ts": 1})
.toArray(callback);
},
getProjectMetaData: (project_id, callback = (error, updates) ->) -> getProjectMetaData(project_id, callback) {
db.projectHistoryMetaData if (callback == null) { callback = function(error, updates) {}; }
.find { return db.projectHistoryMetaData
.find({
project_id: ObjectId(project_id) project_id: ObjectId(project_id)
}, },
(error, projects) -> (error, projects) => callback(error, projects[0]));
callback error, projects[0] },
setPreserveHistoryForProject: (project_id, callback = (error) ->) -> setPreserveHistoryForProject(project_id, callback) {
db.projectHistoryMetaData.update { if (callback == null) { callback = function(error) {}; }
return db.projectHistoryMetaData.update({
project_id: ObjectId(project_id) project_id: ObjectId(project_id)
}, { }, {
$set: { preserveHistory: true } $set: { preserveHistory: true }
}, { }, {
upsert: true upsert: true
}, callback }, callback);
},
pushRawUpdates: (project_id, doc_id, updates, callback = (error) ->) -> pushRawUpdates(project_id, doc_id, updates, callback) {
rclient.sadd Keys.docsWithHistoryOps({project_id}), doc_id, (error) -> if (callback == null) { callback = function(error) {}; }
return callback(error) if error? return rclient.sadd(Keys.docsWithHistoryOps({project_id}), doc_id, function(error) {
rclient.rpush Keys.uncompressedHistoryOps({doc_id}), (JSON.stringify(u) for u in updates)..., callback if (error != null) { return callback(error); }
return rclient.rpush(Keys.uncompressedHistoryOps({doc_id}), ...Array.from(((Array.from(updates).map((u) => JSON.stringify(u))))), callback);
});
},
getDiff: (project_id, doc_id, from, to, callback = (error, diff) ->) -> getDiff(project_id, doc_id, from, to, callback) {
request.get { if (callback == null) { callback = function(error, diff) {}; }
url: "http://localhost:3015/project/#{project_id}/doc/#{doc_id}/diff?from=#{from}&to=#{to}" return request.get({
}, (error, response, body) => url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/diff?from=${from}&to=${to}`
response.statusCode.should.equal 200 }, (error, response, body) => {
callback null, JSON.parse(body) response.statusCode.should.equal(200);
return callback(null, JSON.parse(body));
});
},
getUpdates: (project_id, options, callback = (error, body) ->) -> getUpdates(project_id, options, callback) {
request.get { if (callback == null) { callback = function(error, body) {}; }
url: "http://localhost:3015/project/#{project_id}/updates?before=#{options.before}&min_count=#{options.min_count}" return request.get({
}, (error, response, body) => url: `http://localhost:3015/project/${project_id}/updates?before=${options.before}&min_count=${options.min_count}`
response.statusCode.should.equal 200 }, (error, response, body) => {
callback null, JSON.parse(body) response.statusCode.should.equal(200);
return callback(null, JSON.parse(body));
});
},
restoreDoc: (project_id, doc_id, version, user_id, callback = (error) ->) -> restoreDoc(project_id, doc_id, version, user_id, callback) {
request.post { if (callback == null) { callback = function(error) {}; }
url: "http://localhost:3015/project/#{project_id}/doc/#{doc_id}/version/#{version}/restore" return request.post({
headers: url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/version/${version}/restore`,
headers: {
"X-User-Id": user_id "X-User-Id": user_id
}, (error, response, body) => }
response.statusCode.should.equal 204 }, (error, response, body) => {
callback null response.statusCode.should.equal(204);
return callback(null);
});
},
pushDocHistory: (project_id, doc_id, callback = (error) ->) -> pushDocHistory(project_id, doc_id, callback) {
request.post { if (callback == null) { callback = function(error) {}; }
url: "http://localhost:3015/project/#{project_id}/doc/#{doc_id}/push" return request.post({
}, (error, response, body) => url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/push`
response.statusCode.should.equal 204 }, (error, response, body) => {
callback(error) response.statusCode.should.equal(204);
return callback(error);
});
},
pullDocHistory: (project_id, doc_id, callback = (error) ->) -> pullDocHistory(project_id, doc_id, callback) {
request.post { if (callback == null) { callback = function(error) {}; }
url: "http://localhost:3015/project/#{project_id}/doc/#{doc_id}/pull" return request.post({
}, (error, response, body) => url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/pull`
response.statusCode.should.equal 204 }, (error, response, body) => {
callback(error) response.statusCode.should.equal(204);
return callback(error);
});
},
waitForS3: (done, retries=42) -> waitForS3(done, retries) {
if !Settings.trackchanges.s3.endpoint if (retries == null) { retries = 42; }
return done() if (!Settings.trackchanges.s3.endpoint) {
return done();
}
request.get "#{Settings.trackchanges.s3.endpoint}/", (err, res) -> return request.get(`${Settings.trackchanges.s3.endpoint}/`, function(err, res) {
if res && res.statusCode < 500 if (res && (res.statusCode < 500)) {
return done() return done();
}
if retries == 0 if (retries === 0) {
return done(err or new Error("s3 returned #{res.statusCode}")) return done(err || new Error(`s3 returned ${res.statusCode}`));
}
setTimeout () -> return setTimeout(() => TrackChangesClient.waitForS3(done, --retries)
TrackChangesClient.waitForS3(done, --retries) , 1000);
, 1000 });
},
getS3Doc: (project_id, doc_id, pack_id, callback = (error, body) ->) -> getS3Doc(project_id, doc_id, pack_id, callback) {
params = if (callback == null) { callback = function(error, body) {}; }
Bucket: S3_BUCKET const params = {
Key: "#{project_id}/changes-#{doc_id}/pack-#{pack_id}" Bucket: S3_BUCKET,
Key: `${project_id}/changes-${doc_id}/pack-${pack_id}`
};
s3.getObject params, (error, data) -> return s3.getObject(params, function(error, data) {
return callback(error) if error? if (error != null) { return callback(error); }
body = data.Body const body = data.Body;
return callback(new Error("empty response from s3")) if not body? if ((body == null)) { return callback(new Error("empty response from s3")); }
zlib.gunzip body, (err, result) -> return zlib.gunzip(body, function(err, result) {
return callback(err) if err? if (err != null) { return callback(err); }
callback(null, JSON.parse(result.toString())) return callback(null, JSON.parse(result.toString()));
});
});
},
removeS3Doc: (project_id, doc_id, callback = (error, res, body) ->) -> removeS3Doc(project_id, doc_id, callback) {
params = if (callback == null) { callback = function(error, res, body) {}; }
Bucket: S3_BUCKET let params = {
Prefix: "#{project_id}/changes-#{doc_id}" Bucket: S3_BUCKET,
Prefix: `${project_id}/changes-${doc_id}`
};
s3.listObjects params, (error, data) -> return s3.listObjects(params, function(error, data) {
return callback(error) if error? if (error != null) { return callback(error); }
params = params = {
Bucket: S3_BUCKET Bucket: S3_BUCKET,
Delete: Delete: {
Objects: data.Contents.map((s3object) -> {Key: s3object.Key}) Objects: data.Contents.map(s3object => ({Key: s3object.Key}))
}
};
s3.deleteObjects params, callback return s3.deleteObjects(params, callback);
});
}
});