/* 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: * 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 } = chai const async = require('async') const { db, ObjectId } = require('../../../app/js/mongodb') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') describe('Ranges', function () { before(function (done) { return DocUpdaterApp.ensureRunning(done) }) describe('tracking changes from ops', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.id_seed = '587357bd35e64f6157' this.doc = { id: DocUpdaterClient.randomId(), lines: ['aaa'] } this.updates = [ { doc: this.doc.id, op: [{ i: '123', p: 1 }], v: 0, meta: { user_id: this.user_id } }, { doc: this.doc.id, op: [{ i: '456', p: 5 }], v: 1, meta: { user_id: this.user_id, tc: this.id_seed } }, { doc: this.doc.id, op: [{ d: '12', p: 1 }], v: 2, meta: { user_id: this.user_id } } ] MockWebApi.insertDoc(this.project_id, this.doc.id, { lines: this.doc.lines, version: 0 }) const jobs = [] for (const update of Array.from(this.updates)) { ;((update) => { return jobs.push((callback) => DocUpdaterClient.sendUpdate( this.project_id, this.doc.id, update, callback ) ) })(update) } return DocUpdaterApp.ensureRunning((error) => { if (error != null) { throw error } return DocUpdaterClient.preloadDoc( this.project_id, this.doc.id, (error) => { if (error != null) { throw error } return async.series(jobs, (error) => { if (error != null) { throw error } return done() }) } ) }) }) it('should update the ranges', function (done) { return DocUpdaterClient.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } const { ranges } = data const change = ranges.changes[0] change.op.should.deep.equal({ i: '456', p: 3 }) change.id.should.equal(this.id_seed + '000001') change.metadata.user_id.should.equal(this.user_id) return done() } ) }) return describe('Adding comments', function () { describe('standalone', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc = { id: DocUpdaterClient.randomId(), lines: ['foo bar baz'] } this.updates = [ { doc: this.doc.id, op: [ { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) } ], v: 0 } ] MockWebApi.insertDoc(this.project_id, this.doc.id, { lines: this.doc.lines, version: 0 }) const jobs = [] for (const update of Array.from(this.updates)) { ;((update) => { return jobs.push((callback) => DocUpdaterClient.sendUpdate( this.project_id, this.doc.id, update, callback ) ) })(update) } return DocUpdaterClient.preloadDoc( this.project_id, this.doc.id, (error) => { if (error != null) { throw error } return async.series(jobs, (error) => { if (error != null) { throw error } return setTimeout(done, 200) }) } ) }) return it('should update the ranges', function (done) { return DocUpdaterClient.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } const { ranges } = data const comment = ranges.comments[0] comment.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid }) comment.id.should.equal(this.tid) return done() } ) }) }) return describe('with conflicting ops needing OT', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc = { id: DocUpdaterClient.randomId(), lines: ['foo bar baz'] } this.updates = [ { doc: this.doc.id, op: [{ i: 'ABC', p: 3 }], v: 0, meta: { user_id: this.user_id } }, { doc: this.doc.id, op: [ { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) } ], v: 0 } ] MockWebApi.insertDoc(this.project_id, this.doc.id, { lines: this.doc.lines, version: 0 }) const jobs = [] for (const update of Array.from(this.updates)) { ;((update) => { return jobs.push((callback) => DocUpdaterClient.sendUpdate( this.project_id, this.doc.id, update, callback ) ) })(update) } return DocUpdaterClient.preloadDoc( this.project_id, this.doc.id, (error) => { if (error != null) { throw error } return async.series(jobs, (error) => { if (error != null) { throw error } return setTimeout(done, 200) }) } ) }) return it('should update the comments with the OT shifted comment', function (done) { return DocUpdaterClient.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } const { ranges } = data const comment = ranges.comments[0] comment.op.should.deep.equal({ c: 'bar', p: 7, t: this.tid }) return done() } ) }) }) }) }) describe('Loading ranges from persistence layer', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.id_seed = '587357bd35e64f6157' this.doc = { id: DocUpdaterClient.randomId(), lines: ['a123aa'] } this.update = { doc: this.doc.id, op: [{ i: '456', p: 5 }], v: 0, meta: { user_id: this.user_id, tc: this.id_seed } } MockWebApi.insertDoc(this.project_id, this.doc.id, { lines: this.doc.lines, version: 0, ranges: { changes: [ { op: { i: '123', p: 1 }, metadata: { user_id: this.user_id, ts: new Date() } } ] } }) return 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(done, 200) } ) } ) }) it('should have preloaded the existing ranges', function (done) { return DocUpdaterClient.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } const { changes } = data.ranges changes[0].op.should.deep.equal({ i: '123', p: 1 }) changes[1].op.should.deep.equal({ i: '456', p: 5 }) return done() } ) }) return it('should flush the ranges to the persistence layer again', function (done) { return DocUpdaterClient.flushDoc( this.project_id, this.doc.id, (error) => { if (error != null) { throw error } return MockWebApi.getDocument( this.project_id, this.doc.id, (error, doc) => { const { changes } = doc.ranges changes[0].op.should.deep.equal({ i: '123', p: 1 }) changes[1].op.should.deep.equal({ i: '456', p: 5 }) return done() } ) } ) }) }) describe('accepting a change', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.id_seed = '587357bd35e64f6157' this.doc = { id: DocUpdaterClient.randomId(), lines: ['aaa'] } this.update = { doc: this.doc.id, op: [{ i: '456', p: 1 }], v: 0, meta: { user_id: this.user_id, tc: this.id_seed } } MockWebApi.insertDoc(this.project_id, this.doc.id, { lines: this.doc.lines, version: 0 }) return 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.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } const { ranges } = data const change = ranges.changes[0] change.op.should.deep.equal({ i: '456', p: 1 }) change.id.should.equal(this.id_seed + '000001') change.metadata.user_id.should.equal(this.user_id) return done() } ) }, 200) } ) } ) }) return it('should remove the change after accepting', function (done) { return DocUpdaterClient.acceptChange( this.project_id, this.doc.id, this.id_seed + '000001', (error) => { if (error != null) { throw error } return DocUpdaterClient.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } expect(data.ranges.changes).to.be.undefined return done() } ) } ) }) }) describe('deleting a comment range', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc = { id: DocUpdaterClient.randomId(), lines: ['foo bar'] } this.update = { doc: this.doc.id, op: [{ c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) }], v: 0 } MockWebApi.insertDoc(this.project_id, this.doc.id, { lines: this.doc.lines, version: 0 }) return 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.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } const { ranges } = data const change = ranges.comments[0] change.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid }) change.id.should.equal(this.tid) return done() } ) }, 200) } ) } ) }) return it('should remove the comment range', function (done) { return DocUpdaterClient.removeComment( this.project_id, this.doc.id, this.tid, (error, res) => { if (error != null) { throw error } expect(res.statusCode).to.equal(204) return DocUpdaterClient.getDoc( this.project_id, this.doc.id, (error, res, data) => { if (error != null) { throw error } expect(data.ranges.comments).to.be.undefined return done() } ) } ) }) }) describe('tripping range size limit', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.id_seed = DocUpdaterClient.randomId() this.doc = { id: DocUpdaterClient.randomId(), lines: ['aaa'] } this.i = new Array(3 * 1024 * 1024).join('a') this.updates = [ { doc: this.doc.id, op: [{ i: this.i, p: 1 }], v: 0, meta: { user_id: this.user_id, tc: this.id_seed } } ] MockWebApi.insertDoc(this.project_id, this.doc.id, { lines: this.doc.lines, version: 0 }) const jobs = [] for (const update of Array.from(this.updates)) { ;((update) => { return jobs.push((callback) => DocUpdaterClient.sendUpdate( this.project_id, this.doc.id, update, callback ) ) })(update) } return DocUpdaterClient.preloadDoc( this.project_id, this.doc.id, (error) => { if (error != null) { throw error } return async.series(jobs, (error) => { if (error != null) { throw error } return setTimeout(done, 200) }) } ) }) return it('should not update the ranges', function (done) { return 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 describe('deleting text surrounding a comment', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: ['foo bar baz'], version: 0, ranges: { comments: [ { op: { c: 'a', p: 5, tid: (this.tid = DocUpdaterClient.randomId()) }, metadata: { user_id: this.user_id, ts: new Date() } } ] } }) this.updates = [ { doc: this.doc_id, op: [{ d: 'foo ', p: 0 }], v: 0, meta: { user_id: this.user_id } }, { doc: this.doc_id, op: [{ d: 'bar ', p: 0 }], v: 1, meta: { user_id: this.user_id } } ] const jobs = [] for (const update of Array.from(this.updates)) { ;((update) => { return jobs.push((callback) => DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, update, callback ) ) })(update) } return DocUpdaterClient.preloadDoc( this.project_id, this.doc_id, (error) => { if (error != null) { throw error } return async.series(jobs, function (error) { if (error != null) { throw error } return setTimeout(() => { return DocUpdaterClient.getDoc( this.project_id, this.doc_id, (error, res, data) => { if (error != null) { throw error } return done() } ) }, 200) }) } ) }) return it('should write a snapshot from before the destructive change', function (done) { return DocUpdaterClient.getDoc( this.project_id, this.doc_id, (error, res, data) => { if (error != null) { return done(error) } db.docSnapshots .find({ project_id: ObjectId(this.project_id), doc_id: ObjectId(this.doc_id) }) .toArray((error, docSnapshots) => { if (error != null) { return done(error) } expect(docSnapshots.length).to.equal(1) expect(docSnapshots[0].version).to.equal(1) expect(docSnapshots[0].lines).to.deep.equal(['bar baz']) expect(docSnapshots[0].ranges.comments[0].op).to.deep.equal({ c: 'a', p: 1, tid: this.tid }) return done() }) } ) }) }) })