overleaf/services/document-updater/test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee

286 lines
9.9 KiB
CoffeeScript

sinon = require "sinon"
chai = require("chai")
chai.should()
Settings = require('settings-sharelatex')
rclient_history = require("redis-sharelatex").createClient(Settings.redis.history)
ProjectHistoryKeys = Settings.redis.project_history.key_schema
MockProjectHistoryApi = require "./helpers/MockProjectHistoryApi"
MockWebApi = require "./helpers/MockWebApi"
DocUpdaterClient = require "./helpers/DocUpdaterClient"
DocUpdaterApp = require "./helpers/DocUpdaterApp"
describe "Applying updates to a project's structure", ->
before ->
@user_id = 'user-id-123'
@version = 1234
describe "renaming a file", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
@fileUpdate =
id: DocUpdaterClient.randomId()
pathname: '/file-path'
newPathname: '/new-file-path'
@fileUpdates = [ @fileUpdate ]
DocUpdaterApp.ensureRunning (error) =>
throw error if error?
DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, @version, (error) ->
throw error if error?
setTimeout done, 200
it "should push the applied file renames to the project history api", (done) ->
rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
throw error if error?
update = JSON.parse(updates[0])
update.file.should.equal @fileUpdate.id
update.pathname.should.equal '/file-path'
update.new_pathname.should.equal '/new-file-path'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.0"
done()
describe "renaming a document", ->
before ->
@docUpdate =
id: DocUpdaterClient.randomId()
pathname: '/doc-path'
newPathname: '/new-doc-path'
@docUpdates = [ @docUpdate ]
describe "when the document is not loaded", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
throw error if error?
setTimeout done, 200
it "should push the applied doc renames to the project history api", (done) ->
rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
throw error if error?
update = JSON.parse(updates[0])
update.doc.should.equal @docUpdate.id
update.pathname.should.equal '/doc-path'
update.new_pathname.should.equal '/new-doc-path'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.0"
done()
describe "when the document is loaded", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
MockWebApi.insertDoc @project_id, @docUpdate.id, {}
DocUpdaterClient.preloadDoc @project_id, @docUpdate.id, (error) =>
throw error if error?
sinon.spy MockWebApi, "getDocument"
DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
throw error if error?
setTimeout done, 200
after ->
MockWebApi.getDocument.restore()
it "should update the doc", (done) ->
DocUpdaterClient.getDoc @project_id, @docUpdate.id, (error, res, doc) =>
doc.pathname.should.equal @docUpdate.newPathname
done()
it "should push the applied doc renames to the project history api", (done) ->
rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
throw error if error?
update = JSON.parse(updates[0])
update.doc.should.equal @docUpdate.id
update.pathname.should.equal '/doc-path'
update.new_pathname.should.equal '/new-doc-path'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.0"
done()
describe "renaming multiple documents and files", ->
before ->
@docUpdate0 =
id: DocUpdaterClient.randomId()
pathname: '/doc-path0'
newPathname: '/new-doc-path0'
@docUpdate1 =
id: DocUpdaterClient.randomId()
pathname: '/doc-path1'
newPathname: '/new-doc-path1'
@docUpdates = [ @docUpdate0, @docUpdate1 ]
@fileUpdate0 =
id: DocUpdaterClient.randomId()
pathname: '/file-path0'
newPathname: '/new-file-path0'
@fileUpdate1 =
id: DocUpdaterClient.randomId()
pathname: '/file-path1'
newPathname: '/new-file-path1'
@fileUpdates = [ @fileUpdate0, @fileUpdate1 ]
describe "when the documents are not loaded", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, @fileUpdates, @version, (error) ->
throw error if error?
setTimeout done, 200
it "should push the applied doc renames to the project history api", (done) ->
rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
throw error if error?
update = JSON.parse(updates[0])
update.doc.should.equal @docUpdate0.id
update.pathname.should.equal '/doc-path0'
update.new_pathname.should.equal '/new-doc-path0'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.0"
update = JSON.parse(updates[1])
update.doc.should.equal @docUpdate1.id
update.pathname.should.equal '/doc-path1'
update.new_pathname.should.equal '/new-doc-path1'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.1"
update = JSON.parse(updates[2])
update.file.should.equal @fileUpdate0.id
update.pathname.should.equal '/file-path0'
update.new_pathname.should.equal '/new-file-path0'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.2"
update = JSON.parse(updates[3])
update.file.should.equal @fileUpdate1.id
update.pathname.should.equal '/file-path1'
update.new_pathname.should.equal '/new-file-path1'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.3"
done()
describe "adding a file", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
@fileUpdate =
id: DocUpdaterClient.randomId()
pathname: '/file-path'
url: 'filestore.example.com'
@fileUpdates = [ @fileUpdate ]
DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, @version, (error) ->
throw error if error?
setTimeout done, 200
it "should push the file addition to the project history api", (done) ->
rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
throw error if error?
update = JSON.parse(updates[0])
update.file.should.equal @fileUpdate.id
update.pathname.should.equal '/file-path'
update.url.should.equal 'filestore.example.com'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.0"
done()
describe "adding a doc", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
@docUpdate =
id: DocUpdaterClient.randomId()
pathname: '/file-path'
docLines: 'a\nb'
@docUpdates = [ @docUpdate ]
DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
throw error if error?
setTimeout done, 200
it "should push the doc addition to the project history api", (done) ->
rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
throw error if error?
update = JSON.parse(updates[0])
update.doc.should.equal @docUpdate.id
update.pathname.should.equal '/file-path'
update.docLines.should.equal 'a\nb'
update.meta.user_id.should.equal @user_id
update.meta.ts.should.be.a('string')
update.version.should.equal "#{@version}.0"
done()
describe "with enough updates to flush to the history service", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
@user_id = DocUpdaterClient.randomId()
@version0 = 12345
@version1 = @version0 + 1
updates = []
for v in [0..599] # Should flush after 500 ops
updates.push
id: DocUpdaterClient.randomId(),
pathname: '/file-' + v
docLines: 'a\nb'
sinon.spy MockProjectHistoryApi, "flushProject"
# Send updates in chunks to causes multiple flushes
projectId = @project_id
userId = @project_id
DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 250), [], @version0, (error) ->
throw error if error?
DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(250), [], @version1, (error) ->
throw error if error?
setTimeout done, 2000
after ->
MockProjectHistoryApi.flushProject.restore()
it "should flush project history", ->
MockProjectHistoryApi.flushProject.calledWith(@project_id).should.equal true
describe "with too few updates to flush to the history service", ->
before (done) ->
@project_id = DocUpdaterClient.randomId()
@user_id = DocUpdaterClient.randomId()
@version0 = 12345
@version1 = @version0 + 1
updates = []
for v in [0..42] # Should flush after 500 ops
updates.push
id: DocUpdaterClient.randomId(),
pathname: '/file-' + v
docLines: 'a\nb'
sinon.spy MockProjectHistoryApi, "flushProject"
# Send updates in chunks
projectId = @project_id
userId = @project_id
DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 10), [], @version0, (error) ->
throw error if error?
DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(10), [], @version1, (error) ->
throw error if error?
setTimeout done, 2000
after ->
MockProjectHistoryApi.flushProject.restore()
it "should not flush project history", ->
MockProjectHistoryApi.flushProject.calledWith(@project_id).should.equal false