From 8b71d222d42c7dbcacb265737d563ffc39bcc250 Mon Sep 17 00:00:00 2001 From: James Allen Date: Tue, 4 Mar 2014 14:05:17 +0000 Subject: [PATCH] Create DiffManager.getDiff --- .../app/coffee/DiffManager.coffee | 41 +++++-- .../app/coffee/MongoManager.coffee | 12 +- .../DiffManager/DiffManagerTests.coffee | 110 ++++++++++++++++++ .../MongoManager/MongoManagerTests.coffee | 48 +++++--- 4 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 services/track-changes/test/unit/coffee/DiffManager/DiffManagerTests.coffee diff --git a/services/track-changes/app/coffee/DiffManager.coffee b/services/track-changes/app/coffee/DiffManager.coffee index f1186293e8..4c45661aa3 100644 --- a/services/track-changes/app/coffee/DiffManager.coffee +++ b/services/track-changes/app/coffee/DiffManager.coffee @@ -1,8 +1,35 @@ +HistoryManager = require "./HistoryManager" +DocumentUpdaterManager = require "./DocumentUpdaterManager" +MongoManager = require "./MongoManager" +DiffGenerator = require "./DiffGenerator" + module.exports = DiffManager = - getDiff: (doc_id, fromDate, toDate, callback = (error, diff) ->) -> - # Flush diff - # Get doc content and version - # Get updates from Mongo - # Check version matches - # Build diff - # Return diff \ No newline at end of file + getLatestDocAndUpdates: (project_id, doc_id, fromDate, toDate, callback = (error, lines, version, updates) ->) -> + HistoryManager.processUncompressedUpdatesWithLock doc_id, (error) -> + return callback(error) if error? + DocumentUpdaterManager.getDocument project_id, doc_id, (error, lines, version) -> + return callback(error) if error? + MongoManager.getUpdatesBetweenDates doc_id, fromDate, toDate, (error, updates) -> + return callback(error) if error? + callback(null, lines, version, updates) + + getDiff: (project_id, doc_id, fromDate, toDate, callback = (error, diff) ->) -> + DiffManager.getLatestDocAndUpdates project_id, doc_id, fromDate, null, (error, lines, version, updates) -> + return callback(error) if error? + lastUpdate = updates[updates.length - 1] + if lastUpdate? and lastUpdate.v != version + return callback new Error("latest update version, #{lastUpdate.v}, does not match doc version, #{version}") + + + updatesToApply = [] + for update in updates + if update.meta.end_ts <= toDate + updatesToApply.push update + + try + startingContent = DiffGenerator.rewindUpdates lines.join("\n"), updates + diff = DiffGenerator.buildDiff startingContent, updatesToApply + catch e + return callback(e) + + callback(null, diff) \ No newline at end of file diff --git a/services/track-changes/app/coffee/MongoManager.coffee b/services/track-changes/app/coffee/MongoManager.coffee index b194188992..1f884d0b67 100644 --- a/services/track-changes/app/coffee/MongoManager.coffee +++ b/services/track-changes/app/coffee/MongoManager.coffee @@ -40,12 +40,14 @@ module.exports = MongoManager = }, callback getUpdatesBetweenDates:(doc_id, fromDate, toDate, callback = (error, updates) ->) -> + query = + doc_id: ObjectId(doc_id.toString()) + if fromDate? + query["meta.start_ts"] = { $gte: fromDate } + if toDate? + query["meta.end_ts"] = { $lte: toDate } db.docHistory - .find({ - doc_id: ObjectId(doc_id.toString()) - "meta.start_ts" : { $gte: fromDate } - "meta.end_ts" : { $lte: toDate } - }) + .find( query ) .sort( "meta.end_ts": -1 ) .toArray callback diff --git a/services/track-changes/test/unit/coffee/DiffManager/DiffManagerTests.coffee b/services/track-changes/test/unit/coffee/DiffManager/DiffManagerTests.coffee new file mode 100644 index 0000000000..332a73120d --- /dev/null +++ b/services/track-changes/test/unit/coffee/DiffManager/DiffManagerTests.coffee @@ -0,0 +1,110 @@ +sinon = require('sinon') +chai = require('chai') +should = chai.should() +expect = chai.expect +modulePath = "../../../../app/js/DiffManager.js" +SandboxedModule = require('sandboxed-module') + +describe "DiffManager", -> + beforeEach -> + @DiffManager = SandboxedModule.require modulePath, requires: + "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } + "./HistoryManager": @HistoryManager = {} + "./DocumentUpdaterManager": @DocumentUpdaterManager = {} + "./MongoManager": @MongoManager = {} + "./DiffGenerator": @DiffGenerator = {} + @callback = sinon.stub() + @from = new Date() + @to = new Date(Date.now() + 10000) + @project_id = "mock-project-id" + @doc_id = "mock-doc-id" + + describe "getLatestDocAndUpdates", -> + beforeEach -> + @lines = [ "hello", "world" ] + @version = 42 + @updates = [ "mock-update-1", "mock-update-2" ] + + @HistoryManager.processUncompressedUpdatesWithLock = sinon.stub().callsArg(1) + @DocumentUpdaterManager.getDocument = sinon.stub().callsArgWith(2, null, @lines, @version) + @MongoManager.getUpdatesBetweenDates = sinon.stub().callsArgWith(3, null, @updates) + @DiffManager.getLatestDocAndUpdates @project_id, @doc_id, @from, @to, @callback + + it "should ensure the latest updates have been compressed", -> + @HistoryManager.processUncompressedUpdatesWithLock + .calledWith(@doc_id) + .should.equal true + + it "should get the latest version of the doc", -> + @DocumentUpdaterManager.getDocument + .calledWith(@project_id, @doc_id) + .should.equal true + + it "should get the requested updates from Mongo", -> + @MongoManager.getUpdatesBetweenDates + .calledWith(@doc_id, @from, @to) + .should.equal true + + it "should call the callback with the lines, version and updates", -> + @callback.calledWith(null, @lines, @version, @updates).should.equal true + + describe "getDiff", -> + beforeEach -> + @lines = [ "hello", "world" ] + @version = 42 + @updates = [ + { op: "mock-1", v: 41, meta: { end_ts: new Date(@to.getTime() - 10)} } + { op: "mock-2", v: 42, meta: { end_ts: new Date(@to.getTime() + 10)} } + ] + @diffed_updates = @updates.slice(0,1) + @rewound_content = "rewound-content" + @diff = [ u: "mock-diff" ] + + describe "with matching versions", -> + beforeEach -> + @DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(4, null, @lines, @version, @updates) + @DiffGenerator.rewindUpdates = sinon.stub().returns(@rewound_content) + @DiffGenerator.buildDiff = sinon.stub().returns(@diff) + @DiffManager.getDiff @project_id, @doc_id, @from, @to, @callback + + it "should get the latest doc and version with all recent updates", -> + @DiffManager.getLatestDocAndUpdates + .calledWith(@project_id, @doc_id, @from, null) + .should.equal true + + it "should rewind the diff", -> + @DiffGenerator.rewindUpdates + .calledWith(@lines.join("\n"), @updates) + .should.equal true + + it "should generate the diff", -> + @DiffGenerator.buildDiff + .calledWith(@rewound_content, @diffed_updates) + .should.equal true + + it "should call the callback with the diff", -> + @callback.calledWith(null, @diff).should.equal true + + describe "with mismatching versions", -> + beforeEach -> + @version = 42 + @updates = [ { op: "mock-1", v: 39 }, { op: "mock-1", v: 40 } ] + @DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(4, null, @lines, @version, @updates) + @DiffManager.getDiff @project_id, @doc_id, @from, @to, @callback + + it "should call the callback with an error", -> + @callback + .calledWith(new Error("latest update version, 40, does not match doc version, 42")) + .should.equal true + + describe "when the updates are inconsistent", -> + beforeEach -> + @DiffManager.getLatestDocAndUpdates = sinon.stub().callsArgWith(4, null, @lines, @version, @updates) + @DiffGenerator.rewindUpdates = sinon.stub().throws(@error = new Error("inconsistent!")) + @DiffManager.getDiff @project_id, @doc_id, @from, @to, @callback + + it "should call the callback with an error", -> + @callback + .calledWith(@error) + .should.equal true + diff --git a/services/track-changes/test/unit/coffee/MongoManager/MongoManagerTests.coffee b/services/track-changes/test/unit/coffee/MongoManager/MongoManagerTests.coffee index ab9fb73ddf..21f57c6477 100644 --- a/services/track-changes/test/unit/coffee/MongoManager/MongoManagerTests.coffee +++ b/services/track-changes/test/unit/coffee/MongoManager/MongoManagerTests.coffee @@ -143,21 +143,39 @@ describe "MongoManager", -> @from = new Date(Date.now()) @to = new Date(Date.now() + 100000) - @MongoManager.getUpdatesBetweenDates @doc_id, @from, @to, @callback + describe "with a toDate", -> + beforeEach -> + @MongoManager.getUpdatesBetweenDates @doc_id, @from, @to, @callback - it "should find the updates for the doc", -> - @db.docHistory.find - .calledWith({ - doc_id: ObjectId(@doc_id) - "meta.start_ts": { $gte: @from } - "meta.end_ts": { $lte: @to } - }) - .should.equal true + it "should find the all updates between the to and from date", -> + @db.docHistory.find + .calledWith({ + doc_id: ObjectId(@doc_id) + "meta.start_ts": { $gte: @from } + "meta.end_ts": { $lte: @to } + }) + .should.equal true - it "should sort in descending timestamp order", -> - @db.docHistory.sort - .calledWith("meta.end_ts": -1) - .should.equal true + it "should sort in descending timestamp order", -> + @db.docHistory.sort + .calledWith("meta.end_ts": -1) + .should.equal true + + it "should call the call back with the updates", -> + @callback.calledWith(null, @updates).should.equal true + + describe "without a todo date", -> + beforeEach -> + @MongoManager.getUpdatesBetweenDates @doc_id, @from, null, @callback + + it "should find the all updates after the from date", -> + @db.docHistory.find + .calledWith({ + doc_id: ObjectId(@doc_id) + "meta.start_ts": { $gte: @from } + }) + .should.equal true + + it "should call the call back with the updates", -> + @callback.calledWith(null, @updates).should.equal true - it "should call the call back with the updates", -> - @callback.calledWith(null, @updates).should.equal true