overleaf/services/track-changes/test/acceptance/js/AppendingUpdatesTests.js
2021-07-13 12:04:43 +01:00

587 lines
16 KiB
JavaScript

/* eslint-disable
handle-callback-err,
no-unused-vars,
*/
// 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 { expect } = require('chai')
const { ObjectId } = require('../../../app/js/mongodb')
const Settings = require('@overleaf/settings')
const request = require('request')
const rclient = require('redis').createClient(Settings.redis.history) // Only works locally for now
const TrackChangesApp = require('./helpers/TrackChangesApp')
const TrackChangesClient = require('./helpers/TrackChangesClient')
const MockWebApi = require('./helpers/MockWebApi')
describe('Appending doc ops to the history', function () {
before(function (done) {
return TrackChangesApp.ensureRunning(done)
})
describe('when the history does not exist yet', function () {
before(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: false } }
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [{ i: 'f', p: 3 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 3,
},
{
op: [{ i: 'o', p: 4 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 4,
},
{
op: [{ i: 'o', p: 5 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 5,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
it('should insert the compressed op into mongo', function () {
return expect(this.updates[0].pack[0].op).to.deep.equal([
{
p: 3,
i: 'foo',
},
])
})
it('should insert the correct version number into mongo', function () {
return expect(this.updates[0].v).to.equal(5)
})
it('should store the doc id', function () {
return expect(this.updates[0].doc_id.toString()).to.equal(this.doc_id)
})
it('should store the project id', function () {
return expect(this.updates[0].project_id.toString()).to.equal(
this.project_id
)
})
return it('should clear the doc from the DocsWithHistoryOps set', function (done) {
rclient.sismember(
`DocsWithHistoryOps:${this.project_id}`,
this.doc_id,
(error, member) => {
member.should.equal(0)
return done()
}
)
return null
})
})
describe('when the history has already been started', function () {
beforeEach(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: false } }
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [{ i: 'f', p: 3 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 3,
},
{
op: [{ i: 'o', p: 4 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 4,
},
{
op: [{ i: 'o', p: 5 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 5,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
describe('when the updates are recent and from the same user', function () {
beforeEach(function (done) {
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [{ i: 'b', p: 6 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 6,
},
{
op: [{ i: 'a', p: 7 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 7,
},
{
op: [{ i: 'r', p: 8 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 8,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
it('should combine all the updates into one pack', function () {
return expect(this.updates[0].pack[1].op).to.deep.equal([
{
p: 6,
i: 'bar',
},
])
})
return it('should insert the correct version number into mongo', function () {
return expect(this.updates[0].v_end).to.equal(8)
})
})
return describe('when the updates are far apart', function () {
beforeEach(function (done) {
const oneDay = 24 * 60 * 60 * 1000
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [{ i: 'b', p: 6 }],
meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 6,
},
{
op: [{ i: 'a', p: 7 }],
meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 7,
},
{
op: [{ i: 'r', p: 8 }],
meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 8,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
return it('should combine the updates into one pack', function () {
expect(this.updates[0].pack[0].op).to.deep.equal([
{
p: 3,
i: 'foo',
},
])
return expect(this.updates[0].pack[1].op).to.deep.equal([
{
p: 6,
i: 'bar',
},
])
})
})
})
describe('when the updates need processing in batches', function () {
before(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: false } }
const updates = []
this.expectedOp = [{ p: 0, i: '' }]
for (let i = 0; i <= 250; i++) {
updates.push({
op: [{ i: 'a', p: 0 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: i,
})
this.expectedOp[0].i = `a${this.expectedOp[0].i}`
}
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
updates,
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates1) => {
this.updates = updates1
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
it('should concat the compressed op into mongo', function () {
return expect(this.updates[0].pack.length).to.deep.equal(3)
}) // batch size is 100
return it('should insert the correct version number into mongo', function () {
return expect(this.updates[0].v_end).to.equal(250)
})
})
describe('when there are multiple ops in each update', function () {
before(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: false } }
const oneDay = 24 * 60 * 60 * 1000
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [
{ i: 'f', p: 3 },
{ i: 'o', p: 4 },
{ i: 'o', p: 5 },
],
meta: { ts: Date.now(), user_id: this.user_id },
v: 3,
},
{
op: [
{ i: 'b', p: 6 },
{ i: 'a', p: 7 },
{ i: 'r', p: 8 },
],
meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 4,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
it('should insert the compressed ops into mongo', function () {
expect(this.updates[0].pack[0].op).to.deep.equal([
{
p: 3,
i: 'foo',
},
])
return expect(this.updates[0].pack[1].op).to.deep.equal([
{
p: 6,
i: 'bar',
},
])
})
return it('should insert the correct version numbers into mongo', function () {
expect(this.updates[0].pack[0].v).to.equal(3)
return expect(this.updates[0].pack[1].v).to.equal(4)
})
})
describe('when there is a no-op update', function () {
before(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: false } }
const oneDay = 24 * 60 * 60 * 1000
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [],
meta: { ts: Date.now(), user_id: this.user_id },
v: 3,
},
{
op: [{ i: 'foo', p: 3 }],
meta: { ts: Date.now() + oneDay, user_id: this.user_id },
v: 4,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
it('should insert the compressed no-op into mongo', function () {
return expect(this.updates[0].pack[0].op).to.deep.equal([])
})
it('should insert the compressed next update into mongo', function () {
return expect(this.updates[0].pack[1].op).to.deep.equal([
{
p: 3,
i: 'foo',
},
])
})
return it('should insert the correct version numbers into mongo', function () {
expect(this.updates[0].pack[0].v).to.equal(3)
return expect(this.updates[0].pack[1].v).to.equal(4)
})
})
describe('when there is a comment update', function () {
before(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: false } }
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [
{ c: 'foo', p: 3 },
{ d: 'bar', p: 6 },
],
meta: { ts: Date.now(), user_id: this.user_id },
v: 3,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
it('should ignore the comment op', function () {
return expect(this.updates[0].pack[0].op).to.deep.equal([
{ d: 'bar', p: 6 },
])
})
return it('should insert the correct version numbers into mongo', function () {
return expect(this.updates[0].pack[0].v).to.equal(3)
})
})
describe('when the project has versioning enabled', function () {
before(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: true } }
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [{ i: 'f', p: 3 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 3,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
return it('should not add a expiresAt entry in the update in mongo', function () {
return expect(this.updates[0].expiresAt).to.be.undefined
})
})
return describe('when the project does not have versioning enabled', function () {
before(function (done) {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.user_id = ObjectId().toString()
MockWebApi.projects[this.project_id] = { features: { versioning: false } }
TrackChangesClient.pushRawUpdates(
this.project_id,
this.doc_id,
[
{
op: [{ i: 'f', p: 3 }],
meta: { ts: Date.now(), user_id: this.user_id },
v: 3,
},
],
error => {
if (error != null) {
throw error
}
return TrackChangesClient.flushAndGetCompressedUpdates(
this.project_id,
this.doc_id,
(error, updates) => {
this.updates = updates
if (error != null) {
throw error
}
return done()
}
)
}
)
return null
})
return it('should add a expiresAt entry in the update in mongo', function () {
return expect(this.updates[0].expiresAt).to.exist
})
})
})