/* 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: * DS101: Remove unnecessary use of Array.from * 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 { expect } = require('chai') const Settings = require('settings-sharelatex') const rclient_du = require('redis-sharelatex').createClient( Settings.redis.documentupdater ) const Keys = Settings.redis.documentupdater.key_schema const MockTrackChangesApi = require('./helpers/MockTrackChangesApi') const MockProjectHistoryApi = require('./helpers/MockProjectHistoryApi') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') describe('Setting a document', function () { before(function (done) { this.lines = ['one', 'two', 'three'] this.version = 42 this.update = { doc: this.doc_id, op: [ { i: 'one and a half\n', p: 4 } ], v: this.version } this.result = ['one', 'one and a half', 'two', 'three'] this.newLines = ['these', 'are', 'the', 'new', 'lines'] this.source = 'dropbox' this.user_id = 'user-id-123' sinon.spy(MockTrackChangesApi, 'flushDoc') sinon.spy(MockProjectHistoryApi, 'flushProject') sinon.spy(MockWebApi, 'setDocument') return DocUpdaterApp.ensureRunning(done) }) after(function () { MockTrackChangesApi.flushDoc.restore() MockProjectHistoryApi.flushProject.restore() return MockWebApi.setDocument.restore() }) describe('when the updated doc exists in the doc updater', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ DocUpdaterClient.randomId(), DocUpdaterClient.randomId() ]) MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version }) DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => { if (error != null) { throw error } return DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, this.update, (error) => { if (error != null) { throw error } return setTimeout(() => { return DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => { this.statusCode = res.statusCode return done() } ) }, 200) } ) }) return null }) after(function () { MockTrackChangesApi.flushDoc.reset() MockProjectHistoryApi.flushProject.reset() return MockWebApi.setDocument.reset() }) it('should return a 204 status code', function () { return this.statusCode.should.equal(204) }) it('should send the updated doc lines and version to the web api', function () { return MockWebApi.setDocument .calledWith(this.project_id, this.doc_id, this.newLines) .should.equal(true) }) it('should update the lines in the doc updater', function (done) { DocUpdaterClient.getDoc( this.project_id, this.doc_id, (error, res, doc) => { doc.lines.should.deep.equal(this.newLines) return done() } ) return null }) it('should bump the version in the doc updater', function (done) { DocUpdaterClient.getDoc( this.project_id, this.doc_id, (error, res, doc) => { doc.version.should.equal(this.version + 2) return done() } ) return null }) return it('should leave the document in redis', function (done) { rclient_du.get(Keys.docLines({ doc_id: this.doc_id }), (error, lines) => { if (error != null) { throw error } expect(JSON.parse(lines)).to.deep.equal(this.newLines) return done() }) return null }) }) describe('when the updated doc does not exist in the doc updater', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ DocUpdaterClient.randomId(), DocUpdaterClient.randomId() ]) MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version }) DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => { this.statusCode = res.statusCode return setTimeout(done, 200) } ) return null }) after(function () { MockTrackChangesApi.flushDoc.reset() MockProjectHistoryApi.flushProject.reset() return MockWebApi.setDocument.reset() }) it('should return a 204 status code', function () { return this.statusCode.should.equal(204) }) it('should send the updated doc lines to the web api', function () { return MockWebApi.setDocument .calledWith(this.project_id, this.doc_id, this.newLines) .should.equal(true) }) it('should flush track changes', function () { return MockTrackChangesApi.flushDoc .calledWith(this.doc_id) .should.equal(true) }) it('should flush project history', function () { return MockProjectHistoryApi.flushProject .calledWith(this.project_id) .should.equal(true) }) return it('should remove the document from redis', function (done) { rclient_du.get(Keys.docLines({ doc_id: this.doc_id }), (error, lines) => { if (error != null) { throw error } expect(lines).to.not.exist return done() }) return null }) }) describe('when the updated doc is too large for the body parser', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ DocUpdaterClient.randomId(), DocUpdaterClient.randomId() ]) MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version }) this.newLines = [] while ( JSON.stringify(this.newLines).length < Settings.max_doc_length + 64 * 1024 ) { this.newLines.push('(a long line of text)'.repeat(10000)) } DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => { this.statusCode = res.statusCode return setTimeout(done, 200) } ) return null }) after(function () { MockTrackChangesApi.flushDoc.reset() MockProjectHistoryApi.flushProject.reset() return MockWebApi.setDocument.reset() }) it('should return a 413 status code', function () { return this.statusCode.should.equal(413) }) it('should not send the updated doc lines to the web api', function () { return MockWebApi.setDocument.called.should.equal(false) }) it('should not flush track changes', function () { return MockTrackChangesApi.flushDoc.called.should.equal(false) }) return it('should not flush project history', function () { return MockProjectHistoryApi.flushProject.called.should.equal(false) }) }) describe('when the updated doc is large but under the bodyParser and HTTPController size limit', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ DocUpdaterClient.randomId(), DocUpdaterClient.randomId() ]) MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version }) this.newLines = [] while (JSON.stringify(this.newLines).length < 2 * 1024 * 1024) { // limit in HTTPController this.newLines.push('(a long line of text)'.repeat(10000)) } this.newLines.pop() // remove the line which took it over the limit DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, false, (error, res, body) => { this.statusCode = res.statusCode return setTimeout(done, 200) } ) return null }) after(function () { MockTrackChangesApi.flushDoc.reset() MockProjectHistoryApi.flushProject.reset() return MockWebApi.setDocument.reset() }) it('should return a 204 status code', function () { return this.statusCode.should.equal(204) }) return it('should send the updated doc lines to the web api', function () { return MockWebApi.setDocument .calledWith(this.project_id, this.doc_id, this.newLines) .should.equal(true) }) }) return describe('with track changes', function () { before(function () { this.lines = ['one', 'one and a half', 'two', 'three'] this.id_seed = '587357bd35e64f6157' return (this.update = { doc: this.doc_id, op: [ { d: 'one and a half\n', p: 4 } ], meta: { tc: this.id_seed, user_id: this.user_id }, v: this.version }) }) describe('with the undo flag', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ DocUpdaterClient.randomId(), DocUpdaterClient.randomId() ]) MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version }) DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => { if (error != null) { throw error } return DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, this.update, (error) => { if (error != null) { throw error } // Go back to old lines, with undo flag return DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.lines, this.source, this.user_id, true, (error, res, body) => { this.statusCode = res.statusCode return setTimeout(done, 200) } ) } ) }) return null }) after(function () { MockTrackChangesApi.flushDoc.reset() MockProjectHistoryApi.flushProject.reset() return MockWebApi.setDocument.reset() }) return it('should undo the tracked changes', function (done) { DocUpdaterClient.getDoc( this.project_id, this.doc_id, (error, res, data) => { if (error != null) { throw error } const { ranges } = data expect(ranges.changes).to.be.undefined return done() } ) return null }) }) return describe('without the undo flag', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ DocUpdaterClient.randomId(), DocUpdaterClient.randomId() ]) MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version }) DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, (error) => { if (error != null) { throw error } return DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, this.update, (error) => { if (error != null) { throw error } // Go back to old lines, without undo flag return DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.lines, this.source, this.user_id, false, (error, res, body) => { this.statusCode = res.statusCode return setTimeout(done, 200) } ) } ) }) return null }) after(function () { MockTrackChangesApi.flushDoc.reset() MockProjectHistoryApi.flushProject.reset() return MockWebApi.setDocument.reset() }) return it('should not undo the tracked changes', function (done) { DocUpdaterClient.getDoc( this.project_id, this.doc_id, (error, res, data) => { if (error != null) { throw error } const { ranges } = data expect(ranges.changes.length).to.equal(1) return done() } ) return null }) }) }) })