Просмотр исходного кода

Set and get ranges along with lines

James Allen 10 лет назад
Родитель
Сommit
0a65a5eabf

+ 46 - 22
services/docstore/app/coffee/DocManager.coffee

@@ -3,6 +3,7 @@ Errors = require "./Errors"
 logger = require "logger-sharelatex"
 _ = require "underscore"
 DocArchive = require "./DocArchiveManager"
+RangeManager = require "./RangeManager"
 
 module.exports = DocManager =
 	# TODO: For historical reasons, the doc version is currently stored in the docOps
@@ -40,7 +41,7 @@ module.exports = DocManager =
 				else
 					return callback(null, docs)
 
-	updateDoc: (project_id, doc_id, lines, version, callback = (error, modified, rev) ->) ->
+	updateDoc: (project_id, doc_id, lines, version, ranges, callback = (error, modified, rev) ->) ->
 		if !lines? or !version?
 			return callback(new Error("no lines or version provided"))
 	
@@ -48,33 +49,56 @@ module.exports = DocManager =
 			if err? and !(err instanceof Errors.NotFoundError)
 				logger.err project_id: project_id, doc_id: doc_id, err:err, "error getting document for update"
 				return callback(err)
+			
+			ranges = RangeManager.jsonRangesToMongo(ranges)
 
-			isNewDoc = lines.length == 0
-			linesAreSame =  _.isEqual(doc?.lines, lines)
-			versionsAreSame = (doc?.version == version)
-
-			if linesAreSame and versionsAreSame and !isNewDoc
-				logger.log project_id: project_id, doc_id: doc_id, rev: doc?.rev, "doc lines have not changed - not updating"
-				return callback null, false, doc?.rev
+			if !doc?
+				# If the document doesn't exist, we'll make sure to create/update all parts of it.
+				updateLines = true
+				updateVersion = true
+				updateRanges = true
 			else
-				oldRev = doc?.rev || 0
-				logger.log {
-					project_id: project_id
-					doc_id: doc_id,
-					oldDocLines: doc?.lines
-					newDocLines: lines
-					rev: oldRev
-					oldVersion: doc?.version
-					newVersion: version
-				}, "updating doc lines"
-				MongoManager.upsertIntoDocCollection project_id, doc_id, {lines}, (error)->
+				updateLines = not _.isEqual(doc.lines, lines)
+				updateVersion = (doc.version != version)
+				updateRanges = RangeManager.shouldUpdateRanges(doc.ranges, ranges)
+			
+			modified = false
+			rev = doc?.rev || 0
+
+			updateLinesAndRangesIfNeeded = (cb) ->
+				if updateLines or updateRanges
+					update = {}
+					if updateLines
+						update.lines = lines
+					if updateRanges
+						update.ranges = ranges
+					logger.log { project_id, doc_id, oldDoc: doc, update: update }, "updating doc lines and ranges"
+					
+					modified = true
+					rev += 1 # rev will be incremented in mongo by MongoManager.upsertIntoDocCollection
+					MongoManager.upsertIntoDocCollection project_id, doc_id, update, cb
+				else
+					logger.log { project_id, doc_id, }, "doc lines have not changed - not updating"
+					cb()
+			
+			updateVersionIfNeeded = (cb) ->
+				if updateVersion
+					logger.log { project_id, doc_id, oldVersion: doc?.version, newVersion: version }, "updating doc version"
+					modified = true
+					MongoManager.setDocVersion doc_id, version, cb
+				else
+					logger.log { project_id, doc_id, version }, "doc version has not changed - not updating"
+					cb()
+			
+			updateLinesAndRangesIfNeeded (error) ->
+				return callback(error) if error?
+				updateVersionIfNeeded (error) ->
 					return callback(callback) if error?
-					MongoManager.setDocVersion doc_id, version, (error) ->
-						return callback(error) if error?
-						callback null, true, oldRev + 1 # rev will have been incremented in mongo by MongoManager.updateDoc
+					callback null, modified, rev
 
 	deleteDoc: (project_id, doc_id, callback = (error) ->) ->
 		DocManager.getDoc project_id, doc_id, { version: false }, (error, doc) ->
 			return callback(error) if error?
 			return callback new Errors.NotFoundError("No such project/doc to delete: #{project_id}/#{doc_id}") if !doc?
 			MongoManager.markDocAsDeleted project_id, doc_id, callback
+

+ 4 - 1
services/docstore/app/coffee/HttpController.coffee

@@ -50,6 +50,7 @@ module.exports = HttpController =
 		doc_id     = req.params.doc_id
 		lines      = req.body?.lines
 		version    = req.body?.version
+		ranges     = req.body?.ranges
 
 		if !lines? or lines not instanceof Array
 			logger.error project_id: project_id, doc_id: doc_id, "no doc lines provided"
@@ -62,7 +63,7 @@ module.exports = HttpController =
 			return
 
 		logger.log project_id: project_id, doc_id: doc_id, "got http request to update doc"
-		DocManager.updateDoc project_id, doc_id, lines, version, (error, modified, rev) ->
+		DocManager.updateDoc project_id, doc_id, lines, version, ranges, (error, modified, rev) ->
 			return next(error) if error?
 			res.json {
 				modified: modified
@@ -86,6 +87,8 @@ module.exports = HttpController =
 		}
 		if doc.version?
 			doc_view.version = doc.version
+		if doc.ranges?
+			doc_view.ranges = doc.ranges
 		return doc_view
 
 	_buildRawDocView: (doc)->

+ 35 - 0
services/docstore/app/coffee/RangeManager.coffee

@@ -0,0 +1,35 @@
+_ = require "underscore"
+{ObjectId} = require("./mongojs")
+
+module.exports = RangeManager =
+	shouldUpdateRanges: (doc_ranges, incoming_ranges) ->
+		# TODO: If we have no incoming_ranges, just ignore for now while
+		# we're rolling this out, but eventually this should be a mandatory
+		# field and this will become an error condition
+		return false if !incoming_ranges?
+
+		# If the ranges are empty, we don't store them in the DB, so set
+		# doc_ranges to an empty object as default, since this is was the 
+		# incoming_ranges will be for an empty range set.
+		if !doc_ranges?
+			doc_ranges = {}
+
+		return not _.isEqual(doc_ranges, incoming_ranges)
+	
+	jsonRangesToMongo: (ranges) ->
+		return null if !ranges?
+		for change in ranges.changes or []
+			change.id = @_safeObjectId(change.id)
+			if change.metadata?.ts?
+				change.metadata.ts = new Date(change.metadata.ts)
+		for comment in ranges.comments or []
+			comment.id = @_safeObjectId(comment.id)
+			if comment.metadata?.ts?
+				comment.metadata.ts = new Date(comment.metadata.ts)
+		return ranges
+	
+	_safeObjectId: (data) ->
+		try
+			return ObjectId(data)
+		catch
+			return data

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
services/docstore/test/acceptance/coffee/ArchiveDocsTests.coffee


+ 2 - 1
services/docstore/test/acceptance/coffee/DeletingDocsTests.coffee

@@ -11,7 +11,8 @@ describe "Deleting a doc", ->
 		@doc_id = ObjectId()
 		@lines = ["original", "lines"]
 		@version = 42
-		DocstoreClient.createDoc @project_id, @doc_id, @lines, (error) =>
+		@ranges = []
+		DocstoreClient.createDoc @project_id, @doc_id, @lines, @version, @ranges, (error) =>
 			throw error if error?
 			done()
 

+ 4 - 2
services/docstore/test/acceptance/coffee/GettingAllDocsTests.coffee

@@ -22,12 +22,14 @@ describe "Getting all docs", ->
 			lines: ["111", "222", "333"]
 			rev: 6
 		}]
+		version = 42
+		ranges = 42
 		jobs = for doc in @docs
 			do (doc) =>
 				(callback) => 
-					DocstoreClient.createDoc @project_id, doc._id, doc.lines, (err)=>
+					DocstoreClient.createDoc @project_id, doc._id, doc.lines, version, ranges, (err)=>
 						doc.lines[0] = doc.lines[0]+" added"
-						DocstoreClient.updateDoc @project_id, doc._id, doc.lines, 42, callback
+						DocstoreClient.updateDoc @project_id, doc._id, doc.lines, version, ranges, callback
 		async.series jobs, done 
 
 	it "should return all the docs", (done) ->

+ 18 - 2
services/docstore/test/acceptance/coffee/GettingDocsTests.coffee

@@ -10,7 +10,17 @@ describe "Getting a doc", ->
 		@project_id = ObjectId()
 		@doc_id = ObjectId()
 		@lines = ["original", "lines"]
-		DocstoreClient.createDoc @project_id, @doc_id, @lines, (error) =>
+		@version = 42
+		@ranges = {
+			changes: [{
+				id: ObjectId().toString()
+				op: { i: "foo", p: 3 }
+				meta:
+					user_id: ObjectId().toString()
+					ts: new Date().toString()
+			}]
+		}
+		DocstoreClient.createDoc @project_id, @doc_id, @lines, @version, @ranges, (error) =>
 			throw error if error?
 			done()
 
@@ -18,6 +28,8 @@ describe "Getting a doc", ->
 		it "should get the doc lines and version", (done) ->
 			DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) =>
 				doc.lines.should.deep.equal @lines
+				doc.version.should.equal @version
+				doc.ranges.should.deep.equal @ranges
 				done()
 
 	describe "when the doc does not exist", ->
@@ -30,11 +42,15 @@ describe "Getting a doc", ->
 	describe "when the doc is a deleted doc", ->
 		beforeEach (done) ->
 			@deleted_doc_id = ObjectId()
-			DocstoreClient.createDeletedDoc @project_id, @deleted_doc_id, @lines, done
+			DocstoreClient.createDoc @project_id, @deleted_doc_id, @lines, @version, @ranges, (error) =>
+				throw error if error?
+				DocstoreClient.deleteDoc @project_id, @deleted_doc_id, done
 
 		it "should return the doc", (done) ->
 			DocstoreClient.getDoc @project_id, @deleted_doc_id, {include_deleted:true},(error, res, doc) =>
 				doc.lines.should.deep.equal @lines
+				doc.version.should.equal @version
+				doc.ranges.should.deep.equal @ranges
 				doc.deleted.should.equal true
 				done()
 

+ 71 - 33
services/docstore/test/acceptance/coffee/UpdatingDocsTests.coffee

@@ -11,16 +11,47 @@ describe "Applying updates to a doc", ->
 		@doc_id = ObjectId()
 		@originalLines = ["original", "lines"]
 		@newLines = ["new", "lines"]
+		@originalRanges = {
+			changes: [{
+				id: ObjectId().toString()
+				op: { i: "foo", p: 3 }
+				meta:
+					user_id: ObjectId().toString()
+					ts: new Date().toString()
+			}]
+		}
+		@newRanges = {
+			changes: [{
+				id: ObjectId().toString()
+				op: { i: "bar", p: 6 }
+				meta:
+					user_id: ObjectId().toString()
+					ts: new Date().toString()
+			}]
+		}
 		@version = 42
-		DocstoreClient.createDoc @project_id, @doc_id, @originalLines, (error) =>
+		DocstoreClient.createDoc @project_id, @doc_id, @originalLines, @version, @originalRanges, (error) =>
 			throw error if error?
-			DocstoreClient.setDocVersion @doc_id, @version, (error) =>
-				throw error if error?
+			done()
+
+	describe "when nothing has been updated", ->
+		beforeEach (done) ->
+			DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version, @originalRanges, (error, res, @body) =>
+				done()
+
+		it "should return modified = false", ->
+			@body.modified.should.equal false
+
+		it "should not update the doc in the API", (done) ->
+			DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) =>
+				doc.lines.should.deep.equal @originalLines
+				doc.version.should.equal @version
+				doc.ranges.should.deep.equal @originalRanges
 				done()
 
 	describe "when the lines have changed", ->
 		beforeEach (done) ->
-			DocstoreClient.updateDoc @project_id, @doc_id, @newLines, @version, (error, res, @body) =>
+			DocstoreClient.updateDoc @project_id, @doc_id, @newLines, @version, @originalRanges, (error, res, @body) =>
 				done()
 
 		it "should return modified = true", ->
@@ -29,29 +60,53 @@ describe "Applying updates to a doc", ->
 		it "should return the rev", ->
 			@body.rev.should.equal 2
 
-		it "should update the doc in the API but not change the version", (done) ->
+		it "should update the doc in the API", (done) ->
 			DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) =>
 				doc.lines.should.deep.equal @newLines
 				doc.version.should.equal @version
+				doc.ranges.should.deep.equal @originalRanges
 				done()
 
-	describe "when the lines and version have not been updated", ->
+	describe "when the version has changed", ->
 		beforeEach (done) ->
-			DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version, (error, res, @body) =>
+			DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version + 1, @originalRanges, (error, res, @body) =>
 				done()
 
-		it "should return modified = false", ->
-			@body.modified.should.equal false
+		it "should return modified = true", ->
+			@body.modified.should.equal true
 
-		it "should not update the doc in the API", (done) ->
+		it "should return the rev", ->
+			@body.rev.should.equal 2
+
+		it "should update the doc in the API", (done) ->
 			DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) =>
 				doc.lines.should.deep.equal @originalLines
+				doc.version.should.equal @version + 1
+				doc.ranges.should.deep.equal @originalRanges
+				done()
+
+	describe "when the ranges have changed", ->
+		beforeEach (done) ->
+			DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version, @newRanges, (error, res, @body) =>
+				done()
+
+		it "should return modified = true", ->
+			@body.modified.should.equal true
+
+		it "should return the rev", ->
+			@body.rev.should.equal 2
+
+		it "should update the doc in the API", (done) ->
+			DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) =>
+				doc.lines.should.deep.equal @originalLines
+				doc.version.should.equal @version
+				doc.ranges.should.deep.equal @newRanges
 				done()
 
 	describe "when the doc does not exist", ->
 		beforeEach (done) ->
 			@missing_doc_id = ObjectId()
-			DocstoreClient.updateDoc @project_id, @missing_doc_id, @originalLines, 0, (error, @res, @body) =>
+			DocstoreClient.updateDoc @project_id, @missing_doc_id, @originalLines, 0, @originalRanges, (error, @res, @body) =>
 				done()
 
 		it "should create the doc", ->
@@ -61,12 +116,13 @@ describe "Applying updates to a doc", ->
 			DocstoreClient.getDoc @project_id, @missing_doc_id, {}, (error, res, doc) =>
 				doc.lines.should.deep.equal @originalLines
 				doc.version.should.equal 0
+				doc.ranges.should.deep.equal @originalRanges
 				done()
 
 	describe "when malformed doc lines are provided", ->
 		describe "when the lines are not an array", ->
 			beforeEach (done) ->
-				DocstoreClient.updateDoc @project_id, @doc_id, { foo: "bar" }, @version, (error, @res, @body) =>
+				DocstoreClient.updateDoc @project_id, @doc_id, { foo: "bar" }, @version, @originalRanges, (error, @res, @body) =>
 					done()
 
 			it "should return 400", ->
@@ -79,7 +135,7 @@ describe "Applying updates to a doc", ->
 
 		describe "when the lines are not present", ->
 			beforeEach (done) ->
-				DocstoreClient.updateDoc @project_id, @doc_id, null, @version, (error, @res, @body) =>
+				DocstoreClient.updateDoc @project_id, @doc_id, null, @version, @originalRanges, (error, @res, @body) =>
 					done()
 
 			it "should return 400", ->
@@ -92,7 +148,7 @@ describe "Applying updates to a doc", ->
 	
 	describe "when no version is provided", ->
 		beforeEach (done) ->
-			DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, null, (error, @res, @body) =>
+			DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, null, @originalRanges, (error, @res, @body) =>
 				done()
 
 		it "should return 400", ->
@@ -108,7 +164,7 @@ describe "Applying updates to a doc", ->
 		beforeEach (done) ->
 			line = new Array(1025).join("x") # 1kb
 			@largeLines = Array.apply(null, Array(1024)).map(() -> line) # 1mb
-			DocstoreClient.updateDoc @project_id, @doc_id, @largeLines, @version, (error, res, @body) =>
+			DocstoreClient.updateDoc @project_id, @doc_id, @largeLines, @version, @originalRanges, (error, res, @body) =>
 				done()
 
 		it "should return modified = true", ->
@@ -118,21 +174,3 @@ describe "Applying updates to a doc", ->
 			DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) =>
 				doc.lines.should.deep.equal @largeLines
 				done()
-
-	describe "when the version has changed", ->
-		beforeEach (done) ->
-			DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version + 1, (error, res, @body) =>
-				done()
-
-		it "should return modified = true", ->
-			@body.modified.should.equal true
-
-		it "should return the rev", ->
-			@body.rev.should.equal 2
-
-		it "should update the doc in the API", (done) ->
-			DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) =>
-				doc.lines.should.deep.equal @originalLines
-				doc.version.should.equal @version + 1
-				done()
-

+ 4 - 14
services/docstore/test/acceptance/coffee/helpers/DocstoreClient.coffee

@@ -5,19 +5,8 @@ DocArchiveManager = require("../../../../app/js/DocArchiveManager.js")
 
 module.exports = DocstoreClient =
 
-	createDoc: (project_id, doc_id, lines, callback = (error) ->) ->
-		db.docs.save({_id: doc_id, project_id:project_id, lines: lines, rev:1}, callback)
-	
-	setDocVersion: (doc_id, version, callback = (error) ->) ->
-		db.docOps.save({doc_id: doc_id, version: version}, callback)
-
-	createDeletedDoc: (project_id, doc_id, lines, callback = (error) ->) ->
-		db.docs.insert {
-			_id: doc_id
-			project_id: project_id
-			lines: lines
-			deleted: true
-		}, callback
+	createDoc: (project_id, doc_id, lines, version, ranges, callback = (error) ->) ->
+		DocstoreClient.updateDoc project_id, doc_id, lines, version, ranges, callback
 
 	getDoc: (project_id, doc_id, qs, callback = (error, res, body) ->) ->
 		request.get {
@@ -32,12 +21,13 @@ module.exports = DocstoreClient =
 			json: true
 		}, callback
 
-	updateDoc: (project_id, doc_id, lines, version, callback = (error, res, body) ->) ->
+	updateDoc: (project_id, doc_id, lines, version, ranges, callback = (error, res, body) ->) ->
 		request.post {
 			url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/doc/#{doc_id}"
 			json:
 				lines: lines
 				version: version
+				ranges: ranges
 		}, callback
 
 	deleteDoc: (project_id, doc_id, callback = (error, res, body) ->) ->

+ 82 - 43
services/docstore/test/unit/coffee/DocManagerTests.coffee

@@ -12,6 +12,10 @@ describe "DocManager", ->
 		@DocManager = SandboxedModule.require modulePath, requires:
 			"./MongoManager": @MongoManager = {}
 			"./DocArchiveManager": @DocArchiveManager = {}
+			"./RangeManager": @RangeManager = {
+				jsonRangesToMongo: (r) -> r
+				shouldUpdateRanges: sinon.stub().returns false
+			}
 			"logger-sharelatex": @logger = 
 				log: sinon.stub()
 				warn:->
@@ -160,17 +164,35 @@ describe "DocManager", ->
 		beforeEach ->
 			@oldDocLines = ["old", "doc", "lines"]
 			@newDocLines = ["new", "doc", "lines"]
+			@originalRanges = {
+				changes: [{
+					id: ObjectId().toString()
+					op: { i: "foo", p: 3 }
+					meta:
+						user_id: ObjectId().toString()
+						ts: new Date().toString()
+				}]
+			}
+			@newRanges = {
+				changes: [{
+					id: ObjectId().toString()
+					op: { i: "bar", p: 6 }
+					meta:
+						user_id: ObjectId().toString()
+						ts: new Date().toString()
+				}]
+			}
 			@version = 42
-			@doc = { _id: @doc_id, project_id: @project_id, lines: @oldDocLines, rev: @rev = 5, version: @version }
+			@doc = { _id: @doc_id, project_id: @project_id, lines: @oldDocLines, rev: @rev = 5, version: @version, ranges: @originalRanges }
 
 			@MongoManager.upsertIntoDocCollection = sinon.stub().callsArg(3)
 			@MongoManager.setDocVersion = sinon.stub().yields()
 			@DocManager.getDoc = sinon.stub()
 
-		describe "when the doc lines have changed", ->
+		describe "when only the doc lines have changed", ->
 			beforeEach ->
 				@DocManager.getDoc = sinon.stub().callsArgWith(3, null, @doc)
-				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @callback
+				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @originalRanges, @callback
 
 			it "should get the existing doc", ->
 				@DocManager.getDoc
@@ -182,61 +204,84 @@ describe "DocManager", ->
 					.calledWith(@project_id, @doc_id, {lines: @newDocLines})
 					.should.equal true
 			
-			it "should update the version", ->
-				@MongoManager.setDocVersion
-					.calledWith(@doc_id, @version)
+			it "should not update the version", ->
+				@MongoManager.setDocVersion.called.should.equal false
+
+			it "should return the callback with the new rev", ->
+				@callback.calledWith(null, true, @rev + 1).should.equal true
+
+		describe "when the doc ranges have changed", ->
+			beforeEach ->
+				@DocManager.getDoc = sinon.stub().callsArgWith(3, null, @doc)
+				@RangeManager.shouldUpdateRanges.returns true
+				@DocManager.updateDoc @project_id, @doc_id, @oldDocLines, @version, @newRanges, @callback
+
+			it "should get the existing doc", ->
+				@DocManager.getDoc
+					.calledWith(@project_id, @doc_id)
 					.should.equal true
 
-			it "should log out the old and new doc lines", ->
-				@logger.log
-					.calledWith(
-						project_id: @project_id
-						doc_id: @doc_id
-						oldDocLines: @oldDocLines
-						newDocLines: @newDocLines
-						rev: @doc.rev
-						oldVersion: @version
-						newVersion: @version
-						"updating doc lines"
-					)
+			it "should upsert the ranges", ->
+				@MongoManager.upsertIntoDocCollection
+					.calledWith(@project_id, @doc_id, {ranges: @newRanges})
 					.should.equal true
+			
+			it "should not update the version", ->
+				@MongoManager.setDocVersion.called.should.equal false
 
 			it "should return the callback with the new rev", ->
 				@callback.calledWith(null, true, @rev + 1).should.equal true
 
-		describe "when the version has changed", ->
+		describe "when only the version has changed", ->
 			beforeEach ->
 				@DocManager.getDoc = sinon.stub().callsArgWith(3, null, @doc)
-				@DocManager.updateDoc @project_id, @doc_id, @oldDocLines, @version + 1, @callback
+				@DocManager.updateDoc @project_id, @doc_id, @oldDocLines, @version + 1, @originalRanges, @callback
 
 			it "should get the existing doc with the version", ->
 				@DocManager.getDoc
 					.calledWith(@project_id, @doc_id, {version: true})
 					.should.equal true
 
-			it "should upsert the document to the doc collection", ->
-				@MongoManager.upsertIntoDocCollection
-					.calledWith(@project_id, @doc_id, {lines: @oldDocLines})
-					.should.equal true
+			it "should not change the lines or ranges", ->
+				@MongoManager.upsertIntoDocCollection.called.should.equal false
 			
 			it "should update the version", ->
 				@MongoManager.setDocVersion
 					.calledWith(@doc_id, @version + 1)
 					.should.equal true
 
-			it "should return the callback with the new rev", ->
-				@callback.calledWith(null, true, @rev + 1).should.equal true
+			it "should return the callback with the old rev", ->
+				@callback.calledWith(null, true, @rev).should.equal true
+
+		describe "when the doc has not changed at all", ->
+			beforeEach ->
+				@DocManager.getDoc = sinon.stub().callsArgWith(3, null, @doc)
+				@DocManager.updateDoc @project_id, @doc_id, @oldDocLines, @version, @originalRanges, @callback
+
+			it "should get the existing doc", ->
+				@DocManager.getDoc
+					.calledWith(@project_id, @doc_id)
+					.should.equal true
+
+			it "should not update the ranges or lines", ->
+				@MongoManager.upsertIntoDocCollection.called.should.equal false
+			
+			it "should not update the version", ->
+				@MongoManager.setDocVersion.called.should.equal false
+
+			it "should return the callback with the old rev and modified == false", ->
+				@callback.calledWith(null, false, @rev).should.equal true
 
 		describe "when the version is null", ->
 			beforeEach ->
-				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, null, @callback
+				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, null, @originalRanges, @callback
 
 			it "should return an error", ->
 				@callback.calledWith(new Error("no lines or version provided")).should.equal true
 
 		describe "when the lines are null", ->
 			beforeEach ->
-				@DocManager.updateDoc @project_id, @doc_id, null, @version, @callback
+				@DocManager.updateDoc @project_id, @doc_id, null, @version, @originalRanges, @callback
 
 			it "should return an error", ->
 				@callback.calledWith(new Error("no lines or version provided")).should.equal true
@@ -245,7 +290,7 @@ describe "DocManager", ->
 			beforeEach ->
 				@error =  new Error("doc could not be found")
 				@DocManager.getDoc = sinon.stub().callsArgWith(3, @error, null, null)
-				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @callback
+				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @originalRanges, @callback
 			
 			it "should not upsert the document to the doc collection", ->
 				@MongoManager.upsertIntoDocCollection.called.should.equal false
@@ -256,7 +301,7 @@ describe "DocManager", ->
 		describe "when the doc lines have not changed", ->
 			beforeEach ->
 				@DocManager.getDoc = sinon.stub().callsArgWith(3, null, @doc)
-				@DocManager.updateDoc @project_id, @doc_id, @oldDocLines.slice(), @version, @callback
+				@DocManager.updateDoc @project_id, @doc_id, @oldDocLines.slice(), @version, @originalRanges, @callback
 
 			it "should not update the doc", ->
 				@MongoManager.upsertIntoDocCollection.called.should.equal false
@@ -264,25 +309,19 @@ describe "DocManager", ->
 			it "should return the callback with the existing rev", ->
 				@callback.calledWith(null, false, @rev).should.equal true
 
-		describe "when the doc lines are an empty array", ->
-			beforeEach ->
-				@doc.lines = []
-				@DocManager.getDoc = sinon.stub().callsArgWith(3, null, @doc)
-				@DocManager.updateDoc @project_id, @doc_id, @doc.lines, @callback
-
-			it "should upsert the document to the doc collection", ->
-				@MongoManager.upsertIntoDocCollection
-					.calledWith(@project_id, @doc_id, {lines: @doc.lines})
-					.should.equal true	
-
 		describe "when the doc does not exist", ->
 			beforeEach ->
 				@DocManager.getDoc = sinon.stub().callsArgWith(3, null, null, null)
-				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @callback
+				@DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @originalRanges, @callback
 			
 			it "should upsert the document to the doc collection", ->
 				@MongoManager.upsertIntoDocCollection
-					.calledWith(@project_id, @doc_id, {lines: @newDocLines})
+					.calledWith(@project_id, @doc_id, {lines: @newDocLines, ranges: @originalRanges})
+					.should.equal true
+				
+			it "should set the version", ->
+				@MongoManager.setDocVersion
+					.calledWith(@doc_id, @version)
 					.should.equal true
 
 			it "should return the callback with the new rev", ->

+ 2 - 1
services/docstore/test/unit/coffee/HttpControllerTests.coffee

@@ -196,12 +196,13 @@ describe "HttpController", ->
 				@req.body =
 					lines: @lines = ["hello", "world"]
 					version: @version = 42
+					ranges: @ranges = { changes: "mock" }
 				@DocManager.updateDoc = sinon.stub().yields(null, true, @rev = 5)
 				@HttpController.updateDoc @req, @res, @next
 
 			it "should update the document", ->
 				@DocManager.updateDoc
-					.calledWith(@project_id, @doc_id, @lines, @version)
+					.calledWith(@project_id, @doc_id, @lines, @version, @ranges)
 					.should.equal true
 
 			it "should return a modified status", ->

Некоторые файлы не были показаны из-за большого количества измененных файлов