diff --git a/services/track-changes/app/coffee/HistoryManager.coffee b/services/track-changes/app/coffee/HistoryManager.coffee index 9d332a6f79..7ed112f996 100644 --- a/services/track-changes/app/coffee/HistoryManager.coffee +++ b/services/track-changes/app/coffee/HistoryManager.coffee @@ -1,55 +1,17 @@ -{db, ObjectId} = require "./mongojs" +MongoManager = require "./MongoManager" UpdateCompressor = require "./UpdateCompressor" logger = require "logger-sharelatex" -async = require "async" module.exports = HistoryManager = - getLastCompressedUpdate: (doc_id, callback = (error, update) ->) -> - db.docHistory - .find(doc_id: ObjectId(doc_id.toString())) - .sort(timestamp: -1) - .limit(1) - .toArray (error, compressedUpdates) -> - return callback(error) if error? - return callback null, compressedUpdates[0] or null - - deleteCompressedUpdate: (id, callback = (error) ->) -> - db.docHistory.remove({ _id: ObjectId(id.toString()) }, callback) - - popLastCompressedUpdate: (doc_id, callback = (error, update) ->) -> - HistoryManager.getLastCompressedUpdate doc_id, (error, update) -> - return callback(error) if error? - if update? - HistoryManager.deleteCompressedUpdate update._id, (error) -> - return callback(error) if error? - callback null, update - else - callback null, null - - insertCompressedUpdates: (doc_id, updates, callback = (error) ->) -> - jobs = [] - for update in updates - do (update) -> - jobs.push (callback) -> HistoryManager.insertCompressedUpdate doc_id, update, callback - async.series jobs, callback - - insertCompressedUpdate: (doc_id, update, callback = (error) ->) -> - logger.log doc_id: doc_id, update: update, "inserting compressed update" - db.docHistory.insert { - doc_id: ObjectId(doc_id.toString()) - op: update.op - meta: update.meta - }, callback - compressAndSaveRawUpdates: (doc_id, rawUpdates, callback = (error) ->) -> length = rawUpdates.length if length == 0 return callback() - HistoryManager.popLastCompressedUpdate doc_id, (error, lastCompressedUpdate) -> + MongoManager.popLastCompressedUpdate doc_id, (error, lastCompressedUpdate) -> return callback(error) if error? compressedUpdates = UpdateCompressor.compressRawUpdates lastCompressedUpdate, rawUpdates - HistoryManager.insertCompressedUpdates doc_id, compressedUpdates, (error) -> + MongoManager.insertCompressedUpdates doc_id, compressedUpdates, (error) -> return callback(error) if error? logger.log doc_id: doc_id, rawUpdatesLength: length, compressedUpdatesLength: compressedUpdates.length, "compressed doc updates" callback() diff --git a/services/track-changes/app/coffee/MongoManager.coffee b/services/track-changes/app/coffee/MongoManager.coffee new file mode 100644 index 0000000000..dc17f12d76 --- /dev/null +++ b/services/track-changes/app/coffee/MongoManager.coffee @@ -0,0 +1,40 @@ +{db, ObjectId} = require "./mongojs" +async = require "async" + +module.exports = MongoManager = + getLastCompressedUpdate: (doc_id, callback = (error, update) ->) -> + db.docHistory + .find(doc_id: ObjectId(doc_id.toString())) + .sort( "meta.end_ts": -1) + .limit(1) + .toArray (error, compressedUpdates) -> + return callback(error) if error? + return callback null, compressedUpdates[0] or null + + deleteCompressedUpdate: (id, callback = (error) ->) -> + db.docHistory.remove({ _id: ObjectId(id.toString()) }, callback) + + popLastCompressedUpdate: (doc_id, callback = (error, update) ->) -> + MongoManager.getLastCompressedUpdate doc_id, (error, update) -> + return callback(error) if error? + if update? + MongoManager.deleteCompressedUpdate update._id, (error) -> + return callback(error) if error? + callback null, update + else + callback null, null + + insertCompressedUpdates: (doc_id, updates, callback = (error) ->) -> + jobs = [] + for update in updates + do (update) -> + jobs.push (callback) -> MongoManager.insertCompressedUpdate doc_id, update, callback + async.series jobs, callback + + insertCompressedUpdate: (doc_id, update, callback = (error) ->) -> + db.docHistory.insert { + doc_id: ObjectId(doc_id.toString()) + op: update.op + meta: update.meta + v: update.v + }, callback \ No newline at end of file diff --git a/services/track-changes/test/unit/coffee/HistoryManager/HistoryManagerTests.coffee b/services/track-changes/test/unit/coffee/HistoryManager/HistoryManagerTests.coffee index 9750f95461..4c1bae0564 100644 --- a/services/track-changes/test/unit/coffee/HistoryManager/HistoryManagerTests.coffee +++ b/services/track-changes/test/unit/coffee/HistoryManager/HistoryManagerTests.coffee @@ -9,20 +9,20 @@ describe "HistoryManager", -> beforeEach -> @HistoryManager = SandboxedModule.require modulePath, requires: "./UpdateCompressor": @UpdateCompressor = {} - "./mongojs" : {} + "./MongoManager" : @MongoManager = {} "logger-sharelatex": { log: sinon.stub() } @doc_id = "doc-id-123" @callback = sinon.stub() describe "when there are no raw ops", -> beforeEach -> - @HistoryManager.popLastCompressedUpdate = sinon.stub() - @HistoryManager.insertCompressedUpdates = sinon.stub() + @MongoManager.popLastCompressedUpdate = sinon.stub() + @MongoManager.insertCompressedUpdates = sinon.stub() @HistoryManager.compressAndSaveRawUpdates @doc_id, [], @callback it "should not need to access the database", -> - @HistoryManager.popLastCompressedUpdate.called.should.equal false - @HistoryManager.insertCompressedUpdates.called.should.equal false + @MongoManager.popLastCompressedUpdate.called.should.equal false + @MongoManager.insertCompressedUpdates.called.should.equal false it "should call the callback", -> @callback.called.should.equal true @@ -32,13 +32,13 @@ describe "HistoryManager", -> @rawUpdates = ["mock-raw-op-1", "mock-raw-op-2"] @compressedUpdates = ["mock-compressed-op"] - @HistoryManager.popLastCompressedUpdate = sinon.stub().callsArgWith(1, null, null) - @HistoryManager.insertCompressedUpdates = sinon.stub().callsArg(2) + @MongoManager.popLastCompressedUpdate = sinon.stub().callsArgWith(1, null, null) + @MongoManager.insertCompressedUpdates = sinon.stub().callsArg(2) @UpdateCompressor.compressRawUpdates = sinon.stub().returns(@compressedUpdates) @HistoryManager.compressAndSaveRawUpdates @doc_id, @rawUpdates, @callback it "should try to pop the last compressed op", -> - @HistoryManager.popLastCompressedUpdate + @MongoManager.popLastCompressedUpdate .calledWith(@doc_id) .should.equal true @@ -48,7 +48,7 @@ describe "HistoryManager", -> .should.equal true it "should save the compressed ops", -> - @HistoryManager.insertCompressedUpdates + @MongoManager.insertCompressedUpdates .calledWith(@doc_id, @compressedUpdates) .should.equal true @@ -61,13 +61,13 @@ describe "HistoryManager", -> @lastCompressedUpdate = "mock-last-compressed-op-0" @compressedUpdates = ["mock-compressed-op-1"] - @HistoryManager.popLastCompressedUpdate = sinon.stub().callsArgWith(1, null, @lastCompressedUpdate) - @HistoryManager.insertCompressedUpdates = sinon.stub().callsArg(2) + @MongoManager.popLastCompressedUpdate = sinon.stub().callsArgWith(1, null, @lastCompressedUpdate) + @MongoManager.insertCompressedUpdates = sinon.stub().callsArg(2) @UpdateCompressor.compressRawUpdates = sinon.stub().returns(@compressedUpdates) @HistoryManager.compressAndSaveRawUpdates @doc_id, @rawUpdates, @callback it "should try to pop the last compressed op", -> - @HistoryManager.popLastCompressedUpdate + @MongoManager.popLastCompressedUpdate .calledWith(@doc_id) .should.equal true @@ -77,7 +77,7 @@ describe "HistoryManager", -> .should.equal true it "should save the compressed ops", -> - @HistoryManager.insertCompressedUpdates + @MongoManager.insertCompressedUpdates .calledWith(@doc_id, @compressedUpdates) .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 new file mode 100644 index 0000000000..0aa43677e4 --- /dev/null +++ b/services/track-changes/test/unit/coffee/MongoManager/MongoManagerTests.coffee @@ -0,0 +1,133 @@ +sinon = require('sinon') +chai = require('chai') +should = chai.should() +expect = chai.expect +modulePath = "../../../../app/js/MongoManager.js" +SandboxedModule = require('sandboxed-module') +{ObjectId} = require("mongojs") + +describe "MongoManager", -> + beforeEach -> + @MongoManager = SandboxedModule.require modulePath, requires: + "./mongojs" : { db: @db = {}, ObjectId: ObjectId } + @callback = sinon.stub() + @doc_id = ObjectId().toString() + + describe "getLastCompressedUpdate", -> + beforeEach -> + @update = "mock-update" + @db.docHistory = {} + @db.docHistory.find = sinon.stub().returns @db.docHistory + @db.docHistory.sort = sinon.stub().returns @db.docHistory + @db.docHistory.limit = sinon.stub().returns @db.docHistory + @db.docHistory.toArray = sinon.stub().callsArgWith(0, null, [@update]) + + @MongoManager.getLastCompressedUpdate @doc_id, @callback + + it "should find the updates for the doc", -> + @db.docHistory.find + .calledWith(doc_id: ObjectId(@doc_id)) + .should.equal true + + it "should limit to one result", -> + @db.docHistory.limit + .calledWith(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 update", -> + @callback.calledWith(null, @update).should.equal true + + describe "deleteCompressedUpdate", -> + beforeEach -> + @update_id = ObjectId().toString() + @db.docHistory = + remove: sinon.stub().callsArg(1) + @MongoManager.deleteCompressedUpdate(@update_id, @callback) + + it "should remove the update", -> + @db.docHistory.remove + .calledWith(_id: ObjectId(@update_id)) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "popLastCompressedUpdate", -> + describe "when there is no last update", -> + beforeEach -> + @MongoManager.getLastCompressedUpdate = sinon.stub().callsArgWith(1, null, null) + @MongoManager.deleteCompressedUpdate = sinon.stub() + @MongoManager.popLastCompressedUpdate @doc_id, @callback + + it "should get the last update", -> + @MongoManager.getLastCompressedUpdate + .calledWith(@doc_id) + .should.equal true + + it "should not try to delete the last update", -> + @MongoManager.deleteCompressedUpdate.called.should.equal false + + it "should call the callback with no update", -> + @callback.calledWith(null, null).should.equal true + + describe "when there is an update", -> + beforeEach -> + @update = { _id: Object() } + @MongoManager.getLastCompressedUpdate = sinon.stub().callsArgWith(1, null, @update) + @MongoManager.deleteCompressedUpdate = sinon.stub().callsArgWith(1, null) + @MongoManager.popLastCompressedUpdate @doc_id, @callback + + it "should get the last update", -> + @MongoManager.getLastCompressedUpdate + .calledWith(@doc_id) + .should.equal true + + it "should delete the last update", -> + @MongoManager.deleteCompressedUpdate + .calledWith(@update._id) + .should.equal true + + it "should call the callback with the update", -> + @callback.calledWith(null, @update).should.equal true + + describe "insertCompressedUpdate", -> + beforeEach -> + @update = { op: "op", meta: "meta", v: "v"} + @db.docHistory = + insert: sinon.stub().callsArg(1) + @MongoManager.insertCompressedUpdate @doc_id, @update, @callback + + it "should insert the update", -> + @db.docHistory.insert + .calledWith({ + doc_id: ObjectId(@doc_id), + op: @update.op, + meta: @update.meta, + v: @update.v + }) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "insertCompressedUpdates", -> + beforeEach (done) -> + @updates = [ "mock-update-1", "mock-update-2" ] + @MongoManager.insertCompressedUpdate = sinon.stub().callsArg(2) + @MongoManager.insertCompressedUpdates @doc_id, @updates, (args...) => + @callback(args...) + done() + + it "should insert each update", -> + for update in @updates + @MongoManager.insertCompressedUpdate + .calledWith(@doc_id, update) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true