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

530 lines
15 KiB
JavaScript
Raw Normal View History

/* eslint-disable
camelcase,
handle-callback-err,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* 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
*/
const sinon = require('sinon')
const chai = require('chai')
chai.should()
const Settings = require('settings-sharelatex')
const rclient_project_history = require('redis-sharelatex').createClient(
Settings.redis.project_history
)
const ProjectHistoryKeys = Settings.redis.project_history.key_schema
const MockProjectHistoryApi = require('./helpers/MockProjectHistoryApi')
const MockWebApi = require('./helpers/MockWebApi')
const DocUpdaterClient = require('./helpers/DocUpdaterClient')
const DocUpdaterApp = require('./helpers/DocUpdaterApp')
describe("Applying updates to a project's structure", function () {
before(function () {
this.user_id = 'user-id-123'
return (this.version = 1234)
})
describe('renaming a file', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
this.fileUpdate = {
id: DocUpdaterClient.randomId(),
pathname: '/file-path',
newPathname: '/new-file-path'
}
this.fileUpdates = [this.fileUpdate]
return DocUpdaterApp.ensureRunning((error) => {
if (error != null) {
throw error
}
return DocUpdaterClient.sendProjectUpdate(
this.project_id,
this.user_id,
[],
this.fileUpdates,
this.version,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 200)
}
)
})
})
return it('should push the applied file renames to the project history api', function (done) {
rclient_project_history.lrange(
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
0,
-1,
(error, updates) => {
if (error != null) {
throw error
}
const update = JSON.parse(updates[0])
update.file.should.equal(this.fileUpdate.id)
update.pathname.should.equal('/file-path')
update.new_pathname.should.equal('/new-file-path')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.0`)
return done()
}
)
return null
})
})
describe('renaming a document', function () {
before(function () {
this.docUpdate = {
id: DocUpdaterClient.randomId(),
pathname: '/doc-path',
newPathname: '/new-doc-path'
}
return (this.docUpdates = [this.docUpdate])
})
describe('when the document is not loaded', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
DocUpdaterClient.sendProjectUpdate(
this.project_id,
this.user_id,
this.docUpdates,
[],
this.version,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 200)
}
)
return null
})
return it('should push the applied doc renames to the project history api', function (done) {
rclient_project_history.lrange(
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
0,
-1,
(error, updates) => {
if (error != null) {
throw error
}
const update = JSON.parse(updates[0])
update.doc.should.equal(this.docUpdate.id)
update.pathname.should.equal('/doc-path')
update.new_pathname.should.equal('/new-doc-path')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.0`)
return done()
}
)
return null
})
})
return describe('when the document is loaded', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
MockWebApi.insertDoc(this.project_id, this.docUpdate.id, {})
DocUpdaterClient.preloadDoc(
this.project_id,
this.docUpdate.id,
(error) => {
if (error != null) {
throw error
}
sinon.spy(MockWebApi, 'getDocument')
return DocUpdaterClient.sendProjectUpdate(
this.project_id,
this.user_id,
this.docUpdates,
[],
this.version,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 200)
}
)
}
)
return null
})
after(function () {
return MockWebApi.getDocument.restore()
})
it('should update the doc', function (done) {
DocUpdaterClient.getDoc(
this.project_id,
this.docUpdate.id,
(error, res, doc) => {
doc.pathname.should.equal(this.docUpdate.newPathname)
return done()
}
)
return null
})
return it('should push the applied doc renames to the project history api', function (done) {
rclient_project_history.lrange(
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
0,
-1,
(error, updates) => {
if (error != null) {
throw error
}
const update = JSON.parse(updates[0])
update.doc.should.equal(this.docUpdate.id)
update.pathname.should.equal('/doc-path')
update.new_pathname.should.equal('/new-doc-path')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.0`)
return done()
}
)
return null
})
})
})
describe('renaming multiple documents and files', function () {
before(function () {
this.docUpdate0 = {
id: DocUpdaterClient.randomId(),
pathname: '/doc-path0',
newPathname: '/new-doc-path0'
}
this.docUpdate1 = {
id: DocUpdaterClient.randomId(),
pathname: '/doc-path1',
newPathname: '/new-doc-path1'
}
this.docUpdates = [this.docUpdate0, this.docUpdate1]
this.fileUpdate0 = {
id: DocUpdaterClient.randomId(),
pathname: '/file-path0',
newPathname: '/new-file-path0'
}
this.fileUpdate1 = {
id: DocUpdaterClient.randomId(),
pathname: '/file-path1',
newPathname: '/new-file-path1'
}
return (this.fileUpdates = [this.fileUpdate0, this.fileUpdate1])
})
return describe('when the documents are not loaded', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
DocUpdaterClient.sendProjectUpdate(
this.project_id,
this.user_id,
this.docUpdates,
this.fileUpdates,
this.version,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 200)
}
)
return null
})
return it('should push the applied doc renames to the project history api', function (done) {
rclient_project_history.lrange(
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
0,
-1,
(error, updates) => {
if (error != null) {
throw error
}
let update = JSON.parse(updates[0])
update.doc.should.equal(this.docUpdate0.id)
update.pathname.should.equal('/doc-path0')
update.new_pathname.should.equal('/new-doc-path0')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.0`)
update = JSON.parse(updates[1])
update.doc.should.equal(this.docUpdate1.id)
update.pathname.should.equal('/doc-path1')
update.new_pathname.should.equal('/new-doc-path1')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.1`)
update = JSON.parse(updates[2])
update.file.should.equal(this.fileUpdate0.id)
update.pathname.should.equal('/file-path0')
update.new_pathname.should.equal('/new-file-path0')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.2`)
update = JSON.parse(updates[3])
update.file.should.equal(this.fileUpdate1.id)
update.pathname.should.equal('/file-path1')
update.new_pathname.should.equal('/new-file-path1')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.3`)
return done()
}
)
return null
})
})
})
describe('adding a file', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
this.fileUpdate = {
id: DocUpdaterClient.randomId(),
pathname: '/file-path',
url: 'filestore.example.com'
}
this.fileUpdates = [this.fileUpdate]
DocUpdaterClient.sendProjectUpdate(
this.project_id,
this.user_id,
[],
this.fileUpdates,
this.version,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 200)
}
)
return null
})
return it('should push the file addition to the project history api', function (done) {
rclient_project_history.lrange(
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
0,
-1,
(error, updates) => {
if (error != null) {
throw error
}
const update = JSON.parse(updates[0])
update.file.should.equal(this.fileUpdate.id)
update.pathname.should.equal('/file-path')
update.url.should.equal('filestore.example.com')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.0`)
return done()
}
)
return null
})
})
describe('adding a doc', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
this.docUpdate = {
id: DocUpdaterClient.randomId(),
pathname: '/file-path',
docLines: 'a\nb'
}
this.docUpdates = [this.docUpdate]
DocUpdaterClient.sendProjectUpdate(
this.project_id,
this.user_id,
this.docUpdates,
[],
this.version,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 200)
}
)
return null
})
return it('should push the doc addition to the project history api', function (done) {
rclient_project_history.lrange(
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
0,
-1,
(error, updates) => {
if (error != null) {
throw error
}
const update = JSON.parse(updates[0])
update.doc.should.equal(this.docUpdate.id)
update.pathname.should.equal('/file-path')
update.docLines.should.equal('a\nb')
update.meta.user_id.should.equal(this.user_id)
update.meta.ts.should.be.a('string')
update.version.should.equal(`${this.version}.0`)
return done()
}
)
return null
})
})
describe('with enough updates to flush to the history service', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
this.user_id = DocUpdaterClient.randomId()
this.version0 = 12345
this.version1 = this.version0 + 1
const updates = []
for (let v = 0; v <= 599; v++) {
// 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
const projectId = this.project_id
const userId = this.project_id
DocUpdaterClient.sendProjectUpdate(
projectId,
userId,
updates.slice(0, 250),
[],
this.version0,
function (error) {
if (error != null) {
throw error
}
return DocUpdaterClient.sendProjectUpdate(
projectId,
userId,
updates.slice(250),
[],
this.version1,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 2000)
}
)
}
)
return null
})
after(function () {
return MockProjectHistoryApi.flushProject.restore()
})
return it('should flush project history', function () {
return MockProjectHistoryApi.flushProject
.calledWith(this.project_id)
.should.equal(true)
})
})
return describe('with too few updates to flush to the history service', function () {
before(function (done) {
this.project_id = DocUpdaterClient.randomId()
this.user_id = DocUpdaterClient.randomId()
this.version0 = 12345
this.version1 = this.version0 + 1
const updates = []
for (let v = 0; v <= 42; v++) {
// Should flush after 500 ops
updates.push({
id: DocUpdaterClient.randomId(),
pathname: '/file-' + v,
docLines: 'a\nb'
})
}
sinon.spy(MockProjectHistoryApi, 'flushProject')
// Send updates in chunks
const projectId = this.project_id
const userId = this.project_id
DocUpdaterClient.sendProjectUpdate(
projectId,
userId,
updates.slice(0, 10),
[],
this.version0,
function (error) {
if (error != null) {
throw error
}
return DocUpdaterClient.sendProjectUpdate(
projectId,
userId,
updates.slice(10),
[],
this.version1,
(error) => {
if (error != null) {
throw error
}
return setTimeout(done, 2000)
}
)
}
)
return null
})
after(function () {
return MockProjectHistoryApi.flushProject.restore()
})
return it('should not flush project history', function () {
return MockProjectHistoryApi.flushProject
.calledWith(this.project_id)
.should.equal(false)
})
})
})