overleaf/services/document-updater/test/unit/js/HistoryManager/HistoryManagerTests.js
Eric Mc Sween 4d70bd664f Reintroduce Node 12 and metrics upgrades
These changes were previously merged, not deployed, and reverted. This
reverts the revert.

This reverts commit a6b8c6c658b33b6eee78b8b99e43308f32211ae2, reversing
changes made to 93c98921372eed4244d22fce800716cb27eca299.
2021-04-01 15:51:00 -04:00

419 lines
13 KiB
JavaScript

/* eslint-disable
mocha/no-nested-tests,
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
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const modulePath = require('path').join(
__dirname,
'../../../../app/js/HistoryManager'
)
describe('HistoryManager', function () {
beforeEach(function () {
this.HistoryManager = SandboxedModule.require(modulePath, {
requires: {
request: (this.request = {}),
'settings-sharelatex': (this.Settings = {
apis: {
project_history: {
enabled: true,
url: 'http://project_history.example.com'
},
trackchanges: {
url: 'http://trackchanges.example.com'
}
}
}),
'./DocumentManager': (this.DocumentManager = {}),
'./HistoryRedisManager': (this.HistoryRedisManager = {}),
'./RedisManager': (this.RedisManager = {}),
'./ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
'./Metrics': (this.metrics = { inc: sinon.stub() })
}
})
this.project_id = 'mock-project-id'
this.doc_id = 'mock-doc-id'
return (this.callback = sinon.stub())
})
describe('flushDocChangesAsync', function () {
beforeEach(function () {
return (this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }))
})
describe('when the project uses track changes', function () {
beforeEach(function () {
this.RedisManager.getHistoryType = sinon
.stub()
.yields(null, 'track-changes')
return this.HistoryManager.flushDocChangesAsync(
this.project_id,
this.doc_id
)
})
return it('should send a request to the track changes api', function () {
return this.request.post
.calledWith(
`${this.Settings.apis.trackchanges.url}/project/${this.project_id}/doc/${this.doc_id}/flush`
)
.should.equal(true)
})
})
describe('when the project uses project history and double flush is not disabled', function () {
beforeEach(function () {
this.RedisManager.getHistoryType = sinon
.stub()
.yields(null, 'project-history')
return this.HistoryManager.flushDocChangesAsync(
this.project_id,
this.doc_id
)
})
return it('should send a request to the track changes api', function () {
return this.request.post.called.should.equal(true)
})
})
return describe('when the project uses project history and double flush is disabled', function () {
beforeEach(function () {
this.Settings.disableDoubleFlush = true
this.RedisManager.getHistoryType = sinon
.stub()
.yields(null, 'project-history')
return this.HistoryManager.flushDocChangesAsync(
this.project_id,
this.doc_id
)
})
return it('should not send a request to the track changes api', function () {
return this.request.post.called.should.equal(false)
})
})
})
describe('flushProjectChangesAsync', function () {
beforeEach(function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
return this.HistoryManager.flushProjectChangesAsync(this.project_id)
})
return it('should send a request to the project history api', function () {
return this.request.post
.calledWith({
url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`,
qs: { background: true }
})
.should.equal(true)
})
})
describe('flushProjectChanges', function () {
describe('in the normal case', function () {
beforeEach(function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
return this.HistoryManager.flushProjectChanges(this.project_id, {
background: true
})
})
return it('should send a request to the project history api', function () {
return this.request.post
.calledWith({
url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`,
qs: { background: true }
})
.should.equal(true)
})
})
return describe('with the skip_history_flush option', function () {
beforeEach(function () {
this.request.post = sinon.stub()
return this.HistoryManager.flushProjectChanges(this.project_id, {
skip_history_flush: true
})
})
return it('should not send a request to the project history api', function () {
return this.request.post.called.should.equal(false)
})
})
})
describe('recordAndFlushHistoryOps', function () {
beforeEach(function () {
this.ops = ['mock-ops']
this.project_ops_length = 10
this.doc_ops_length = 5
this.HistoryManager.flushProjectChangesAsync = sinon.stub()
this.HistoryRedisManager.recordDocHasHistoryOps = sinon.stub().callsArg(3)
return (this.HistoryManager.flushDocChangesAsync = sinon.stub())
})
describe('with no ops', function () {
beforeEach(function () {
return this.HistoryManager.recordAndFlushHistoryOps(
this.project_id,
this.doc_id,
[],
this.doc_ops_length,
this.project_ops_length,
this.callback
)
})
it('should not flush project changes', function () {
return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
false
)
})
it('should not record doc has history ops', function () {
return this.HistoryRedisManager.recordDocHasHistoryOps.called.should.equal(
false
)
})
it('should not flush doc changes', function () {
return this.HistoryManager.flushDocChangesAsync.called.should.equal(
false
)
})
return it('should call the callback', function () {
return this.callback.called.should.equal(true)
})
})
describe('with enough ops to flush project changes', function () {
beforeEach(function () {
this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
this.HistoryManager.shouldFlushHistoryOps
.withArgs(this.project_ops_length)
.returns(true)
this.HistoryManager.shouldFlushHistoryOps
.withArgs(this.doc_ops_length)
.returns(false)
return this.HistoryManager.recordAndFlushHistoryOps(
this.project_id,
this.doc_id,
this.ops,
this.doc_ops_length,
this.project_ops_length,
this.callback
)
})
it('should flush project changes', function () {
return this.HistoryManager.flushProjectChangesAsync
.calledWith(this.project_id)
.should.equal(true)
})
it('should record doc has history ops', function () {
return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
this.project_id,
this.doc_id,
this.ops
)
})
it('should not flush doc changes', function () {
return this.HistoryManager.flushDocChangesAsync.called.should.equal(
false
)
})
return it('should call the callback', function () {
return this.callback.called.should.equal(true)
})
})
describe('with enough ops to flush doc changes', function () {
beforeEach(function () {
this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
this.HistoryManager.shouldFlushHistoryOps
.withArgs(this.project_ops_length)
.returns(false)
this.HistoryManager.shouldFlushHistoryOps
.withArgs(this.doc_ops_length)
.returns(true)
return this.HistoryManager.recordAndFlushHistoryOps(
this.project_id,
this.doc_id,
this.ops,
this.doc_ops_length,
this.project_ops_length,
this.callback
)
})
it('should not flush project changes', function () {
return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
false
)
})
it('should record doc has history ops', function () {
return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
this.project_id,
this.doc_id,
this.ops
)
})
it('should flush doc changes', function () {
return this.HistoryManager.flushDocChangesAsync
.calledWith(this.project_id, this.doc_id)
.should.equal(true)
})
return it('should call the callback', function () {
return this.callback.called.should.equal(true)
})
})
describe('when recording doc has history ops errors', function () {
beforeEach(function () {
this.error = new Error('error')
this.HistoryRedisManager.recordDocHasHistoryOps = sinon
.stub()
.callsArgWith(3, this.error)
return this.HistoryManager.recordAndFlushHistoryOps(
this.project_id,
this.doc_id,
this.ops,
this.doc_ops_length,
this.project_ops_length,
this.callback
)
})
it('should not flush doc changes', function () {
return this.HistoryManager.flushDocChangesAsync.called.should.equal(
false
)
})
return it('should call the callback with the error', function () {
return this.callback.calledWith(this.error).should.equal(true)
})
})
return describe('shouldFlushHistoryOps', function () {
it('should return false if the number of ops is not known', function () {
return this.HistoryManager.shouldFlushHistoryOps(
null,
['a', 'b', 'c'].length,
1
).should.equal(false)
})
it("should return false if the updates didn't take us past the threshold", function () {
// Currently there are 14 ops
// Previously we were on 11 ops
// We didn't pass over a multiple of 5
this.HistoryManager.shouldFlushHistoryOps(
14,
['a', 'b', 'c'].length,
5
).should.equal(false)
it('should return true if the updates took to the threshold', function () {})
// Currently there are 15 ops
// Previously we were on 12 ops
// We've reached a new multiple of 5
return this.HistoryManager.shouldFlushHistoryOps(
15,
['a', 'b', 'c'].length,
5
).should.equal(true)
})
return it('should return true if the updates took past the threshold', function () {
// Currently there are 19 ops
// Previously we were on 16 ops
// We didn't pass over a multiple of 5
return this.HistoryManager.shouldFlushHistoryOps(
17,
['a', 'b', 'c'].length,
5
).should.equal(true)
})
})
})
return describe('resyncProjectHistory', function () {
beforeEach(function () {
this.projectHistoryId = 'history-id-1234'
this.docs = [
{
doc: this.doc_id,
path: 'main.tex'
}
]
this.files = [
{
file: 'mock-file-id',
path: 'universe.png',
url: `www.filestore.test/${this.project_id}/mock-file-id`
}
]
this.ProjectHistoryRedisManager.queueResyncProjectStructure = sinon
.stub()
.yields()
this.DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
return this.HistoryManager.resyncProjectHistory(
this.project_id,
this.projectHistoryId,
this.docs,
this.files,
this.callback
)
})
it('should queue a project structure reync', function () {
return this.ProjectHistoryRedisManager.queueResyncProjectStructure
.calledWith(
this.project_id,
this.projectHistoryId,
this.docs,
this.files
)
.should.equal(true)
})
it('should queue doc content reyncs', function () {
return this.DocumentManager.resyncDocContentsWithLock
.calledWith(this.project_id, this.doc_id)
.should.equal(true)
})
return it('should call the callback', function () {
return this.callback.called.should.equal(true)
})
})
})