From c104aa454eb9f2f0aeb843d11c9291fd8f4f059c Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:34:24 +0100 Subject: [PATCH] Merge pull request #28845 from overleaf/td-async-await-doc-updater-client Convert DocUpdateClient in document-updater acceptance tests to async/await GitOrigin-RevId: 8f2352119f8f1175c2703ed90dbbc483ed039e86 --- package-lock.json | 1 + .../document-updater/app/js/HttpController.js | 6 +- .../document-updater/app/js/RedisManager.js | 4 +- services/document-updater/package.json | 1 + .../js/ApplyingUpdatesToADocTests.js | 938 +++++------------- .../ApplyingUpdatesToProjectStructureTests.js | 251 ++--- .../js/CheckRedisMongoSyncStateTests.js | 24 +- .../acceptance/js/DeletingADocumentTests.js | 109 +- .../acceptance/js/DeletingAProjectTests.js | 264 ++--- .../acceptance/js/FlushingAProjectTests.js | 107 +- .../test/acceptance/js/FlushingDocsTests.js | 104 +- .../acceptance/js/GettingADocumentTests.js | 223 ++--- .../acceptance/js/GettingProjectDocsTests.js | 187 +--- .../test/acceptance/js/PeekingADoc.js | 68 +- .../test/acceptance/js/RangesTests.js | 834 +++++----------- .../acceptance/js/RejectingChangesTests.js | 237 ++--- .../acceptance/js/SettingADocumentTests.js | 512 +++------- .../test/acceptance/js/SizeCheckTests.js | 123 +-- .../acceptance/js/helpers/DocUpdaterApp.js | 56 +- .../acceptance/js/helpers/DocUpdaterClient.js | 348 +++---- .../document-updater/test/stress/js/run.js | 387 -------- 21 files changed, 1359 insertions(+), 3425 deletions(-) delete mode 100644 services/document-updater/test/stress/js/run.js diff --git a/package-lock.json b/package-lock.json index 804ffbc07d..c2f676e2dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51098,6 +51098,7 @@ "services/document-updater": { "name": "@overleaf/document-updater", "dependencies": { + "@overleaf/fetch-utils": "*", "@overleaf/logger": "*", "@overleaf/metrics": "*", "@overleaf/mongo-utils": "*", diff --git a/services/document-updater/app/js/HttpController.js b/services/document-updater/app/js/HttpController.js index 497c0e5c35..c45876ed68 100644 --- a/services/document-updater/app/js/HttpController.js +++ b/services/document-updater/app/js/HttpController.js @@ -226,7 +226,11 @@ async function setDoc(req, res) { ) timer.done() logger.debug({ projectId, docId }, 'set doc via http') - res.json(result) + + // If the document is unchanged and hasn't been updated, `result` will be + // undefined, which leads to an invalid JSON response, so we send an empty + // object instead. + res.json(result || {}) } async function appendToDoc(req, res) { diff --git a/services/document-updater/app/js/RedisManager.js b/services/document-updater/app/js/RedisManager.js index e4eedf8fdd..1dc20fdf38 100644 --- a/services/document-updater/app/js/RedisManager.js +++ b/services/document-updater/app/js/RedisManager.js @@ -542,7 +542,7 @@ const RedisManager = { ) // return if no projects ready to be processed if (!projectsReady || projectsReady.length === 0) { - return + return {} } // pop the oldest entry (get and remove in a multi) const multi = rclient.multi() @@ -552,7 +552,7 @@ const RedisManager = { multi.zcard(keys.flushAndDeleteQueue()) // the total length of the queue (for metrics) const reply = await multi.exec() if (!reply || reply.length === 0) { - return + return {} } const [key, timestamp] = reply[0] const queueLength = reply[2] diff --git a/services/document-updater/package.json b/services/document-updater/package.json index 69cbdec526..bc2c1c6580 100644 --- a/services/document-updater/package.json +++ b/services/document-updater/package.json @@ -18,6 +18,7 @@ "types:check": "tsc --noEmit" }, "dependencies": { + "@overleaf/fetch-utils": "*", "@overleaf/logger": "*", "@overleaf/metrics": "*", "@overleaf/mongo-utils": "*", diff --git a/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js b/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js index 39ec6c2ac7..855c4f937e 100644 --- a/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js +++ b/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js @@ -1,6 +1,6 @@ const sinon = require('sinon') const { expect } = require('chai') -const async = require('async') +const { setTimeout } = require('node:timers/promises') const Settings = require('@overleaf/settings') const rclientProjectHistory = require('@overleaf/redis-wrapper').createClient( Settings.redis.project_history @@ -14,9 +14,17 @@ const ProjectHistoryKeys = Settings.redis.project_history.key_schema const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const { RequestFailedError } = require('@overleaf/fetch-utils') + +async function sendUpdateAndWait(projectId, docId, update) { + await DocUpdaterClient.sendUpdate(projectId, docId, update) + + // It seems that we need to wait for a little while + await setTimeout(200) +} describe('Applying updates to a doc', function () { - beforeEach(function (done) { + beforeEach(async function () { sinon.spy(MockWebApi, 'getDocument') this.lines = ['one', 'two', 'three'] this.version = 42 @@ -38,44 +46,27 @@ describe('Applying updates to a doc', function () { meta: { source: 'random-publicId' }, } this.result = ['one', 'one and a half', 'two', 'three'] - DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) + afterEach(function () { sinon.restore() }) describe('when the document is not loaded', function () { - beforeEach(function (done) { + beforeEach(async function () { this.startTime = Date.now() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, }) - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - rclientProjectHistory.get( - ProjectHistoryKeys.projectHistoryFirstOpTimestamp({ - project_id: this.project_id, - }), - (error, result) => { - if (error != null) { - throw error - } - result = parseInt(result, 10) - this.firstOpTimestamp = result - done() - } - ) - }, 200) - } + await sendUpdateAndWait(this.project_id, this.doc_id, this.update) + const result = await rclientProjectHistory.get( + ProjectHistoryKeys.projectHistoryFirstOpTimestamp({ + project_id: this.project_id, + }) ) + this.firstOpTimestamp = parseInt(result, 10) }) it('should load the document from the web API', function () { @@ -84,16 +75,9 @@ describe('Applying updates to a doc', function () { .should.equal(true) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) done(error) - doc.lines.should.deep.equal(this.result) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) }) it('should push the applied updates to the project history changes api', function (done) { @@ -115,69 +99,42 @@ describe('Applying updates to a doc', function () { this.firstOpTimestamp.should.be.within(this.startTime, Date.now()) }) - it('should yield last updated time', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - this.project_id, - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.lastUpdatedAt.should.be.within(this.startTime, Date.now()) - done() - } + it('should yield last updated time', async function () { + const { lastUpdatedAt } = await DocUpdaterClient.getProjectLastUpdatedAt( + this.project_id ) + lastUpdatedAt.should.be.within(this.startTime, Date.now()) }) - it('should yield no last updated time for another project', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - DocUpdaterClient.randomId(), - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.should.deep.equal({}) - done() - } + it('should yield no last updated time for another project', async function () { + const body = await DocUpdaterClient.getProjectLastUpdatedAt( + DocUpdaterClient.randomId() ) + body.should.deep.equal({}) }) describe('when sending another update', function () { - beforeEach(function (done) { + beforeEach(async function () { this.timeout(10000) this.second_update = Object.assign({}, this.update) this.second_update.v = this.version + 1 this.secondStartTime = Date.now() - DocUpdaterClient.sendUpdate( + await sendUpdateAndWait( this.project_id, this.doc_id, - this.second_update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } + this.second_update ) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) done(error) - doc.lines.should.deep.equal([ - 'one', - 'one and a half', - 'one and a half', - 'two', - 'three', - ]) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal([ + 'one', + 'one and a half', + 'one and a half', + 'two', + 'three', + ]) }) it('should not change the first op timestamp', function (done) { @@ -196,26 +153,15 @@ describe('Applying updates to a doc', function () { ) }) - it('should yield last updated time', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - this.project_id, - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.lastUpdatedAt.should.be.within( - this.secondStartTime, - Date.now() - ) - done() - } - ) + it('should yield last updated time', async function () { + const { lastUpdatedAt } = + await DocUpdaterClient.getProjectLastUpdatedAt(this.project_id) + lastUpdatedAt.should.be.within(this.secondStartTime, Date.now()) }) }) describe('when another client is sending a concurrent update', function () { - beforeEach(function (done) { + beforeEach(async function () { this.timeout(10000) this.otherUpdate = { doc: this.doc_id, @@ -224,35 +170,18 @@ describe('Applying updates to a doc', function () { meta: { source: 'other-random-publicId' }, } this.secondStartTime = Date.now() - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.otherUpdate, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await sendUpdateAndWait(this.project_id, this.doc_id, this.otherUpdate) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) done(error) - doc.lines.should.deep.equal([ - 'one', - 'one and a half', - 'two', - 'two and a half', - 'three', - ]) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal([ + 'one', + 'one and a half', + 'two', + 'two and a half', + 'three', + ]) }) it('should not change the first op timestamp', function (done) { @@ -271,58 +200,33 @@ describe('Applying updates to a doc', function () { ) }) - it('should yield last updated time', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - this.project_id, - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.lastUpdatedAt.should.be.within( - this.secondStartTime, - Date.now() - ) - done() - } - ) + it('should yield last updated time', async function () { + const { lastUpdatedAt } = + await DocUpdaterClient.getProjectLastUpdatedAt(this.project_id) + lastUpdatedAt.should.be.within(this.secondStartTime, Date.now()) }) }) }) describe('when the document is not loaded (history-ot)', function () { - beforeEach(function (done) { + beforeEach(async function () { this.startTime = Date.now() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, otMigrationStage: 1, }) - DocUpdaterClient.sendUpdate( + await sendUpdateAndWait( this.project_id, this.doc_id, - this.historyOTUpdate, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - rclientProjectHistory.get( - ProjectHistoryKeys.projectHistoryFirstOpTimestamp({ - project_id: this.project_id, - }), - (error, result) => { - if (error != null) { - throw error - } - result = parseInt(result, 10) - this.firstOpTimestamp = result - done() - } - ) - }, 200) - } + this.historyOTUpdate ) + const result = await rclientProjectHistory.get( + ProjectHistoryKeys.projectHistoryFirstOpTimestamp({ + project_id: this.project_id, + }) + ) + this.firstOpTimestamp = parseInt(result, 10) }) it('should load the document from the web API', function () { @@ -331,16 +235,9 @@ describe('Applying updates to a doc', function () { .should.equal(true) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) done(error) - doc.lines.should.deep.equal(this.result) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) }) it('should push the applied updates to the project history changes api', function (done) { @@ -364,36 +261,22 @@ describe('Applying updates to a doc', function () { this.firstOpTimestamp.should.be.within(this.startTime, Date.now()) }) - it('should yield last updated time', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - this.project_id, - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.lastUpdatedAt.should.be.within(this.startTime, Date.now()) - done() - } + it('should yield last updated time', async function () { + const { lastUpdatedAt } = await DocUpdaterClient.getProjectLastUpdatedAt( + this.project_id ) + lastUpdatedAt.should.be.within(this.startTime, Date.now()) }) - it('should yield no last updated time for another project', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - DocUpdaterClient.randomId(), - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.should.deep.equal({}) - done() - } + it('should yield no last updated time for another project', async function () { + const body = await DocUpdaterClient.getProjectLastUpdatedAt( + DocUpdaterClient.randomId() ) + body.should.deep.equal({}) }) describe('when sending another update', function () { - beforeEach(function (done) { + beforeEach(async function () { this.timeout(10000) this.second_update = Object.assign({}, this.historyOTUpdate) this.second_update.op = [ @@ -403,35 +286,22 @@ describe('Applying updates to a doc', function () { ] this.second_update.v = this.version + 1 this.secondStartTime = Date.now() - DocUpdaterClient.sendUpdate( + await sendUpdateAndWait( this.project_id, this.doc_id, - this.second_update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } + this.second_update ) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) done(error) - doc.lines.should.deep.equal([ - 'one', - 'one and a half', - 'one and a half', - 'two', - 'three', - ]) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal([ + 'one', + 'one and a half', + 'one and a half', + 'two', + 'three', + ]) }) it('should not change the first op timestamp', function (done) { @@ -450,26 +320,15 @@ describe('Applying updates to a doc', function () { ) }) - it('should yield last updated time', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - this.project_id, - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.lastUpdatedAt.should.be.within( - this.secondStartTime, - Date.now() - ) - done() - } - ) + it('should yield last updated time', async function () { + const { lastUpdatedAt } = + await DocUpdaterClient.getProjectLastUpdatedAt(this.project_id) + lastUpdatedAt.should.be.within(this.secondStartTime, Date.now()) }) }) describe('when another client is sending a concurrent update', function () { - beforeEach(function (done) { + beforeEach(async function () { this.timeout(10000) this.otherUpdate = { doc: this.doc_id, @@ -478,35 +337,18 @@ describe('Applying updates to a doc', function () { meta: { source: 'other-random-publicId' }, } this.secondStartTime = Date.now() - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.otherUpdate, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await sendUpdateAndWait(this.project_id, this.doc_id, this.otherUpdate) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) done(error) - doc.lines.should.deep.equal([ - 'one', - 'one and a half', - 'two', - 'two and a half', - 'three', - ]) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal([ + 'one', + 'one and a half', + 'two', + 'two and a half', + 'three', + ]) }) it('should not change the first op timestamp', function (done) { @@ -525,64 +367,32 @@ describe('Applying updates to a doc', function () { ) }) - it('should yield last updated time', function (done) { - DocUpdaterClient.getProjectLastUpdatedAt( - this.project_id, - (error, res, body) => { - if (error != null) { - throw error - } - res.statusCode.should.equal(200) - body.lastUpdatedAt.should.be.within( - this.secondStartTime, - Date.now() - ) - done() - } - ) + it('should yield last updated time', async function () { + const { lastUpdatedAt } = + await DocUpdaterClient.getProjectLastUpdatedAt(this.project_id) + lastUpdatedAt.should.be.within(this.secondStartTime, Date.now()) }) }) }) describe('when the document is loaded', function () { - beforeEach(function (done) { + beforeEach(async function () { 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 - } - sinon.resetHistory() - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + sinon.resetHistory() + await sendUpdateAndWait(this.project_id, this.doc_id, this.update) }) it('should not need to call the web api', function () { MockWebApi.getDocument.called.should.equal(false) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.result) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) }) it('should push the applied updates to the project history changes api', function (done) { @@ -600,40 +410,19 @@ describe('Applying updates to a doc', function () { }) describe('when the document is loaded and is using project-history only', function () { - beforeEach(function (done) { + beforeEach(async function () { 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 - } - sinon.resetHistory() - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + sinon.resetHistory() + await sendUpdateAndWait(this.project_id, this.doc_id, this.update) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.result) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) }) it('should push the applied updates to the project history changes api', function (done) { @@ -651,42 +440,25 @@ describe('Applying updates to a doc', function () { }) describe('when the document is loaded (history-ot)', function () { - beforeEach(function (done) { + beforeEach(async function () { MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, otMigrationStage: 1, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { - if (error != null) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.historyOTUpdate, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) - }) - }) - - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + await sendUpdateAndWait( this.project_id, this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.result) - done() - } + this.historyOTUpdate ) }) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) + }) + it('should push the applied updates to the project history changes api', function (done) { rclientProjectHistory.lrange( ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }), @@ -704,7 +476,7 @@ describe('Applying updates to a doc', function () { describe('when the document has been deleted', function () { describe('when the ops come in a single linear order', function () { - beforeEach(function (done) { + beforeEach(async function () { const lines = ['', '', ''] MockWebApi.insertDoc(this.project_id, this.doc_id, { lines, @@ -724,49 +496,31 @@ describe('Applying updates to a doc', function () { { doc_id: this.doc_id, v: 10, op: [{ i: 'd', p: 10 }] }, ] this.my_result = ['hello world', '', ''] - const actions = [] + for (const update of this.updates.slice(0, 6)) { - actions.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - callback - ) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + update ) } - actions.push(callback => - DocUpdaterClient.deleteDoc(this.project_id, this.doc_id, callback) - ) + + await DocUpdaterClient.deleteDoc(this.project_id, this.doc_id) + for (const update of this.updates.slice(6)) { - actions.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - callback - ) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + update ) } - // process updates - actions.push(cb => - DocUpdaterClient.getDoc(this.project_id, this.doc_id, cb) - ) - - async.series(actions, done) + await DocUpdaterClient.getDoc(this.project_id, this.doc_id) }) - it('should be able to continue applying updates when the project has been deleted', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.my_result) - done() - } - ) + it('should be able to continue applying updates when the project has been deleted', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.my_result) }) it('should store the doc ops in the correct order', function (done) { @@ -788,7 +542,7 @@ describe('Applying updates to a doc', function () { }) describe('when older ops come in after the delete', function () { - beforeEach(function (done) { + beforeEach(function () { const lines = ['', '', ''] MockWebApi.insertDoc(this.project_id, this.doc_id, { lines, @@ -803,60 +557,35 @@ describe('Applying updates to a doc', function () { { doc_id: this.doc_id, v: 0, op: [{ i: 'world', p: 1 }] }, ] this.my_result = ['hello', 'world', ''] - done() }) - it('should be able to continue applying updates when the project has been deleted', function (done) { - let update - const actions = [] - for (update of this.updates.slice(0, 5)) { - ;(update => { - actions.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - callback - ) - ) - })(update) - } - actions.push(callback => - DocUpdaterClient.deleteDoc(this.project_id, this.doc_id, callback) - ) - for (update of this.updates.slice(5)) { - ;(update => { - actions.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - callback - ) - ) - })(update) - } - - async.series(actions, error => { - if (error != null) { - throw error - } - DocUpdaterClient.getDoc( + it('should be able to continue applying updates when the project has been deleted', async function () { + for (const update of this.updates.slice(0, 5)) { + await DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.my_result) - done() - } + update ) - }) + } + + await DocUpdaterClient.deleteDoc(this.project_id, this.doc_id) + + for (const update of this.updates.slice(5)) { + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + update + ) + } + + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.my_result) }) }) }) describe('with a broken update', function () { - beforeEach(function (done) { + beforeEach(async function () { this.broken_update = { doc: this.doc_id, v: this.version, @@ -871,29 +600,12 @@ describe('Applying updates to a doc', function () { (this.messageCallback = sinon.stub()) ) - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.broken_update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await sendUpdateAndWait(this.project_id, this.doc_id, this.broken_update) }) - it('should not update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.lines) - done() - } - ) + it('should not update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.lines) }) it('should send a message with an error', function () { @@ -909,7 +621,7 @@ describe('Applying updates to a doc', function () { }) describe('with a broken update (history-ot)', function () { - beforeEach(function (done) { + beforeEach(async function () { this.broken_update = { doc: this.doc_id, v: this.version, @@ -926,29 +638,12 @@ describe('Applying updates to a doc', function () { (this.messageCallback = sinon.stub()) ) - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.broken_update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await sendUpdateAndWait(this.project_id, this.doc_id, this.broken_update) }) - it('should not update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.lines) - done() - } - ) + it('should not update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.lines) }) it('should send a message with an error', function () { @@ -965,7 +660,7 @@ describe('Applying updates to a doc', function () { }) describe('when mixing ot types (sharejs-text-ot -> history-ot)', function () { - beforeEach(function (done) { + beforeEach(async function () { MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, @@ -976,29 +671,16 @@ describe('Applying updates to a doc', function () { (this.messageCallback = sinon.stub()) ) - DocUpdaterClient.sendUpdate( + await sendUpdateAndWait( this.project_id, this.doc_id, - this.historyOTUpdate, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } + this.historyOTUpdate ) }) - it('should not update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.lines) - done() - } - ) + it('should not update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.lines) }) it('should send a message with an error', function () { @@ -1014,7 +696,7 @@ describe('Applying updates to a doc', function () { }) describe('when mixing ot types (history-ot -> sharejs-text-ot)', function () { - beforeEach(function (done) { + beforeEach(async function () { MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, @@ -1025,29 +707,12 @@ describe('Applying updates to a doc', function () { (this.messageCallback = sinon.stub()) ) - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await sendUpdateAndWait(this.project_id, this.doc_id, this.update) }) - it('should not update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.lines) - done() - } - ) + it('should not update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.lines) }) it('should send a message with an error', function () { @@ -1063,7 +728,7 @@ describe('Applying updates to a doc', function () { }) describe('when there is no version in Mongo', function () { - beforeEach(function (done) { + beforeEach(async function () { MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, }) @@ -1073,34 +738,17 @@ describe('Applying updates to a doc', function () { op: this.update.op, v: 0, } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await sendUpdateAndWait(this.project_id, this.doc_id, update) }) - it('should update the doc (using version = 0)', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.result) - done() - } - ) + it('should update the doc (using version = 0)', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) }) }) describe('when the sending duplicate ops', function () { - beforeEach(function (done) { + beforeEach(async function () { MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, @@ -1111,66 +759,39 @@ describe('Applying updates to a doc', function () { ) // One user delete 'one', the next turns it into 'once'. The second becomes a NOP. - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - { - doc: this.doc_id, - op: [ - { - i: 'one and a half\n', - p: 4, - }, - ], - v: this.version, - meta: { - source: 'ikHceq3yfAdQYzBo4-xZ', + await sendUpdateAndWait(this.project_id, this.doc_id, { + doc: this.doc_id, + op: [ + { + i: 'one and a half\n', + p: 4, }, + ], + v: this.version, + meta: { + source: 'ikHceq3yfAdQYzBo4-xZ', }, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - { - doc: this.doc_id, - op: [ - { - i: 'one and a half\n', - p: 4, - }, - ], - v: this.version, - dupIfSource: ['ikHceq3yfAdQYzBo4-xZ'], - meta: { - source: 'ikHceq3yfAdQYzBo4-xZ', - }, - }, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) - }, 200) - } - ) + }) + + await sendUpdateAndWait(this.project_id, this.doc_id, { + doc: this.doc_id, + op: [ + { + i: 'one and a half\n', + p: 4, + }, + ], + v: this.version, + dupIfSource: ['ikHceq3yfAdQYzBo4-xZ'], + meta: { + source: 'ikHceq3yfAdQYzBo4-xZ', + }, + }) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.result) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) }) it('should return a message about duplicate ops', function () { @@ -1183,7 +804,7 @@ describe('Applying updates to a doc', function () { }) describe('when sending duplicate ops (history-ot)', function () { - beforeEach(function (done) { + beforeEach(async function () { MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, @@ -1195,60 +816,33 @@ describe('Applying updates to a doc', function () { ) // One user delete 'one', the next turns it into 'once'. The second becomes a NOP. - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - { - doc: this.doc_id, - op: [{ textOperation: [4, 'one and a half\n', 9] }], - v: this.version, - meta: { - source: 'ikHceq3yfAdQYzBo4-xZ', - }, + await sendUpdateAndWait(this.project_id, this.doc_id, { + doc: this.doc_id, + op: [{ textOperation: [4, 'one and a half\n', 9] }], + v: this.version, + meta: { + source: 'ikHceq3yfAdQYzBo4-xZ', }, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - { - doc: this.doc_id, - op: [ - { - textOperation: [4, 'one and a half\n', 9], - }, - ], - v: this.version, - dupIfSource: ['ikHceq3yfAdQYzBo4-xZ'], - meta: { - source: 'ikHceq3yfAdQYzBo4-xZ', - }, - }, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) - }, 200) - } - ) + }) + + await sendUpdateAndWait(this.project_id, this.doc_id, { + doc: this.doc_id, + op: [ + { + textOperation: [4, 'one and a half\n', 9], + }, + ], + v: this.version, + dupIfSource: ['ikHceq3yfAdQYzBo4-xZ'], + meta: { + source: 'ikHceq3yfAdQYzBo4-xZ', + }, + }) }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - doc.lines.should.deep.equal(this.result) - done() - } - ) + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.result) }) it('should return a message about duplicate ops', function () { @@ -1261,7 +855,7 @@ describe('Applying updates to a doc', function () { }) describe('when sending updates for a non-existing doc id', function () { - beforeEach(function (done) { + beforeEach(async function () { this.non_existing = { doc: this.doc_id, v: this.version, @@ -1272,29 +866,13 @@ describe('Applying updates to a doc', function () { (this.messageCallback = sinon.stub()) ) - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.non_existing, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await sendUpdateAndWait(this.project_id, this.doc_id, this.non_existing) }) - it('should not update or create a doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - res.statusCode.should.equal(404) - done() - } - ) + it('should not update or create a doc', async function () { + await expect(DocUpdaterClient.getDoc(this.project_id, this.doc_id)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 404) }) it('should send a message with an error', function () { diff --git a/services/document-updater/test/acceptance/js/ApplyingUpdatesToProjectStructureTests.js b/services/document-updater/test/acceptance/js/ApplyingUpdatesToProjectStructureTests.js index 9e825a4f4b..607edd5557 100644 --- a/services/document-updater/test/acceptance/js/ApplyingUpdatesToProjectStructureTests.js +++ b/services/document-updater/test/acceptance/js/ApplyingUpdatesToProjectStructureTests.js @@ -1,4 +1,5 @@ const sinon = require('sinon') +const { setTimeout } = require('node:timers/promises') const Settings = require('@overleaf/settings') const rclientProjectHistory = require('@overleaf/redis-wrapper').createClient( Settings.redis.project_history @@ -10,6 +11,13 @@ const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +async function sendProjectUpdateAndWait(projectId, docId, update, version) { + await DocUpdaterClient.sendProjectUpdate(projectId, docId, update, version) + + // It seems that we need to wait for a little while + await setTimeout(200) +} + describe("Applying updates to a project's structure", function () { before(function () { this.user_id = 'user-id-123' @@ -17,7 +25,7 @@ describe("Applying updates to a project's structure", function () { }) describe('renaming a file', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.fileUpdate = { type: 'rename-file', @@ -26,23 +34,13 @@ describe("Applying updates to a project's structure", function () { newPathname: '/new-file-path', } this.updates = [this.fileUpdate] - DocUpdaterApp.ensureRunning(error => { - if (error) { - return done(error) - } - DocUpdaterClient.sendProjectUpdate( - this.project_id, - this.user_id, - this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } - ) - }) + await DocUpdaterApp.ensureRunning() + await sendProjectUpdateAndWait( + this.project_id, + this.user_id, + this.updates, + this.version + ) }) it('should push the applied file renames to the project history api', function (done) { @@ -70,7 +68,7 @@ describe("Applying updates to a project's structure", function () { }) describe('deleting a file', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.fileUpdate = { type: 'rename-file', @@ -79,17 +77,11 @@ describe("Applying updates to a project's structure", function () { newPathname: '', } this.updates = [this.fileUpdate] - DocUpdaterClient.sendProjectUpdate( + await sendProjectUpdateAndWait( this.project_id, this.user_id, this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } + this.version ) }) @@ -129,19 +121,13 @@ describe("Applying updates to a project's structure", function () { }) describe('when the document is not loaded', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() - DocUpdaterClient.sendProjectUpdate( + await sendProjectUpdateAndWait( this.project_id, this.user_id, this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } + this.version ) }) @@ -170,45 +156,29 @@ describe("Applying updates to a project's structure", function () { }) describe('when the document is loaded', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.update.id, {}) - DocUpdaterClient.preloadDoc(this.project_id, this.update.id, error => { - if (error) { - return done(error) - } - sinon.spy(MockWebApi, 'getDocument') - DocUpdaterClient.sendProjectUpdate( - this.project_id, - this.user_id, - this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.update.id) + sinon.spy(MockWebApi, 'getDocument') + await sendProjectUpdateAndWait( + this.project_id, + this.user_id, + this.updates, + this.version + ) }) after(function () { MockWebApi.getDocument.restore() }) - it('should update the doc', function (done) { - DocUpdaterClient.getDoc( + it('should update the doc', async function () { + const doc = await DocUpdaterClient.getDoc( this.project_id, - this.update.id, - (error, res, doc) => { - if (error) { - return done(error) - } - doc.pathname.should.equal(this.update.newPathname) - done() - } + this.update.id ) + doc.pathname.should.equal(this.update.newPathname) }) it('should push the applied doc renames to the project history api', function (done) { @@ -271,19 +241,13 @@ describe("Applying updates to a project's structure", function () { }) describe('when the documents are not loaded', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() - DocUpdaterClient.sendProjectUpdate( + await sendProjectUpdateAndWait( this.project_id, this.user_id, this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } + this.version ) }) @@ -348,19 +312,13 @@ describe("Applying updates to a project's structure", function () { }) describe('when the document is not loaded', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() - DocUpdaterClient.sendProjectUpdate( + await sendProjectUpdateAndWait( this.project_id, this.user_id, this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } + this.version ) }) @@ -389,46 +347,29 @@ describe("Applying updates to a project's structure", function () { }) describe('when the document is loaded', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.update.id, {}) - DocUpdaterClient.preloadDoc(this.project_id, this.update.id, error => { - if (error) { - return done(error) - } - sinon.spy(MockWebApi, 'getDocument') - DocUpdaterClient.sendProjectUpdate( - this.project_id, - this.user_id, - this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.update.id) + sinon.spy(MockWebApi, 'getDocument') + await sendProjectUpdateAndWait( + this.project_id, + this.user_id, + this.updates, + this.version + ) }) after(function () { MockWebApi.getDocument.restore() }) - it('should not modify the doc', function (done) { - DocUpdaterClient.getDoc( + it('should not modify the doc', async function () { + const doc = await DocUpdaterClient.getDoc( this.project_id, - this.update.id, - (error, res, doc) => { - if (error) { - return done(error) - } - - doc.pathname.should.equal('/a/b/c.tex') // default pathname from MockWebApi - done() - } + this.update.id ) + doc.pathname.should.equal('/a/b/c.tex') // default pathname from MockWebApi }) it('should push the applied doc update to the project history api', function (done) { @@ -457,7 +398,7 @@ describe("Applying updates to a project's structure", function () { }) describe('adding a file', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.fileUpdate = { type: 'add-file', @@ -466,17 +407,11 @@ describe("Applying updates to a project's structure", function () { url: 'filestore.example.com', } this.updates = [this.fileUpdate] - DocUpdaterClient.sendProjectUpdate( + await sendProjectUpdateAndWait( this.project_id, this.user_id, this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } + this.version ) }) @@ -505,7 +440,7 @@ describe("Applying updates to a project's structure", function () { }) describe('adding a doc', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.docUpdate = { type: 'add-doc', @@ -514,17 +449,11 @@ describe("Applying updates to a project's structure", function () { docLines: 'a\nb', } this.updates = [this.docUpdate] - DocUpdaterClient.sendProjectUpdate( + await sendProjectUpdateAndWait( this.project_id, this.user_id, this.updates, - this.version, - error => { - if (error) { - return done(error) - } - setTimeout(done, 200) - } + this.version ) }) @@ -553,7 +482,7 @@ describe("Applying updates to a project's structure", function () { }) describe('with enough updates to flush to the history service', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.version0 = 12345 @@ -574,29 +503,19 @@ describe("Applying updates to a project's structure", function () { // Send updates in chunks to causes multiple flushes const projectId = this.project_id const userId = this.project_id - DocUpdaterClient.sendProjectUpdate( + await DocUpdaterClient.sendProjectUpdate( projectId, userId, updates.slice(0, 250), - this.version0, - function (error) { - if (error) { - return done(error) - } - DocUpdaterClient.sendProjectUpdate( - projectId, - userId, - updates.slice(250), - this.version1, - error => { - if (error) { - return done(error) - } - setTimeout(done, 2000) - } - ) - } + this.version0 ) + await DocUpdaterClient.sendProjectUpdate( + projectId, + userId, + updates.slice(250), + this.version1 + ) + await setTimeout(200) }) after(function () { @@ -611,7 +530,7 @@ describe("Applying updates to a project's structure", function () { }) describe('with too few updates to flush to the history service', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.version0 = 12345 @@ -633,29 +552,19 @@ describe("Applying updates to a project's structure", function () { // Send updates in chunks const projectId = this.project_id const userId = this.project_id - DocUpdaterClient.sendProjectUpdate( + await DocUpdaterClient.sendProjectUpdate( projectId, userId, updates.slice(0, 10), - this.version0, - function (error) { - if (error) { - return done(error) - } - DocUpdaterClient.sendProjectUpdate( - projectId, - userId, - updates.slice(10), - this.version1, - error => { - if (error) { - return done(error) - } - setTimeout(done, 2000) - } - ) - } + this.version0 ) + await DocUpdaterClient.sendProjectUpdate( + projectId, + userId, + updates.slice(10), + this.version1 + ) + await setTimeout(200) }) after(function () { diff --git a/services/document-updater/test/acceptance/js/CheckRedisMongoSyncStateTests.js b/services/document-updater/test/acceptance/js/CheckRedisMongoSyncStateTests.js index ebbc015a58..56e5368cdc 100644 --- a/services/document-updater/test/acceptance/js/CheckRedisMongoSyncStateTests.js +++ b/services/document-updater/test/acceptance/js/CheckRedisMongoSyncStateTests.js @@ -15,8 +15,8 @@ const rclient = require('@overleaf/redis-wrapper').createClient( ) describe('CheckRedisMongoSyncState', function () { - beforeEach(function (done) { - DocUpdaterApp.ensureRunning(done) + beforeEach(async function () { + await DocUpdaterApp.ensureRunning() }) beforeEach(async function () { await rclient.flushall() @@ -60,14 +60,14 @@ describe('CheckRedisMongoSyncState', function () { describe('with a project', function () { let projectId, docId - beforeEach(function (done) { + beforeEach(async function () { projectId = DocUpdaterClient.randomId() docId = DocUpdaterClient.randomId() MockWebApi.insertDoc(projectId, docId, { lines: ['mongo', 'lines'], version: 1, }) - DocUpdaterClient.getDoc(projectId, docId, done) + await DocUpdaterClient.preloadDoc(projectId, docId) }) it('should work when in sync', async function () { @@ -149,14 +149,14 @@ describe('CheckRedisMongoSyncState', function () { describe('with a project', function () { let projectId2, docId2 - beforeEach(function (done) { + beforeEach(async function () { projectId2 = DocUpdaterClient.randomId() docId2 = DocUpdaterClient.randomId() MockWebApi.insertDoc(projectId2, docId2, { lines: ['mongo', 'lines'], version: 1, }) - DocUpdaterClient.getDoc(projectId2, docId2, done) + await DocUpdaterClient.preloadDoc(projectId2, docId2) }) it('should work when in sync', async function () { @@ -245,14 +245,14 @@ describe('CheckRedisMongoSyncState', function () { describe('with more projects than the LIMIT', function () { for (let i = 0; i < 20; i++) { - beforeEach(function (done) { + beforeEach(async function () { const projectId = DocUpdaterClient.randomId() const docId = DocUpdaterClient.randomId() MockWebApi.insertDoc(projectId, docId, { lines: ['mongo', 'lines'], version: 1, }) - DocUpdaterClient.getDoc(projectId, docId, done) + await DocUpdaterClient.preloadDoc(projectId, docId) }) } @@ -278,7 +278,7 @@ describe('CheckRedisMongoSyncState', function () { describe('with partially deleted doc', function () { let projectId, docId - beforeEach(function (done) { + beforeEach(async function () { projectId = DocUpdaterClient.randomId() docId = DocUpdaterClient.randomId() MockWebApi.insertDoc(projectId, docId, { @@ -289,10 +289,8 @@ describe('CheckRedisMongoSyncState', function () { lines: ['mongo', 'lines'], version: 1, }) - DocUpdaterClient.getDoc(projectId, docId, err => { - MockWebApi.clearDocs() - done(err) - }) + await DocUpdaterClient.preloadDoc(projectId, docId) + MockWebApi.clearDocs() }) describe('with only the file-tree entry deleted', function () { it('should flag the partial deletion', async function () { diff --git a/services/document-updater/test/acceptance/js/DeletingADocumentTests.js b/services/document-updater/test/acceptance/js/DeletingADocumentTests.js index 24aef32bf2..f43df16347 100644 --- a/services/document-updater/test/acceptance/js/DeletingADocumentTests.js +++ b/services/document-updater/test/acceptance/js/DeletingADocumentTests.js @@ -1,19 +1,12 @@ -// 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 - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ const sinon = require('sinon') const MockProjectHistoryApi = require('./helpers/MockProjectHistoryApi') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const { setTimeout } = require('node:timers/promises') describe('Deleting a document', function () { - before(function (done) { + before(async function () { this.lines = ['one', 'two', 'three'] this.version = 42 this.update = { @@ -29,7 +22,7 @@ describe('Deleting a document', function () { this.result = ['one', 'one and a half', 'two', 'three'] sinon.spy(MockProjectHistoryApi, 'flushProject') - DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) after(function () { @@ -37,11 +30,9 @@ describe('Deleting a document', function () { }) 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(), - ]) + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() sinon.spy(MockWebApi, 'setDocument') sinon.spy(MockWebApi, 'getDocument') @@ -49,32 +40,15 @@ describe('Deleting a document', function () { lines: this.lines, version: this.version, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { - if (error != null) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.deleteDoc( - this.project_id, - this.doc_id, - (error, res, body) => { - if (error) return done(error) - this.statusCode = res.statusCode - setTimeout(done, 200) - } - ) - }, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.update + ) + await setTimeout(200) + const res = await DocUpdaterClient.deleteDoc(this.project_id, this.doc_id) + this.statusCode = res.status }) after(function () { @@ -92,20 +66,13 @@ describe('Deleting a document', function () { .should.equal(true) }) - it('should need to reload the doc if read again', function (done) { + it('should need to reload the doc if read again', async function () { MockWebApi.getDocument.resetHistory() MockWebApi.getDocument.called.should.equals(false) - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - MockWebApi.getDocument - .calledWith(this.project_id, this.doc_id) - .should.equal(true) - done() - } - ) + await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + MockWebApi.getDocument + .calledWith(this.project_id, this.doc_id) + .should.equal(true) }) it('should flush project history', function () { @@ -116,25 +83,16 @@ describe('Deleting a document', function () { }) describe('when the doc is not in the doc updater', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, }) sinon.spy(MockWebApi, 'setDocument') sinon.spy(MockWebApi, 'getDocument') - DocUpdaterClient.deleteDoc( - this.project_id, - this.doc_id, - (error, res, body) => { - if (error) return done(error) - this.statusCode = res.statusCode - setTimeout(done, 200) - } - ) + const res = await DocUpdaterClient.deleteDoc(this.project_id, this.doc_id) + this.statusCode = res.status }) after(function () { @@ -150,19 +108,12 @@ describe('Deleting a document', function () { MockWebApi.setDocument.called.should.equal(false) }) - it('should need to reload the doc if read again', function (done) { + it('should need to reload the doc if read again', async function () { MockWebApi.getDocument.called.should.equals(false) - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - MockWebApi.getDocument - .calledWith(this.project_id, this.doc_id) - .should.equal(true) - done() - } - ) + await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + MockWebApi.getDocument + .calledWith(this.project_id, this.doc_id) + .should.equal(true) }) it('should flush project history', function () { diff --git a/services/document-updater/test/acceptance/js/DeletingAProjectTests.js b/services/document-updater/test/acceptance/js/DeletingAProjectTests.js index cca0b4d862..7fd768ef92 100644 --- a/services/document-updater/test/acceptance/js/DeletingAProjectTests.js +++ b/services/document-updater/test/acceptance/js/DeletingAProjectTests.js @@ -1,13 +1,5 @@ -// 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 - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ const sinon = require('sinon') -const async = require('async') +const { setTimeout } = require('node:timers/promises') const MockProjectHistoryApi = require('./helpers/MockProjectHistoryApi') const MockWebApi = require('./helpers/MockWebApi') @@ -15,7 +7,7 @@ const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') describe('Deleting a project', function () { - beforeEach(function (done) { + beforeEach(async function () { let docId0, docId1 this.project_id = DocUpdaterClient.randomId() this.docs = [ @@ -50,45 +42,27 @@ describe('Deleting a project', function () { updatedLines: ['four', 'four and a half', 'five', 'six'], }, ] - for (const doc of Array.from(this.docs)) { + for (const doc of this.docs) { MockWebApi.insertDoc(this.project_id, doc.id, { lines: doc.lines, version: doc.update.v, }) } - DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) describe('without updates', function () { - beforeEach(function (done) { + beforeEach(async function () { sinon.spy(MockWebApi, 'setDocument') sinon.spy(MockProjectHistoryApi, 'flushProject') - async.series( - this.docs.map(doc => { - return callback => { - DocUpdaterClient.preloadDoc(this.project_id, doc.id, error => { - callback(error) - }) - } - }), - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.deleteProject( - this.project_id, - (error, res, body) => { - if (error) return done(error) - this.statusCode = res.statusCode - done() - } - ) - }, 200) - } - ) + for (const doc of this.docs) { + await DocUpdaterClient.preloadDoc(this.project_id, doc.id) + } + await setTimeout(200) + const res = await DocUpdaterClient.deleteProject(this.project_id) + this.statusCode = res.status }) afterEach(function () { @@ -104,32 +78,18 @@ describe('Deleting a project', function () { MockWebApi.setDocument.should.not.have.been.called }) - it('should need to reload the docs if read again', function (done) { + it('should need to reload the docs if read again', async function () { sinon.spy(MockWebApi, 'getDocument') - async.series( - this.docs.map(doc => { - return callback => { - MockWebApi.getDocument - .calledWith(this.project_id, doc.id) - .should.equal(false) - DocUpdaterClient.getDoc( - this.project_id, - doc.id, - (error, res, returnedDoc) => { - if (error) return done(error) - MockWebApi.getDocument - .calledWith(this.project_id, doc.id) - .should.equal(true) - callback() - } - ) - } - }), - () => { - MockWebApi.getDocument.restore() - done() - } - ) + for (const doc of this.docs) { + MockWebApi.getDocument + .calledWith(this.project_id, doc.id) + .should.equal(false) + await DocUpdaterClient.getDoc(this.project_id, doc.id) + MockWebApi.getDocument + .calledWith(this.project_id, doc.id) + .should.equal(true) + } + MockWebApi.getDocument.restore() }) it('should flush each doc in project history', function () { @@ -140,44 +100,16 @@ describe('Deleting a project', function () { }) describe('with documents which have been updated', function () { - beforeEach(function (done) { + beforeEach(async function () { sinon.spy(MockWebApi, 'setDocument') sinon.spy(MockProjectHistoryApi, 'flushProject') - - async.series( - this.docs.map(doc => { - return callback => { - DocUpdaterClient.preloadDoc(this.project_id, doc.id, error => { - if (error != null) { - return callback(error) - } - DocUpdaterClient.sendUpdate( - this.project_id, - doc.id, - doc.update, - error => { - callback(error) - } - ) - }) - } - }), - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.deleteProject( - this.project_id, - (error, res, body) => { - if (error) return done(error) - this.statusCode = res.statusCode - done() - } - ) - }, 200) - } - ) + for (const doc of this.docs) { + await DocUpdaterClient.preloadDoc(this.project_id, doc.id) + await DocUpdaterClient.sendUpdate(this.project_id, doc.id, doc.update) + } + await setTimeout(200) + const res = await DocUpdaterClient.deleteProject(this.project_id) + this.statusCode = res.status }) afterEach(function () { @@ -190,39 +122,25 @@ describe('Deleting a project', function () { }) it('should send each document to the web api', function () { - Array.from(this.docs).map(doc => + for (const doc of this.docs) { MockWebApi.setDocument .calledWith(this.project_id, doc.id, doc.updatedLines) .should.equal(true) - ) + } }) - it('should need to reload the docs if read again', function (done) { + it('should need to reload the docs if read again', async function () { sinon.spy(MockWebApi, 'getDocument') - async.series( - this.docs.map(doc => { - return callback => { - MockWebApi.getDocument - .calledWith(this.project_id, doc.id) - .should.equal(false) - DocUpdaterClient.getDoc( - this.project_id, - doc.id, - (error, res, returnedDoc) => { - if (error) return done(error) - MockWebApi.getDocument - .calledWith(this.project_id, doc.id) - .should.equal(true) - callback() - } - ) - } - }), - () => { - MockWebApi.getDocument.restore() - done() - } - ) + for (const doc of this.docs) { + MockWebApi.getDocument + .calledWith(this.project_id, doc.id) + .should.equal(false) + await DocUpdaterClient.getDoc(this.project_id, doc.id) + MockWebApi.getDocument + .calledWith(this.project_id, doc.id) + .should.equal(true) + } + MockWebApi.getDocument.restore() }) it('should flush each doc in project history', function () { @@ -233,44 +151,18 @@ describe('Deleting a project', function () { }) describe('with the background=true parameter from realtime and no request to flush the queue', function () { - beforeEach(function (done) { + beforeEach(async function () { sinon.spy(MockWebApi, 'setDocument') sinon.spy(MockProjectHistoryApi, 'flushProject') - - async.series( - this.docs.map(doc => { - return callback => { - DocUpdaterClient.preloadDoc(this.project_id, doc.id, error => { - if (error != null) { - return callback(error) - } - DocUpdaterClient.sendUpdate( - this.project_id, - doc.id, - doc.update, - error => { - callback(error) - } - ) - }) - } - }), - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.deleteProjectOnShutdown( - this.project_id, - (error, res, body) => { - if (error) return done(error) - this.statusCode = res.statusCode - done() - } - ) - }, 200) - } + for (const doc of this.docs) { + await DocUpdaterClient.preloadDoc(this.project_id, doc.id) + await DocUpdaterClient.sendUpdate(this.project_id, doc.id, doc.update) + } + await setTimeout(200) + const res = await DocUpdaterClient.deleteProjectOnShutdown( + this.project_id ) + this.statusCode = res.status }) afterEach(function () { @@ -292,45 +184,21 @@ describe('Deleting a project', function () { }) describe('with the background=true parameter from realtime and a request to flush the queue', function () { - beforeEach(function (done) { + beforeEach(async function () { sinon.spy(MockWebApi, 'setDocument') sinon.spy(MockProjectHistoryApi, 'flushProject') - - async.series( - this.docs.map(doc => { - return callback => { - DocUpdaterClient.preloadDoc(this.project_id, doc.id, error => { - if (error != null) { - return callback(error) - } - DocUpdaterClient.sendUpdate( - this.project_id, - doc.id, - doc.update, - error => { - callback(error) - } - ) - }) - } - }), - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.deleteProjectOnShutdown( - this.project_id, - (error, res, body) => { - if (error) return done(error) - this.statusCode = res.statusCode - // after deleting the project and putting it in the queue, flush the queue - setTimeout(() => DocUpdaterClient.flushOldProjects(done), 2000) - } - ) - }, 200) - } + for (const doc of this.docs) { + await DocUpdaterClient.preloadDoc(this.project_id, doc.id) + await DocUpdaterClient.sendUpdate(this.project_id, doc.id, doc.update) + } + await setTimeout(200) + const res = await DocUpdaterClient.deleteProjectOnShutdown( + this.project_id ) + this.statusCode = res.status + // after deleting the project and putting it in the queue, flush the queue + await setTimeout(2000) + await DocUpdaterClient.flushOldProjects() }) afterEach(function () { @@ -343,11 +211,11 @@ describe('Deleting a project', function () { }) it('should send each document to the web api', function () { - Array.from(this.docs).map(doc => + for (const doc of this.docs) { MockWebApi.setDocument .calledWith(this.project_id, doc.id, doc.updatedLines) .should.equal(true) - ) + } }) it('should flush to project history', function () { diff --git a/services/document-updater/test/acceptance/js/FlushingAProjectTests.js b/services/document-updater/test/acceptance/js/FlushingAProjectTests.js index 3f600040dc..6fae6c9fb7 100644 --- a/services/document-updater/test/acceptance/js/FlushingAProjectTests.js +++ b/services/document-updater/test/acceptance/js/FlushingAProjectTests.js @@ -1,26 +1,18 @@ -// 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 async = require('async') +const { setTimeout } = require('node:timers/promises') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') describe('Flushing a project', function () { - before(function (done) { - let docId0, docId1 + before(async function () { this.project_id = DocUpdaterClient.randomId() + const docId0 = DocUpdaterClient.randomId() + const docId1 = DocUpdaterClient.randomId() this.docs = [ { - id: (docId0 = DocUpdaterClient.randomId()), + id: docId0, lines: ['one', 'two', 'three'], update: { doc: docId0, @@ -35,7 +27,7 @@ describe('Flushing a project', function () { updatedLines: ['one', 'one and a half', 'two', 'three'], }, { - id: (docId1 = DocUpdaterClient.randomId()), + id: docId1, lines: ['four', 'five', 'six'], update: { doc: docId1, @@ -50,92 +42,51 @@ describe('Flushing a project', function () { updatedLines: ['four', 'four and a half', 'five', 'six'], }, ] - for (const doc of Array.from(this.docs)) { + for (const doc of this.docs) { MockWebApi.insertDoc(this.project_id, doc.id, { lines: doc.lines, version: doc.update.v, }) } - return DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) - return describe('with documents which have been updated', function () { - before(function (done) { + describe('with documents which have been updated', function () { + before(async function () { sinon.spy(MockWebApi, 'setDocument') - - return async.series( - this.docs.map(doc => { - return callback => { - return DocUpdaterClient.preloadDoc( - this.project_id, - doc.id, - error => { - if (error != null) { - return callback(error) - } - return DocUpdaterClient.sendUpdate( - this.project_id, - doc.id, - doc.update, - error => { - return callback(error) - } - ) - } - ) - } - }), - error => { - if (error != null) { - throw error - } - return setTimeout(() => { - return DocUpdaterClient.flushProject( - this.project_id, - (error, res, body) => { - if (error) return done(error) - this.statusCode = res.statusCode - return done() - } - ) - }, 200) - } - ) + for (const doc of this.docs) { + await DocUpdaterClient.preloadDoc(this.project_id, doc.id) + await DocUpdaterClient.sendUpdate(this.project_id, doc.id, doc.update) + } + await setTimeout(200) + const res = await DocUpdaterClient.flushProject(this.project_id) + this.statusCode = res.status }) after(function () { - return MockWebApi.setDocument.restore() + MockWebApi.setDocument.restore() }) it('should return a 204 status code', function () { - return this.statusCode.should.equal(204) + this.statusCode.should.equal(204) }) it('should send each document to the web api', function () { - return Array.from(this.docs).map(doc => + for (const doc of this.docs) { MockWebApi.setDocument .calledWith(this.project_id, doc.id, doc.updatedLines) .should.equal(true) - ) + } }) - return it('should update the lines in the doc updater', function (done) { - return async.series( - this.docs.map(doc => { - return callback => { - return DocUpdaterClient.getDoc( - this.project_id, - doc.id, - (error, res, returnedDoc) => { - if (error) return done(error) - returnedDoc.lines.should.deep.equal(doc.updatedLines) - return callback() - } - ) - } - }), - done - ) + it('should update the lines in the doc updater', async function () { + for (const doc of this.docs) { + const returnedDoc = await DocUpdaterClient.getDoc( + this.project_id, + doc.id + ) + returnedDoc.lines.should.deep.equal(doc.updatedLines) + } }) }) }) diff --git a/services/document-updater/test/acceptance/js/FlushingDocsTests.js b/services/document-updater/test/acceptance/js/FlushingDocsTests.js index d6c5d85611..91202a3797 100644 --- a/services/document-updater/test/acceptance/js/FlushingDocsTests.js +++ b/services/document-updater/test/acceptance/js/FlushingDocsTests.js @@ -1,26 +1,13 @@ -/* eslint-disable - no-return-assign, - 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 { expect } = require('chai') -const async = require('async') +const { setTimeout } = require('node:timers/promises') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') describe('Flushing a doc to Mongo', function () { - before(function (done) { + before(async function () { this.lines = ['one', 'two', 'three'] this.version = 42 this.update = { @@ -35,83 +22,69 @@ describe('Flushing a doc to Mongo', function () { v: this.version, } this.result = ['one', 'one and a half', 'two', 'three'] - return DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) 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(), - ]) + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() sinon.spy(MockWebApi, 'setDocument') MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, }) - return DocUpdaterClient.sendUpdates( - this.project_id, - this.doc_id, - [this.update], - error => { - if (error != null) { - throw error - } - return setTimeout(() => { - return DocUpdaterClient.flushDoc(this.project_id, this.doc_id, done) - }, 200) - } - ) + await DocUpdaterClient.sendUpdates(this.project_id, this.doc_id, [ + this.update, + ]) + await setTimeout(200) + await DocUpdaterClient.flushDoc(this.project_id, this.doc_id) }) after(function () { - return MockWebApi.setDocument.restore() + MockWebApi.setDocument.restore() }) it('should flush the updated doc lines and version to the web api', function () { - return MockWebApi.setDocument + MockWebApi.setDocument .calledWith(this.project_id, this.doc_id, this.result, this.version + 1) .should.equal(true) }) - return it('should flush the last update author and time to the web api', function () { + it('should flush the last update author and time to the web api', function () { const lastUpdatedAt = MockWebApi.setDocument.lastCall.args[5] parseInt(lastUpdatedAt).should.be.closeTo(new Date().getTime(), 30000) const lastUpdatedBy = MockWebApi.setDocument.lastCall.args[6] - return lastUpdatedBy.should.equal('last-author-fake-id') + lastUpdatedBy.should.equal('last-author-fake-id') }) }) describe('when the doc does not exist in the doc updater', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, }) sinon.spy(MockWebApi, 'setDocument') - return DocUpdaterClient.flushDoc(this.project_id, this.doc_id, done) + await DocUpdaterClient.flushDoc(this.project_id, this.doc_id) }) after(function () { - return MockWebApi.setDocument.restore() + MockWebApi.setDocument.restore() }) - return it('should not flush the doc to the web api', function () { - return MockWebApi.setDocument.called.should.equal(false) + it('should not flush the doc to the web api', function () { + MockWebApi.setDocument.called.should.equal(false) }) }) - return describe('when the web api http request takes a long time on first request', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) + describe('when the web api http request takes a long time on first request', function () { + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, @@ -130,33 +103,26 @@ describe('Flushing a doc to Mongo', function () { lastUpdatedBy, callback ) => { - if (callback == null) { + if (!callback) { callback = function () {} } setTimeout(callback, t) - return (t = 0) + t = 0 } ) - return DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, done) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) }) after(function () { - return MockWebApi.setDocument.restore() + MockWebApi.setDocument.restore() }) - return it('should still work', function (done) { + it('should still work', async function () { const start = Date.now() - return DocUpdaterClient.flushDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - res.statusCode.should.equal(204) - const delta = Date.now() - start - expect(delta).to.be.below(20000) - return done() - } - ) + const res = await DocUpdaterClient.flushDoc(this.project_id, this.doc_id) + res.status.should.equal(204) + const delta = Date.now() - start + expect(delta).to.be.below(20000) }) }) }) diff --git a/services/document-updater/test/acceptance/js/GettingADocumentTests.js b/services/document-updater/test/acceptance/js/GettingADocumentTests.js index 65298932d9..02c343e38e 100644 --- a/services/document-updater/test/acceptance/js/GettingADocumentTests.js +++ b/services/document-updater/test/acceptance/js/GettingADocumentTests.js @@ -1,32 +1,22 @@ -// 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 { expect } = require('chai') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const { RequestFailedError } = require('@overleaf/fetch-utils') describe('Getting a document', function () { - before(function (done) { + before(async function () { this.lines = ['one', 'two', 'three'] this.version = 42 - return DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) describe('when the document is not loaded', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() sinon.spy(MockWebApi, 'getDocument') MockWebApi.insertDoc(this.project_id, this.doc_id, { @@ -34,87 +24,66 @@ describe('Getting a document', function () { version: this.version, }) - return DocUpdaterClient.getDoc( + this.returnedDoc = await DocUpdaterClient.getDoc( this.project_id, - this.doc_id, - (error, res, returnedDoc) => { - if (error) return done(error) - this.returnedDoc = returnedDoc - return done() - } + this.doc_id ) }) after(function () { - return MockWebApi.getDocument.restore() + MockWebApi.getDocument.restore() }) it('should load the document from the web API', function () { - return MockWebApi.getDocument + MockWebApi.getDocument .calledWith(this.project_id, this.doc_id) .should.equal(true) }) it('should return the document lines', function () { - return this.returnedDoc.lines.should.deep.equal(this.lines) + this.returnedDoc.lines.should.deep.equal(this.lines) }) - return it('should return the document at its current version', function () { - return this.returnedDoc.version.should.equal(this.version) + it('should return the document at its current version', function () { + this.returnedDoc.version.should.equal(this.version) }) }) describe('when the document is already loaded', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: this.lines, version: this.version, }) - return DocUpdaterClient.preloadDoc( + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + + sinon.spy(MockWebApi, 'getDocument') + this.returnedDoc = await DocUpdaterClient.getDoc( this.project_id, - this.doc_id, - error => { - if (error != null) { - throw error - } - sinon.spy(MockWebApi, 'getDocument') - return DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, returnedDoc) => { - if (error) return done(error) - this.returnedDoc = returnedDoc - return done() - } - ) - } + this.doc_id ) }) after(function () { - return MockWebApi.getDocument.restore() + MockWebApi.getDocument.restore() }) it('should not load the document from the web API', function () { - return MockWebApi.getDocument.called.should.equal(false) + MockWebApi.getDocument.called.should.equal(false) }) - return it('should return the document lines', function () { - return this.returnedDoc.lines.should.deep.equal(this.lines) + it('should return the document lines', function () { + this.returnedDoc.lines.should.deep.equal(this.lines) }) }) describe('when the request asks for some recent ops', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) + before(async function () { + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { lines: (this.lines = ['one', 'two', 'three']), }) @@ -125,159 +94,109 @@ describe('Getting a document', function () { v, })) - return DocUpdaterClient.sendUpdates( + await DocUpdaterClient.sendUpdates( this.project_id, this.doc_id, - this.updates, - error => { - if (error != null) { - throw error - } - sinon.spy(MockWebApi, 'getDocument') - return done() - } + this.updates ) + sinon.spy(MockWebApi, 'getDocument') }) after(function () { - return MockWebApi.getDocument.restore() + MockWebApi.getDocument.restore() }) describe('when the ops are loaded', function () { - before(function (done) { - return DocUpdaterClient.getDocAndRecentOps( + before(async function () { + this.returnedDoc = await DocUpdaterClient.getDocAndRecentOps( this.project_id, this.doc_id, - 190, - (error, res, returnedDoc) => { - if (error) return done(error) - this.returnedDoc = returnedDoc - return done() - } + 190 ) }) - return it('should return the recent ops', function () { + it('should return the recent ops', function () { this.returnedDoc.ops.length.should.equal(10) - return Array.from(this.updates.slice(190, -1)).map((update, i) => + for (const [i, update] of this.updates.slice(190, -1).entries()) { this.returnedDoc.ops[i].op.should.deep.equal(update.op) - ) + } }) }) - return describe('when the ops are not all loaded', function () { - before(function (done) { + describe('when the ops are not all loaded', function () { + it('should return UnprocessableEntity', async function () { // We only track 100 ops - return DocUpdaterClient.getDocAndRecentOps( - this.project_id, - this.doc_id, - 10, - (error, res, returnedDoc) => { - if (error) return done(error) - this.res = res - this.returnedDoc = returnedDoc - return done() - } + await expect( + DocUpdaterClient.getDocAndRecentOps(this.project_id, this.doc_id, 10) ) - }) - - return it('should return UnprocessableEntity', function () { - return this.res.statusCode.should.equal(422) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 422) }) }) }) describe('when the document does not exist', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) - return DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - this.statusCode = res.statusCode - return done() - } - ) - }) - - return it('should return 404', function () { - return this.statusCode.should.equal(404) + it('should return 404', async function () { + const projectId = DocUpdaterClient.randomId() + const docId = DocUpdaterClient.randomId() + await expect(DocUpdaterClient.getDoc(projectId, docId)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 404) }) }) describe('when the web api returns an error', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) + before(function () { sinon .stub(MockWebApi, 'getDocument') .callsFake((projectId, docId, callback) => { if (callback == null) { callback = function () {} } - return callback(new Error('oops')) + callback(new Error('oops')) }) - return DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - this.statusCode = res.statusCode - return done() - } - ) }) after(function () { - return MockWebApi.getDocument.restore() + MockWebApi.getDocument.restore() }) - return it('should return 500', function () { - return this.statusCode.should.equal(500) + it('should return 500', async function () { + const projectId = DocUpdaterClient.randomId() + const docId = DocUpdaterClient.randomId() + await expect(DocUpdaterClient.getDoc(projectId, docId)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 500) }) }) - return describe('when the web api http request takes a long time', function () { + describe('when the web api http request takes a long time', function () { before(function (done) { this.timeout = 10000 - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) sinon .stub(MockWebApi, 'getDocument') .callsFake((projectId, docId, callback) => { if (callback == null) { callback = function () {} } - return setTimeout(callback, 30000) + setTimeout(callback, 30000) }) - return done() + done() }) after(function () { - return MockWebApi.getDocument.restore() + MockWebApi.getDocument.restore() }) - return it('should return quickly(ish)', function (done) { + it('should return quickly(ish)', async function () { + const projectId = DocUpdaterClient.randomId() + const docId = DocUpdaterClient.randomId() const start = Date.now() - return DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - res.statusCode.should.equal(500) - const delta = Date.now() - start - expect(delta).to.be.below(20000) - return done() - } - ) + await expect(DocUpdaterClient.getDoc(projectId, docId)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 500) + const delta = Date.now() - start + expect(delta).to.be.below(20000) }) }) }) diff --git a/services/document-updater/test/acceptance/js/GettingProjectDocsTests.js b/services/document-updater/test/acceptance/js/GettingProjectDocsTests.js index 07bdd85a37..8b6e6b8d86 100644 --- a/services/document-updater/test/acceptance/js/GettingProjectDocsTests.js +++ b/services/document-updater/test/acceptance/js/GettingProjectDocsTests.js @@ -1,176 +1,77 @@ -/* eslint-disable - 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 { expect } = require('chai') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const { RequestFailedError } = require('@overleaf/fetch-utils') describe('Getting documents for project', function () { - before(function (done) { + before(async function () { this.lines = ['one', 'two', 'three'] this.version = 42 - return DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) describe('when project state hash does not match', function () { - before(function (done) { - this.projectStateHash = DocUpdaterClient.randomId() - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) - - MockWebApi.insertDoc(this.project_id, this.doc_id, { + it('should return a 409 Conflict response', async function () { + const projectStateHash = DocUpdaterClient.randomId() + const projectId = DocUpdaterClient.randomId() + const docId = DocUpdaterClient.randomId() + MockWebApi.insertDoc(projectId, docId, { lines: this.lines, version: this.version, }) - return DocUpdaterClient.preloadDoc( - this.project_id, - this.doc_id, - error => { - if (error != null) { - throw error - } - return DocUpdaterClient.getProjectDocs( - this.project_id, - this.projectStateHash, - (error, res, returnedDocs) => { - if (error) return done(error) - this.res = res - this.returnedDocs = returnedDocs - return done() - } - ) - } - ) - }) - - return it('should return a 409 Conflict response', function () { - return this.res.statusCode.should.equal(409) + await DocUpdaterClient.preloadDoc(projectId, docId) + await expect(DocUpdaterClient.getProjectDocs(projectId, projectStateHash)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 409) }) }) describe('when project state hash matches', function () { - before(function (done) { - this.projectStateHash = DocUpdaterClient.randomId() - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) - - MockWebApi.insertDoc(this.project_id, this.doc_id, { + it('should return the documents', async function () { + const projectStateHash = DocUpdaterClient.randomId() + const projectId = DocUpdaterClient.randomId() + const docId = DocUpdaterClient.randomId() + MockWebApi.insertDoc(projectId, docId, { lines: this.lines, version: this.version, }) - return DocUpdaterClient.preloadDoc( - this.project_id, - this.doc_id, - error => { - if (error != null) { - throw error - } - return DocUpdaterClient.getProjectDocs( - this.project_id, - this.projectStateHash, - (error, res0, returnedDocs0) => { - if (error) return done(error) - // set the hash - this.res0 = res0 - this.returnedDocs0 = returnedDocs0 - return DocUpdaterClient.getProjectDocs( - this.project_id, - this.projectStateHash, - (error, res, returnedDocs) => { - if (error) return done(error) - // the hash should now match - this.res = res - this.returnedDocs = returnedDocs - return done() - } - ) - } - ) - } + await DocUpdaterClient.preloadDoc(projectId, docId) + // set the hash + await expect(DocUpdaterClient.getProjectDocs(projectId, projectStateHash)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 409) + + const returnedDocs1 = await DocUpdaterClient.getProjectDocs( + projectId, + projectStateHash ) - }) - - it('should return a 200 response', function () { - return this.res.statusCode.should.equal(200) - }) - - return it('should return the documents', function () { - return this.returnedDocs.should.deep.equal([ - { _id: this.doc_id, lines: this.lines, v: this.version }, + // the hash should now match + returnedDocs1.should.deep.equal([ + { _id: docId, lines: this.lines, v: this.version }, ]) }) }) - return describe('when the doc has been removed', function () { - before(function (done) { - this.projectStateHash = DocUpdaterClient.randomId() - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) - - MockWebApi.insertDoc(this.project_id, this.doc_id, { + describe('when the doc has been removed', function () { + it('should return a 409 Conflict response', async function () { + const projectStateHash = DocUpdaterClient.randomId() + const projectId = DocUpdaterClient.randomId() + const docId = DocUpdaterClient.randomId() + MockWebApi.insertDoc(projectId, docId, { lines: this.lines, version: this.version, }) - return DocUpdaterClient.preloadDoc( - this.project_id, - this.doc_id, - error => { - if (error != null) { - throw error - } - return DocUpdaterClient.getProjectDocs( - this.project_id, - this.projectStateHash, - (error, res0, returnedDocs0) => { - if (error) return done(error) - // set the hash - this.res0 = res0 - this.returnedDocs0 = returnedDocs0 - return DocUpdaterClient.deleteDoc( - this.project_id, - this.doc_id, - (error, res, body) => { - if (error) return done(error) - // delete the doc - return DocUpdaterClient.getProjectDocs( - this.project_id, - this.projectStateHash, - (error, res1, returnedDocs) => { - if (error) return done(error) - // the hash would match, but the doc has been deleted - this.res = res1 - this.returnedDocs = returnedDocs - return done() - } - ) - } - ) - } - ) - } - ) - }) - - return it('should return a 409 Conflict response', function () { - return this.res.statusCode.should.equal(409) + await DocUpdaterClient.preloadDoc(projectId, docId) + await expect(DocUpdaterClient.getProjectDocs(projectId, projectStateHash)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 409) + await DocUpdaterClient.deleteDoc(projectId, docId) + // the hash would match, but the doc has been deleted + await expect(DocUpdaterClient.getProjectDocs(projectId, projectStateHash)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 409) }) }) }) diff --git a/services/document-updater/test/acceptance/js/PeekingADoc.js b/services/document-updater/test/acceptance/js/PeekingADoc.js index 94ce16eb54..8ab2000dea 100644 --- a/services/document-updater/test/acceptance/js/PeekingADoc.js +++ b/services/document-updater/test/acceptance/js/PeekingADoc.js @@ -2,16 +2,18 @@ const sinon = require('sinon') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const { expect } = require('chai') +const { RequestFailedError } = require('@overleaf/fetch-utils') describe('Peeking a document', function () { - before(function (done) { + before(async function () { this.lines = ['one', 'two', 'three'] this.version = 42 - return DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) describe('when the document is not loaded', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() sinon.spy(MockWebApi, 'getDocument') @@ -20,34 +22,22 @@ describe('Peeking a document', function () { lines: this.lines, version: this.version, }) - - return DocUpdaterClient.peekDoc( - this.project_id, - this.doc_id, - (error, res, returnedDoc) => { - this.error = error - this.res = res - this.returnedDoc = returnedDoc - return done() - } - ) }) after(function () { - return MockWebApi.getDocument.restore() + MockWebApi.getDocument.restore() }) - it('should return a 404 response', function () { - this.res.statusCode.should.equal(404) - }) - - it('should not load the document from the web API', function () { - return MockWebApi.getDocument.called.should.equal(false) + it('should not load the document from the web API and should return a 404 response', async function () { + await expect(DocUpdaterClient.peekDoc(this.project_id, this.doc_id)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 404) + MockWebApi.getDocument.called.should.equal(false) }) }) describe('when the document is already loaded', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() @@ -55,46 +45,28 @@ describe('Peeking a document', function () { lines: this.lines, version: this.version, }) - return DocUpdaterClient.preloadDoc( + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + sinon.spy(MockWebApi, 'getDocument') + this.returnedDoc = await DocUpdaterClient.peekDoc( this.project_id, - this.doc_id, - error => { - if (error != null) { - throw error - } - sinon.spy(MockWebApi, 'getDocument') - return DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, returnedDoc) => { - if (error) return done(error) - this.res = res - this.returnedDoc = returnedDoc - return done() - } - ) - } + this.doc_id ) }) after(function () { - return MockWebApi.getDocument.restore() - }) - - it('should return a 200 response', function () { - this.res.statusCode.should.equal(200) + MockWebApi.getDocument.restore() }) it('should return the document lines', function () { - return this.returnedDoc.lines.should.deep.equal(this.lines) + this.returnedDoc.lines.should.deep.equal(this.lines) }) it('should return the document version', function () { - return this.returnedDoc.version.should.equal(this.version) + this.returnedDoc.version.should.equal(this.version) }) it('should not load the document from the web API', function () { - return MockWebApi.getDocument.called.should.equal(false) + MockWebApi.getDocument.called.should.equal(false) }) }) }) diff --git a/services/document-updater/test/acceptance/js/RangesTests.js b/services/document-updater/test/acceptance/js/RangesTests.js index 424ea7e81e..26b10f245d 100644 --- a/services/document-updater/test/acceptance/js/RangesTests.js +++ b/services/document-updater/test/acceptance/js/RangesTests.js @@ -1,6 +1,6 @@ const sinon = require('sinon') const { expect } = require('chai') -const async = require('async') +const { setTimeout } = require('node:timers/promises') const { db, ObjectId } = require('../../../app/js/mongodb') const MockWebApi = require('./helpers/MockWebApi') @@ -11,12 +11,12 @@ const RangesManager = require('../../../app/js/RangesManager') const sandbox = sinon.createSandbox() describe('Ranges', function () { - before(function (done) { - DocUpdaterApp.ensureRunning(done) + before(async function () { + await DocUpdaterApp.ensureRunning() }) describe('tracking changes from ops', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.id_seed = '587357bd35e64f6157' @@ -48,59 +48,27 @@ describe('Ranges', function () { lines: this.doc.lines, version: 0, }) - const jobs = [] + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) for (const update of this.updates) { - jobs.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc.id, - update, - callback - ) - ) + await DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, update) } - - DocUpdaterApp.ensureRunning(error => { - if (error != null) { - throw error - } - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - async.series(jobs, error => { - if (error != null) { - throw error - } - done() - }) - }) - }) }) - it('should update the ranges', function (done) { - 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) - done() - } - ) + it('should update the ranges', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + const { ranges } = doc + 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) }) describe('Adding comments', function () { describe('standalone', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() + this.tid = DocUpdaterClient.randomId() this.doc = { id: DocUpdaterClient.randomId(), lines: ['foo bar baz'], @@ -108,9 +76,7 @@ describe('Ranges', function () { this.updates = [ { doc: this.doc.id, - op: [ - { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) }, - ], + op: [{ c: 'bar', p: 4, t: this.tid }], v: 0, }, ] @@ -118,52 +84,34 @@ describe('Ranges', function () { lines: this.doc.lines, version: 0, }) - const jobs = [] + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) for (const update of this.updates) { - jobs.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc.id, - update, - callback - ) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc.id, + update ) } - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - async.series(jobs, error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - }) - }) + await setTimeout(200) }) - it('should update the ranges', function (done) { - DocUpdaterClient.getDoc( + it('should update the ranges', async function () { + const doc = await 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) - done() - } + this.doc.id ) + const { ranges } = doc + const comment = ranges.comments[0] + comment.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid }) + comment.id.should.equal(this.tid) }) }) describe('with conflicting ops needing OT', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() + this.tid = DocUpdaterClient.randomId() this.doc = { id: DocUpdaterClient.randomId(), lines: ['foo bar baz'], @@ -177,9 +125,7 @@ describe('Ranges', function () { }, { doc: this.doc.id, - op: [ - { c: 'bar', p: 4, t: (this.tid = DocUpdaterClient.randomId()) }, - ], + op: [{ c: 'bar', p: 4, t: this.tid }], v: 0, }, ] @@ -187,51 +133,32 @@ describe('Ranges', function () { lines: this.doc.lines, version: 0, }) - const jobs = [] + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) for (const update of this.updates) { - jobs.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc.id, - update, - callback - ) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc.id, + update ) } - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - async.series(jobs, error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - }) - }) + await setTimeout(200) }) - it('should update the comments with the OT shifted comment', function (done) { - DocUpdaterClient.getDoc( + it('should update the comments with the OT shifted comment', async function () { + const doc = await 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 }) - done() - } + this.doc.id ) + const { ranges } = doc + const comment = ranges.comments[0] + comment.op.should.deep.equal({ c: 'bar', p: 7, t: this.tid }) }) }) }) }) describe('Loading ranges from persistence layer', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.id_seed = '587357bd35e64f6157' @@ -260,58 +187,41 @@ describe('Ranges', function () { ], }, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc.id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) - }) - }) - - it('should have preloaded the existing ranges', function (done) { - DocUpdaterClient.getDoc( + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) + await DocUpdaterClient.sendUpdate( 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 }) - done() - } + this.update ) + await setTimeout(200) }) - it('should flush the ranges to the persistence layer again', function (done) { - DocUpdaterClient.flushDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } + it('should have preloaded the existing ranges', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + const { changes } = doc.ranges + changes[0].op.should.deep.equal({ i: '123', p: 1 }) + changes[1].op.should.deep.equal({ i: '456', p: 5 }) + }) + + it('should flush the ranges to the persistence layer again', async function () { + await DocUpdaterClient.flushDoc(this.project_id, this.doc.id) + const doc = await new Promise((resolve, reject) => { MockWebApi.getDocument(this.project_id, this.doc.id, (error, doc) => { - if (error) return done(error) - const { changes } = doc.ranges - changes[0].op.should.deep.equal({ i: '123', p: 1 }) - changes[1].op.should.deep.equal({ i: '456', p: 5 }) - done() + if (error) { + reject(error) + } else { + resolve(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 }) }) }) describe('accepting a change', function () { - beforeEach(function (done) { + beforeEach(async function () { sandbox.spy(MockWebApi, 'setDocument') this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() @@ -330,105 +240,53 @@ describe('Ranges', function () { lines: this.doc.lines, version: 0, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc.id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - 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) - done() - } - ) - }, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc.id, + this.update + ) + await setTimeout(200) + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + const { ranges } = doc + 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) }) afterEach(function () { sandbox.restore() }) - it('should remove the change after accepting', function (done) { - DocUpdaterClient.acceptChange( + it('should remove the change after accepting', async function () { + await DocUpdaterClient.acceptChange( this.project_id, this.doc.id, - this.id_seed + '000001', - error => { - if (error != null) { - throw error - } - DocUpdaterClient.getDoc( - this.project_id, - this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - expect(data.ranges.changes).to.be.undefined - done() - } - ) - } + this.id_seed + '000001' ) + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + expect(doc.ranges.changes).to.be.undefined }) - it('should persist the ranges after accepting', function (done) { - DocUpdaterClient.flushDoc(this.project_id, this.doc.id, err => { - if (err) return done(err) - DocUpdaterClient.acceptChange( - this.project_id, - this.doc.id, - this.id_seed + '000001', - error => { - if (error != null) { - throw error - } + it('should persist the ranges after accepting', async function () { + await DocUpdaterClient.flushDoc(this.project_id, this.doc.id) + await DocUpdaterClient.acceptChange( + this.project_id, + this.doc.id, + this.id_seed + '000001' + ) + await DocUpdaterClient.flushDoc(this.project_id, this.doc.id) + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + expect(doc.ranges.changes).to.be.undefined - DocUpdaterClient.flushDoc(this.project_id, this.doc.id, err => { - if (err) return done(err) - DocUpdaterClient.getDoc( - this.project_id, - this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - expect(data.ranges.changes).to.be.undefined - - MockWebApi.setDocument - .calledWith(this.project_id, this.doc.id, ['a456aa'], 1, {}) - .should.equal(true) - done() - } - ) - }) - } - ) - }) + MockWebApi.setDocument + .calledWith(this.project_id, this.doc.id, ['a456aa'], 1, {}) + .should.equal(true) }) }) describe('accepting multiple changes', function () { - beforeEach(function (done) { + beforeEach(async function () { this.getHistoryUpdatesSpy = sandbox.spy( RangesManager, 'getHistoryUpdatesForAcceptedChanges' @@ -446,199 +304,156 @@ describe('Ranges', function () { version: 0, historyRangesSupport: true, }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) + this.id_seed_1 = 'tc_1' + this.id_seed_2 = 'tc_2' + this.id_seed_3 = 'tc_3' - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - - this.id_seed_1 = 'tc_1' - this.id_seed_2 = 'tc_2' - this.id_seed_3 = 'tc_3' - - this.updates = [ - { - doc: this.doc.id, - op: [{ d: 'bbb', p: 4 }], - v: 0, - meta: { - user_id: this.user_id, - tc: this.id_seed_1, - }, + this.updates = [ + { + doc: this.doc.id, + op: [{ d: 'bbb', p: 4 }], + v: 0, + meta: { + user_id: this.user_id, + tc: this.id_seed_1, }, - { - doc: this.doc.id, - op: [{ d: 'ccc', p: 5 }], - v: 1, - meta: { - user_id: this.user_id, - tc: this.id_seed_2, - }, + }, + { + doc: this.doc.id, + op: [{ d: 'ccc', p: 5 }], + v: 1, + meta: { + user_id: this.user_id, + tc: this.id_seed_2, }, - { - doc: this.doc.id, - op: [{ d: 'ddd', p: 6 }], - v: 2, - meta: { - user_id: this.user_id, - tc: this.id_seed_3, - }, + }, + { + doc: this.doc.id, + op: [{ d: 'ddd', p: 6 }], + v: 2, + meta: { + user_id: this.user_id, + tc: this.id_seed_3, }, - ] + }, + ] - DocUpdaterClient.sendUpdates( - this.project_id, - this.doc.id, - this.updates, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.getDoc( - this.project_id, - this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - const { ranges } = data - const changeOps = ranges.changes - .map(change => change.op) - .flat() - changeOps.should.deep.equal([ - { d: 'bbb', p: 4 }, - { d: 'ccc', p: 5 }, - { d: 'ddd', p: 6 }, - ]) - done() - } - ) - }, 200) - } - ) - }) + await DocUpdaterClient.sendUpdates( + this.project_id, + this.doc.id, + this.updates + ) + + await setTimeout(200) + + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + const { ranges } = doc + const changeOps = ranges.changes.map(change => change.op).flat() + changeOps.should.deep.equal([ + { d: 'bbb', p: 4 }, + { d: 'ccc', p: 5 }, + { d: 'ddd', p: 6 }, + ]) }) afterEach(function () { sandbox.restore() }) - it('accepting changes in order', function (done) { - DocUpdaterClient.acceptChanges( - this.project_id, - this.doc.id, - [ - this.id_seed_1 + '000001', - this.id_seed_2 + '000001', - this.id_seed_3 + '000001', - ], - error => { - if (error != null) { - throw error - } + it('accepting changes in order', async function () { + await DocUpdaterClient.acceptChanges(this.project_id, this.doc.id, [ + this.id_seed_1 + '000001', + this.id_seed_2 + '000001', + this.id_seed_3 + '000001', + ]) - const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0] - expect(historyUpdates[0]).to.deep.equal({ - doc: this.doc.id, - meta: { - pathname: '/a/b/c.tex', - doc_length: 10, - history_doc_length: 19, - ts: historyUpdates[0].meta.ts, - user_id: this.user_id, - }, - op: [{ p: 4, d: 'bbb' }], - }) + const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0] + expect(historyUpdates[0]).to.deep.equal({ + doc: this.doc.id, + meta: { + pathname: '/a/b/c.tex', + doc_length: 10, + history_doc_length: 19, + ts: historyUpdates[0].meta.ts, + user_id: this.user_id, + }, + op: [{ p: 4, d: 'bbb' }], + }) - expect(historyUpdates[1]).to.deep.equal({ - doc: this.doc.id, - meta: { - pathname: '/a/b/c.tex', - doc_length: 10, - history_doc_length: 16, - ts: historyUpdates[1].meta.ts, - user_id: this.user_id, - }, - op: [{ p: 5, d: 'ccc' }], - }) + expect(historyUpdates[1]).to.deep.equal({ + doc: this.doc.id, + meta: { + pathname: '/a/b/c.tex', + doc_length: 10, + history_doc_length: 16, + ts: historyUpdates[1].meta.ts, + user_id: this.user_id, + }, + op: [{ p: 5, d: 'ccc' }], + }) - expect(historyUpdates[2]).to.deep.equal({ - doc: this.doc.id, - meta: { - pathname: '/a/b/c.tex', - doc_length: 10, - history_doc_length: 13, - ts: historyUpdates[2].meta.ts, - user_id: this.user_id, - }, - op: [{ p: 6, d: 'ddd' }], - }) - - done() - } - ) + expect(historyUpdates[2]).to.deep.equal({ + doc: this.doc.id, + meta: { + pathname: '/a/b/c.tex', + doc_length: 10, + history_doc_length: 13, + ts: historyUpdates[2].meta.ts, + user_id: this.user_id, + }, + op: [{ p: 6, d: 'ddd' }], + }) }) - it('accepting changes in reverse order', function (done) { - DocUpdaterClient.acceptChanges( - this.project_id, - this.doc.id, - [ - this.id_seed_3 + '000001', - this.id_seed_2 + '000001', - this.id_seed_1 + '000001', - ], - error => { - if (error != null) { - throw error - } + it('accepting changes in reverse order', async function () { + await DocUpdaterClient.acceptChanges(this.project_id, this.doc.id, [ + this.id_seed_3 + '000001', + this.id_seed_2 + '000001', + this.id_seed_1 + '000001', + ]) - const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0] - expect(historyUpdates[0]).to.deep.equal({ - doc: this.doc.id, - meta: { - pathname: '/a/b/c.tex', - doc_length: 10, - history_doc_length: 19, - ts: historyUpdates[0].meta.ts, - user_id: this.user_id, - }, - op: [{ p: 4, d: 'bbb' }], - }) + const historyUpdates = this.getHistoryUpdatesSpy.returnValues[0] + expect(historyUpdates[0]).to.deep.equal({ + doc: this.doc.id, + meta: { + pathname: '/a/b/c.tex', + doc_length: 10, + history_doc_length: 19, + ts: historyUpdates[0].meta.ts, + user_id: this.user_id, + }, + op: [{ p: 4, d: 'bbb' }], + }) - expect(historyUpdates[1]).to.deep.equal({ - doc: this.doc.id, - meta: { - pathname: '/a/b/c.tex', - doc_length: 10, - history_doc_length: 16, - ts: historyUpdates[1].meta.ts, - user_id: this.user_id, - }, - op: [{ p: 5, d: 'ccc' }], - }) + expect(historyUpdates[1]).to.deep.equal({ + doc: this.doc.id, + meta: { + pathname: '/a/b/c.tex', + doc_length: 10, + history_doc_length: 16, + ts: historyUpdates[1].meta.ts, + user_id: this.user_id, + }, + op: [{ p: 5, d: 'ccc' }], + }) - expect(historyUpdates[2]).to.deep.equal({ - doc: this.doc.id, - meta: { - pathname: '/a/b/c.tex', - doc_length: 10, - history_doc_length: 13, - ts: historyUpdates[2].meta.ts, - user_id: this.user_id, - }, - op: [{ p: 6, d: 'ddd' }], - }) - - done() - } - ) + expect(historyUpdates[2]).to.deep.equal({ + doc: this.doc.id, + meta: { + pathname: '/a/b/c.tex', + doc_length: 10, + history_doc_length: 13, + ts: historyUpdates[2].meta.ts, + user_id: this.user_id, + }, + op: [{ p: 6, d: 'ddd' }], + }) }) }) describe('deleting a comment range', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc = { @@ -654,67 +469,33 @@ describe('Ranges', function () { lines: this.doc.lines, version: 0, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc.id, - this.update, - error => { - if (error != null) { - throw error - } - setTimeout(() => { - 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) - done() - } - ) - }, 200) - } - ) - }) - }) - - it('should remove the comment range', function (done) { - DocUpdaterClient.removeComment( + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) + await DocUpdaterClient.sendUpdate( this.project_id, this.doc.id, - this.tid, - (error, res) => { - if (error != null) { - throw error - } - expect(res.statusCode).to.equal(204) - DocUpdaterClient.getDoc( - this.project_id, - this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - expect(data.ranges.comments).to.be.undefined - done() - } - ) - } + this.update ) + await setTimeout(200) + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + const { ranges } = doc + const change = ranges.comments[0] + change.op.should.deep.equal({ c: 'bar', p: 4, t: this.tid }) + change.id.should.equal(this.tid) + }) + + it('should remove the comment range', async function () { + await DocUpdaterClient.removeComment( + this.project_id, + this.doc.id, + this.tid + ) + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + expect(doc.ranges.comments).to.be.undefined }) }) describe('tripping range size limit', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.id_seed = DocUpdaterClient.randomId() @@ -735,48 +516,21 @@ describe('Ranges', function () { lines: this.doc.lines, version: 0, }) - const jobs = [] + await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id) for (const update of this.updates) { - jobs.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc.id, - update, - callback - ) - ) + await DocUpdaterClient.sendUpdate(this.project_id, this.doc.id, update) } - DocUpdaterClient.preloadDoc(this.project_id, this.doc.id, error => { - if (error != null) { - throw error - } - async.series(jobs, error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - }) - }) + await setTimeout(200) }) - it('should not update the ranges', 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 - done() - } - ) + it('should not update the ranges', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + expect(doc.ranges.changes).to.be.undefined }) }) describe('deleting text surrounding a comment', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() @@ -813,70 +567,30 @@ describe('Ranges', function () { meta: { user_id: this.user_id }, }, ] - const jobs = [] + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) for (const update of this.updates) { - jobs.push(callback => - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - callback - ) - ) + await DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, update) } - DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { - if (error != null) { - throw error - } - async.series(jobs, function (error) { - if (error != null) { - throw error - } - setTimeout(() => { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, data) => { - if (error != null) { - throw error - } - done() - } - ) - }, 200) - }) - }) + await setTimeout(200) + await DocUpdaterClient.getDoc(this.project_id, this.doc_id) }) - it('should write a snapshot from before the destructive change', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, data) => { - if (error != null) { - return done(error) - } - db.docSnapshots - .find({ - project_id: new ObjectId(this.project_id), - doc_id: new 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, - }) - done() - }) - } - ) + it('should write a snapshot from before the destructive change', async function () { + await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + const docSnapshots = await db.docSnapshots + .find({ + project_id: new ObjectId(this.project_id), + doc_id: new ObjectId(this.doc_id), + }) + .toArray() + 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, + }) }) }) }) diff --git a/services/document-updater/test/acceptance/js/RejectingChangesTests.js b/services/document-updater/test/acceptance/js/RejectingChangesTests.js index 8b53e50ecf..c6bd61e1ef 100644 --- a/services/document-updater/test/acceptance/js/RejectingChangesTests.js +++ b/services/document-updater/test/acceptance/js/RejectingChangesTests.js @@ -8,12 +8,12 @@ const DocUpdaterApp = require('./helpers/DocUpdaterApp') const sandbox = sinon.createSandbox() describe('Rejecting Changes', function () { - before(function (done) { - DocUpdaterApp.ensureRunning(done) + before(async function () { + await DocUpdaterApp.ensureRunning() }) describe('rejecting a single change', function () { - beforeEach(function (done) { + beforeEach(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc = { @@ -38,11 +38,10 @@ describe('Rejecting Changes', function () { }, } - DocUpdaterClient.sendUpdate( + await DocUpdaterClient.sendUpdate( this.project_id, this.doc.id, - this.update, - done + this.update ) }) @@ -50,93 +49,54 @@ describe('Rejecting Changes', function () { sandbox.restore() }) - it('should reject the change and restore the original text', function (done) { - DocUpdaterClient.getDoc( + it('should reject the change and restore the original text', async function () { + const doc1 = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + + expect(doc1.ranges.changes).to.have.length(1) + const change = doc1.ranges.changes[0] + expect(change.op).to.deep.equal({ i: 'quick ', p: 4 }) + expect(change.id).to.equal(this.id_seed + '000001') + + expect(doc1.lines).to.deep.equal([ + 'the quick brown fox jumps over the lazy dog', + ]) + + const { rejectedChangeIds } = await DocUpdaterClient.rejectChanges( this.project_id, this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - - expect(data.ranges.changes).to.have.length(1) - const change = data.ranges.changes[0] - expect(change.op).to.deep.equal({ i: 'quick ', p: 4 }) - expect(change.id).to.equal(this.id_seed + '000001') - - expect(data.lines).to.deep.equal([ - 'the quick brown fox jumps over the lazy dog', - ]) - - DocUpdaterClient.rejectChanges( - this.project_id, - this.doc.id, - [change.id], - this.user_id, - (error, res, body) => { - if (error != null) { - throw error - } - - expect(res.statusCode).to.equal(200) - expect(body.rejectedChangeIds).to.be.an('array') - expect(body.rejectedChangeIds).to.include(change.id) - - DocUpdaterClient.getDoc( - this.project_id, - this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - - expect(data.ranges.changes || []).to.have.length(0) - expect(data.lines).to.deep.equal([ - 'the brown fox jumps over the lazy dog', - ]) - done() - } - ) - } - ) - } + [change.id], + this.user_id ) + + expect(rejectedChangeIds).to.be.an('array') + expect(rejectedChangeIds).to.include(change.id) + + const doc2 = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + + expect(doc2.ranges.changes || []).to.have.length(0) + expect(doc2.lines).to.deep.equal([ + 'the brown fox jumps over the lazy dog', + ]) }) - it('should return 200 status code with rejectedChangeIds on successful rejection', function (done) { - DocUpdaterClient.getDoc( + it('should return 200 status code with rejectedChangeIds on successful rejection', async function () { + const data = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + + const changeId = data.ranges.changes[0].id + + const { rejectedChangeIds } = await DocUpdaterClient.rejectChanges( this.project_id, this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - - const changeId = data.ranges.changes[0].id - - DocUpdaterClient.rejectChanges( - this.project_id, - this.doc.id, - [changeId], - this.user_id, - (error, res, body) => { - if (error != null) { - throw error - } - - expect(res.statusCode).to.equal(200) - expect(body.rejectedChangeIds).to.be.an('array') - expect(body.rejectedChangeIds).to.include(changeId) - done() - } - ) - } + [changeId], + this.user_id ) + expect(rejectedChangeIds).to.be.an('array') + expect(rejectedChangeIds).to.include(changeId) }) }) describe('rejecting multiple changes', function () { - beforeEach(function (done) { + beforeEach(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc = { @@ -174,11 +134,10 @@ describe('Rejecting Changes', function () { }, ] - DocUpdaterClient.sendUpdates( + await DocUpdaterClient.sendUpdates( this.project_id, this.doc.id, - this.updates, - done + this.updates ) }) @@ -186,62 +145,36 @@ describe('Rejecting Changes', function () { sandbox.restore() }) - it('should reject multiple changes in order', function (done) { - DocUpdaterClient.getDoc( + it('should reject multiple changes in order', async function () { + const data = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + expect(data.ranges.changes).to.have.length(2) + + expect(data.lines).to.deep.equal([ + 'the quick brown fox jumps over the dog', + ]) + + const changeIds = data.ranges.changes.map(change => change.id) + + const { rejectedChangeIds } = await DocUpdaterClient.rejectChanges( this.project_id, this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - - expect(data.ranges.changes).to.have.length(2) - - expect(data.lines).to.deep.equal([ - 'the quick brown fox jumps over the dog', - ]) - - const changeIds = data.ranges.changes.map(change => change.id) - - DocUpdaterClient.rejectChanges( - this.project_id, - this.doc.id, - changeIds, - this.user_id, - (error, res, body) => { - if (error != null) { - throw error - } - - expect(res.statusCode).to.equal(200) - expect(body.rejectedChangeIds).to.be.an('array') - expect(body.rejectedChangeIds).to.have.length(2) - expect(body.rejectedChangeIds).to.include.members(changeIds) - - DocUpdaterClient.getDoc( - this.project_id, - this.doc.id, - (error, res, data) => { - if (error != null) { - throw error - } - - expect(data.ranges.changes || []).to.have.length(0) - expect(data.lines).to.deep.equal([ - 'the brown fox jumps over the lazy dog', - ]) - done() - } - ) - } - ) - } + changeIds, + this.user_id ) + expect(rejectedChangeIds).to.be.an('array') + expect(rejectedChangeIds).to.have.length(2) + expect(rejectedChangeIds).to.include.members(changeIds) + + const data2 = await DocUpdaterClient.getDoc(this.project_id, this.doc.id) + expect(data2.ranges.changes || []).to.have.length(0) + expect(data2.lines).to.deep.equal([ + 'the brown fox jumps over the lazy dog', + ]) }) }) describe('error cases', function () { - beforeEach(function (done) { + beforeEach(async function () { this.project_id = DocUpdaterClient.randomId() this.user_id = DocUpdaterClient.randomId() this.doc = { @@ -255,46 +188,32 @@ describe('Rejecting Changes', function () { historyRangesSupport: true, }) - DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) - it('should handle rejection of non-existent changes gracefully', function (done) { + it('should handle rejection of non-existent changes gracefully', async function () { const nonExistentChangeId = 'nonexistent_change_id' - DocUpdaterClient.rejectChanges( + const { rejectedChangeIds } = await DocUpdaterClient.rejectChanges( this.project_id, this.doc.id, [nonExistentChangeId], - this.user_id, - (error, res, body) => { - // Should still return 200 with empty rejectedChangeIds if no changes were found to reject - if (error != null) { - throw error - } - expect(res.statusCode).to.equal(200) - expect(body.rejectedChangeIds).to.be.an('array') - expect(body.rejectedChangeIds).to.have.length(0) - done() - } + this.user_id ) + // Should still return 200 with empty rejectedChangeIds if no changes were found to reject + expect(rejectedChangeIds).to.be.an('array') + expect(rejectedChangeIds).to.have.length(0) }) - it('should handle empty change_ids array', function (done) { - DocUpdaterClient.rejectChanges( + it('should handle empty change_ids array', async function () { + const { rejectedChangeIds } = await DocUpdaterClient.rejectChanges( this.project_id, this.doc.id, [], - this.user_id, - (error, res, body) => { - if (error != null) { - throw error - } - expect(res.statusCode).to.equal(200) - expect(body.rejectedChangeIds).to.be.an('array') - expect(body.rejectedChangeIds).to.have.length(0) - done() - } + this.user_id ) + expect(rejectedChangeIds).to.be.an('array') + expect(rejectedChangeIds).to.have.length(0) }) }) }) diff --git a/services/document-updater/test/acceptance/js/SettingADocumentTests.js b/services/document-updater/test/acceptance/js/SettingADocumentTests.js index e1bc54dc90..192cd8f518 100644 --- a/services/document-updater/test/acceptance/js/SettingADocumentTests.js +++ b/services/document-updater/test/acceptance/js/SettingADocumentTests.js @@ -1,5 +1,6 @@ const sinon = require('sinon') const { expect } = require('chai') +const { setTimeout } = require('node:timers/promises') const Settings = require('@overleaf/settings') const docUpdaterRedis = require('@overleaf/redis-wrapper').createClient( Settings.redis.documentupdater @@ -10,10 +11,11 @@ const MockProjectHistoryApi = require('./helpers/MockProjectHistoryApi') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const { RequestFailedError } = require('@overleaf/fetch-utils') describe('Setting a document', function () { let numberOfReceivedUpdates = 0 - before(function (done) { + before(async function () { DocUpdaterClient.subscribeToAppliedOps(() => { numberOfReceivedUpdates++ }) @@ -36,7 +38,7 @@ describe('Setting a document', function () { sinon.spy(MockProjectHistoryApi, 'flushProject') sinon.spy(MockWebApi, 'setDocument') - DocUpdaterApp.ensureRunning(done) + await DocUpdaterApp.ensureRunning() }) after(function () { @@ -45,7 +47,7 @@ describe('Setting a document', function () { }) describe('when the updated doc exists in the doc updater', function () { - before(function (done) { + before(async function () { numberOfReceivedUpdates = 0 this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() @@ -53,39 +55,21 @@ describe('Setting a document', function () { lines: this.lines, version: this.version, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { - if (error) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error) { - throw error - } - setTimeout(() => { - DocUpdaterClient.setDocLines( - this.project_id, - this.doc_id, - this.newLines, - this.source, - this.user_id, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - this.body = body - done() - } - ) - }, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.update + ) + await setTimeout(200) + this.body = await DocUpdaterClient.setDocLines( + this.project_id, + this.doc_id, + this.newLines, + this.source, + this.user_id, + false + ) }) after(function () { @@ -93,10 +77,6 @@ describe('Setting a document', function () { MockWebApi.setDocument.resetHistory() }) - it('should return a 200 status code', function () { - this.statusCode.should.equal(200) - }) - it('should emit two updates (from sendUpdate and setDocLines)', function () { expect(numberOfReceivedUpdates).to.equal(2) }) @@ -107,32 +87,14 @@ describe('Setting a document', function () { .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) => { - if (error) { - return done(error) - } - doc.lines.should.deep.equal(this.newLines) - done() - } - ) + it('should update the lines in the doc updater', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.newLines) }) - it('should bump the version in the doc updater', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) { - return done(error) - } - doc.version.should.equal(this.version + 2) - done() - } - ) + it('should bump the version in the doc updater', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.version.should.equal(this.version + 2) }) it('should leave the document in redis', function (done) { @@ -153,51 +115,33 @@ describe('Setting a document', function () { }) describe('when doc has the same contents', function () { - beforeEach(function (done) { + beforeEach(async function () { numberOfReceivedUpdates = 0 - DocUpdaterClient.setDocLines( + await DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - this.body = body - done() - } + false ) }) - it('should not bump the version in doc updater', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) { - return done(error) - } - doc.version.should.equal(this.version + 2) - done() - } - ) + it('should not bump the version in doc updater', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.version.should.equal(this.version + 2) }) - it('should not emit any updates', function (done) { - setTimeout(() => { - expect(numberOfReceivedUpdates).to.equal(0) - done() - }, 100) // delay by 100ms: make sure we do not check too early! + it('should not emit any updates', async function () { + // delay by 100ms: make sure we do not check too early! + await setTimeout(100) + expect(numberOfReceivedUpdates).to.equal(0) }) }) }) describe('when the updated doc exists in the doc updater (history-ot)', function () { - before(function (done) { + before(async function () { numberOfReceivedUpdates = 0 this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() @@ -212,39 +156,21 @@ describe('Setting a document', function () { version: this.version, otMigrationStage: 1, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { - if (error) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.historyOTUpdate, - error => { - if (error) { - throw error - } - setTimeout(() => { - DocUpdaterClient.setDocLines( - this.project_id, - this.doc_id, - this.newLines, - this.source, - this.user_id, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - this.body = body - done() - } - ) - }, 200) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.historyOTUpdate + ) + await setTimeout(200) + this.body = await DocUpdaterClient.setDocLines( + this.project_id, + this.doc_id, + this.newLines, + this.source, + this.user_id, + false + ) }) after(function () { @@ -252,10 +178,6 @@ describe('Setting a document', function () { MockWebApi.setDocument.resetHistory() }) - it('should return a 200 status code', function () { - this.statusCode.should.equal(200) - }) - it('should emit two updates (from sendUpdate and setDocLines)', function () { expect(numberOfReceivedUpdates).to.equal(2) }) @@ -266,32 +188,14 @@ describe('Setting a document', function () { .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) => { - if (error) { - return done(error) - } - doc.lines.should.deep.equal(this.newLines) - done() - } - ) + it('should update the lines in the doc updater', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.lines.should.deep.equal(this.newLines) }) - it('should bump the version in the doc updater', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) { - return done(error) - } - doc.version.should.equal(this.version + 2) - done() - } - ) + it('should bump the version in the doc updater', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.version.should.equal(this.version + 2) }) it('should leave the document in redis', function (done) { @@ -314,51 +218,33 @@ describe('Setting a document', function () { }) describe('when doc has the same contents', function () { - beforeEach(function (done) { + beforeEach(async function () { numberOfReceivedUpdates = 0 - DocUpdaterClient.setDocLines( + this.body = await DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - this.body = body - done() - } + false ) }) - it('should not bump the version in doc updater', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) { - return done(error) - } - doc.version.should.equal(this.version + 2) - done() - } - ) + it('should not bump the version in doc updater', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + doc.version.should.equal(this.version + 2) }) - it('should not emit any updates', function (done) { - setTimeout(() => { - expect(numberOfReceivedUpdates).to.equal(0) - done() - }, 100) // delay by 100ms: make sure we do not check too early! + it('should not emit any updates', async function () { + // delay by 100ms: make sure we do not check too early! + await setTimeout(100) + expect(numberOfReceivedUpdates).to.equal(0) }) }) }) describe('when the updated doc does not exist in the doc updater', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() numberOfReceivedUpdates = 0 @@ -366,22 +252,15 @@ describe('Setting a document', function () { lines: this.lines, version: this.version, }) - DocUpdaterClient.setDocLines( + this.body = await DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - this.body = body - setTimeout(done, 200) - } + false ) + await setTimeout(200) }) after(function () { @@ -389,10 +268,6 @@ describe('Setting a document', function () { MockWebApi.setDocument.resetHistory() }) - it('should return a 200 status code', function () { - this.statusCode.should.equal(200) - }) - it('should emit an update', function () { expect(numberOfReceivedUpdates).to.equal(1) }) @@ -442,7 +317,7 @@ describe('Setting a document', function () { DOC_TOO_LARGE_TEST_CASES.forEach(testCase => { describe(testCase.desc, function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { @@ -453,21 +328,24 @@ describe('Setting a document', function () { while (JSON.stringify(this.newLines).length <= testCase.size) { 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) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - setTimeout(done, 200) + try { + await DocUpdaterClient.setDocLines( + this.project_id, + this.doc_id, + this.newLines, + this.source, + this.user_id, + false + ) + this.statusCode = 200 + } catch (err) { + if (err instanceof RequestFailedError) { + this.statusCode = err.response.status + } else { + throw err } - ) + } + await setTimeout(200) }) after(function () { @@ -490,7 +368,7 @@ describe('Setting a document', function () { }) describe('when the updated doc is large but under the bodyParser and HTTPController size limit', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() MockWebApi.insertDoc(this.project_id, this.doc_id, { @@ -504,22 +382,15 @@ describe('Setting a document', function () { 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.body = await DocUpdaterClient.setDocLines( this.project_id, this.doc_id, this.newLines, this.source, this.user_id, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - this.body = body - setTimeout(done, 200) - } + false ) + await setTimeout(200) }) after(function () { @@ -527,10 +398,6 @@ describe('Setting a document', function () { MockWebApi.setDocument.resetHistory() }) - it('should return a 200 status code', function () { - this.statusCode.should.equal(200) - }) - it('should send the updated doc lines to the web api', function () { MockWebApi.setDocument .calledWith(this.project_id, this.doc_id, this.newLines) @@ -563,44 +430,29 @@ describe('Setting a document', function () { }) describe('with the undo flag', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.doc_id = 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) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error) { - throw error - } - // Go back to old lines, with undo flag - DocUpdaterClient.setDocLines( - this.project_id, - this.doc_id, - this.lines, - this.source, - this.user_id, - true, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - setTimeout(done, 200) - } - ) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.update + ) + // Go back to old lines, with undo flag + await DocUpdaterClient.setDocLines( + this.project_id, + this.doc_id, + this.lines, + this.source, + this.user_id, + true + ) + await setTimeout(200) }) after(function () { @@ -608,61 +460,36 @@ describe('Setting a document', function () { MockWebApi.setDocument.resetHistory() }) - it('should undo the tracked changes', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, data) => { - if (error) { - throw error - } - const { ranges } = data - expect(ranges.changes).to.be.undefined - done() - } - ) + it('should undo the tracked changes', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + expect(doc.ranges.changes).to.be.undefined }) }) describe('without the undo flag', function () { - before(function (done) { + before(async function () { this.project_id = DocUpdaterClient.randomId() this.doc_id = 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) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.update, - error => { - if (error) { - throw error - } - // Go back to old lines, without undo flag - DocUpdaterClient.setDocLines( - this.project_id, - this.doc_id, - this.lines, - this.source, - this.user_id, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - setTimeout(done, 200) - } - ) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.update + ) + // Go back to old lines, without undo flag + await DocUpdaterClient.setDocLines( + this.project_id, + this.doc_id, + this.lines, + this.source, + this.user_id, + false + ) + await setTimeout(200) }) after(function () { @@ -670,19 +497,9 @@ describe('Setting a document', function () { MockWebApi.setDocument.resetHistory() }) - it('should not undo the tracked changes', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, data) => { - if (error) { - throw error - } - const { ranges } = data - expect(ranges.changes.length).to.equal(1) - done() - } - ) + it('should not undo the tracked changes', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + expect(doc.ranges.changes.length).to.equal(1) }) }) }) @@ -691,7 +508,8 @@ describe('Setting a document', function () { const lines = ['one', 'one and a half', 'two', 'three'] const userId = DocUpdaterClient.randomId() const ts = new Date().toISOString() - beforeEach(function (done) { + + beforeEach(async function () { numberOfReceivedUpdates = 0 this.newLines = ['one', 'two', 'three'] this.project_id = DocUpdaterClient.randomId() @@ -722,32 +540,20 @@ describe('Setting a document', function () { version: this.version, otMigrationStage: 1, }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { - if (error) { - throw error - } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - this.historyOTUpdate, - error => { - if (error) { - throw error - } - DocUpdaterClient.waitForPendingUpdates( - this.project_id, - this.doc_id, - done - ) - } - ) - }) + await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id) + await DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.historyOTUpdate + ) + await DocUpdaterClient.waitForPendingUpdates(this.doc_id) }) afterEach(function () { MockProjectHistoryApi.flushProject.resetHistory() MockWebApi.setDocument.resetHistory() }) + it('should record tracked changes', function (done) { docUpdaterRedis.get( Keys.docLines({ doc_id: this.doc_id }), @@ -776,19 +582,11 @@ describe('Setting a document', function () { ) }) - it('should apply the change', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, data) => { - if (error) { - throw error - } - expect(data.lines).to.deep.equal(this.newLines) - done() - } - ) + it('should apply the change', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + expect(doc.lines).to.deep.equal(this.newLines) }) + const cases = [ { name: 'when resetting the content', @@ -934,22 +732,14 @@ describe('Setting a document', function () { for (const { name, lines, want } of cases) { describe(name, function () { - beforeEach(function (done) { - DocUpdaterClient.setDocLines( + beforeEach(async function () { + this.body = await DocUpdaterClient.setDocLines( this.project_id, this.doc_id, lines, this.source, userId, - false, - (error, res, body) => { - if (error) { - return done(error) - } - this.statusCode = res.statusCode - this.body = body - done() - } + false ) }) it('should update accordingly', function (done) { diff --git a/services/document-updater/test/acceptance/js/SizeCheckTests.js b/services/document-updater/test/acceptance/js/SizeCheckTests.js index dd48461b8b..db365846c1 100644 --- a/services/document-updater/test/acceptance/js/SizeCheckTests.js +++ b/services/document-updater/test/acceptance/js/SizeCheckTests.js @@ -1,13 +1,15 @@ const { expect } = require('chai') +const { setTimeout } = require('node:timers/promises') const Settings = require('@overleaf/settings') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const { RequestFailedError } = require('@overleaf/fetch-utils') describe('SizeChecks', function () { - before(function (done) { - DocUpdaterApp.ensureRunning(done) + before(async function () { + await DocUpdaterApp.ensureRunning() }) beforeEach(function () { this.version = 0 @@ -34,40 +36,27 @@ describe('SizeChecks', function () { }) }) - it('should error when fetching the doc', function (done) { - DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res) => { - if (error) return done(error) - expect(res.statusCode).to.equal(500) - done() - }) + it('should error when fetching the doc', async function () { + await expect(DocUpdaterClient.getDoc(this.project_id, this.doc_id)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 500) }) describe('when trying to update', function () { - beforeEach(function (done) { + beforeEach(async function () { const update = { doc: this.doc_id, op: this.update.op, v: this.version, } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, update) + await setTimeout(200) }) - it('should still error when fetching the doc', function (done) { - DocUpdaterClient.getDoc(this.project_id, this.doc_id, (error, res) => { - if (error) return done(error) - expect(res.statusCode).to.equal(500) - done() - }) + it('should still error when fetching the doc', async function () { + await expect(DocUpdaterClient.getDoc(this.project_id, this.doc_id)) + .to.be.rejectedWith(RequestFailedError) + .and.eventually.have.nested.property('response.status', 500) }) }) }) @@ -91,48 +80,25 @@ describe('SizeChecks', function () { }) }) - it('should be able to fetch the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - expect(doc.lines).to.deep.equal(this.lines) - done() - } - ) + it('should be able to fetch the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + expect(doc.lines).to.deep.equal(this.lines) }) describe('when trying to update', function () { - beforeEach(function (done) { + beforeEach(async function () { const update = { doc: this.doc_id, op: this.update.op, v: this.version, } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, update) + await setTimeout(200) }) - it('should not update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - expect(doc.lines).to.deep.equal(this.lines) - done() - } - ) + it('should not update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + expect(doc.lines).to.deep.equal(this.lines) }) }) }) @@ -146,48 +112,25 @@ describe('SizeChecks', function () { }) }) - it('should be able to fetch the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - expect(doc.lines).to.deep.equal(this.lines) - done() - } - ) + it('should be able to fetch the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + expect(doc.lines).to.deep.equal(this.lines) }) describe('when trying to update', function () { - beforeEach(function (done) { + beforeEach(async function () { const update = { doc: this.doc_id, op: this.update.op, v: this.version, } - DocUpdaterClient.sendUpdate( - this.project_id, - this.doc_id, - update, - error => { - if (error != null) { - throw error - } - setTimeout(done, 200) - } - ) + await DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, update) + await setTimeout(200) }) - it('should not update the doc', function (done) { - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, doc) => { - if (error) return done(error) - expect(doc.lines).to.deep.equal(this.lines) - done() - } - ) + it('should not update the doc', async function () { + const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id) + expect(doc.lines).to.deep.equal(this.lines) }) }) }) diff --git a/services/document-updater/test/acceptance/js/helpers/DocUpdaterApp.js b/services/document-updater/test/acceptance/js/helpers/DocUpdaterApp.js index d34996ca7c..aa06e5f3ec 100644 --- a/services/document-updater/test/acceptance/js/helpers/DocUpdaterApp.js +++ b/services/document-updater/test/acceptance/js/helpers/DocUpdaterApp.js @@ -1,42 +1,26 @@ -// 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 - * DS205: Consider reworking code to avoid use of IIFEs - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ const app = require('../../../../app') -module.exports = { - running: false, - initing: false, - callbacks: [], - ensureRunning(callback) { - if (callback == null) { - callback = function () {} - } - if (this.running) { - return callback() - } else if (this.initing) { - return this.callbacks.push(callback) - } - this.initing = true - this.callbacks.push(callback) +function startApp() { + return new Promise((resolve, reject) => { app.listen(3003, '127.0.0.1', error => { - if (error != null) { - throw error + if (error) { + reject(error) + } else { + resolve() } - this.running = true - return (() => { - const result = [] - for (callback of Array.from(this.callbacks)) { - result.push(callback()) - } - return result - })() }) - }, + }) +} + +let appStartedPromise + +async function ensureRunning() { + if (!appStartedPromise) { + appStartedPromise = startApp() + } + await appStartedPromise +} + +module.exports = { + ensureRunning, } diff --git a/services/document-updater/test/acceptance/js/helpers/DocUpdaterClient.js b/services/document-updater/test/acceptance/js/helpers/DocUpdaterClient.js index a915aed95b..78337c536e 100644 --- a/services/document-updater/test/acceptance/js/helpers/DocUpdaterClient.js +++ b/services/document-updater/test/acceptance/js/helpers/DocUpdaterClient.js @@ -5,8 +5,8 @@ const rclient = require('@overleaf/redis-wrapper').createClient( Settings.redis.documentupdater ) const keys = Settings.redis.documentupdater.key_schema -const request = require('request').defaults({ jar: false }) -const async = require('async') +const { fetchJson, fetchNothing } = require('@overleaf/fetch-utils') +const { setTimeout } = require('node:timers/promises') const rclientSub = require('@overleaf/redis-wrapper').createClient( Settings.redis.pubsub @@ -14,6 +14,15 @@ const rclientSub = require('@overleaf/redis-wrapper').createClient( rclientSub.subscribe('applied-ops') rclientSub.setMaxListeners(0) +function getPendingUpdateListKey() { + const shard = _.random(0, Settings.dispatcherCount - 1) + if (shard === 0) { + return 'pending-updates-list' + } else { + return `pending-updates-list-${shard}` + } +} + module.exports = DocUpdaterClient = { randomId() { let str = '' @@ -23,234 +32,177 @@ module.exports = DocUpdaterClient = { return str }, - subscribeToAppliedOps(callback) { - rclientSub.on('message', callback) + subscribeToAppliedOps(messageHandler) { + rclientSub.on('message', messageHandler) }, - _getPendingUpdateListKey() { - const shard = _.random(0, Settings.dispatcherCount - 1) - if (shard === 0) { - return 'pending-updates-list' - } else { - return `pending-updates-list-${shard}` - } - }, - - sendUpdate(projectId, docId, update, callback) { - rclient.rpush( + async sendUpdate(projectId, docId, update) { + const docKey = `${projectId}:${docId}` + await rclient.rpush( keys.pendingUpdates({ doc_id: docId }), - JSON.stringify(update), - error => { - if (error) { - return callback(error) - } - const docKey = `${projectId}:${docId}` - rclient.sadd('DocsWithPendingUpdates', docKey, error => { - if (error) { - return callback(error) - } + JSON.stringify(update) + ) + await rclient.sadd('DocsWithPendingUpdates', docKey) + await rclient.rpush(getPendingUpdateListKey(), docKey) + }, - rclient.rpush( - DocUpdaterClient._getPendingUpdateListKey(), - docKey, - callback - ) - }) + async sendUpdates(projectId, docId, updates) { + await DocUpdaterClient.preloadDoc(projectId, docId) + for (const update of updates) { + await DocUpdaterClient.sendUpdate(projectId, docId, update) + } + await DocUpdaterClient.waitForPendingUpdates(docId) + }, + + async waitForPendingUpdates(docId) { + const maxRetries = 30 + const retryInterval = 100 + + for (let attempt = 0; attempt < maxRetries; attempt++) { + const length = await rclient.llen(keys.pendingUpdates({ doc_id: docId })) + + if (length === 0) { + return // Success - no pending updates } + + if (attempt < maxRetries - 1) { + await setTimeout(retryInterval) + } + } + throw new Error('updates still pending after maximum retries') + }, + + async getDoc(projectId, docId) { + return await fetchJson( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}` ) }, - sendUpdates(projectId, docId, updates, callback) { - DocUpdaterClient.preloadDoc(projectId, docId, error => { - if (error) { - return callback(error) - } - const jobs = updates.map(update => callback => { - DocUpdaterClient.sendUpdate(projectId, docId, update, callback) - }) - async.series(jobs, err => { - if (err) { - return callback(err) - } - DocUpdaterClient.waitForPendingUpdates(projectId, docId, callback) - }) - }) - }, - - waitForPendingUpdates(projectId, docId, callback) { - async.retry( - { times: 30, interval: 100 }, - cb => - rclient.llen(keys.pendingUpdates({ doc_id: docId }), (err, length) => { - if (err) { - return cb(err) - } - if (length > 0) { - cb(new Error('updates still pending')) - } else { - cb() - } - }), - callback + async getDocAndRecentOps(projectId, docId, fromVersion) { + return await fetchJson( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}` ) }, - getDoc(projectId, docId, callback) { - request.get( - `http://127.0.0.1:3003/project/${projectId}/doc/${docId}`, - (error, res, body) => { - if (body != null && res.statusCode >= 200 && res.statusCode < 300) { - body = JSON.parse(body) - } - callback(error, res, body) - } + async getProjectLastUpdatedAt(projectId) { + return await fetchJson( + `http://127.0.0.1:3003/project/${projectId}/last_updated_at` ) }, - getDocAndRecentOps(projectId, docId, fromVersion, callback) { - request.get( - `http://127.0.0.1:3003/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}`, - (error, res, body) => { - if (body != null && res.statusCode >= 200 && res.statusCode < 300) { - body = JSON.parse(body) - } - callback(error, res, body) - } + async preloadDoc(projectId, docId) { + await DocUpdaterClient.getDoc(projectId, docId) + }, + + async peekDoc(projectId, docId) { + return await fetchJson( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/peek` ) }, - getProjectLastUpdatedAt(projectId, callback) { - request.get( - `http://127.0.0.1:3003/project/${projectId}/last_updated_at`, - (error, res, body) => { - if (body != null && res.statusCode >= 200 && res.statusCode < 300) { - body = JSON.parse(body) - } - callback(error, res, body) - } - ) - }, - - preloadDoc(projectId, docId, callback) { - DocUpdaterClient.getDoc(projectId, docId, callback) - }, - - peekDoc(projectId, docId, callback) { - request.get( - `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/peek`, - (error, res, body) => { - if (body != null && res.statusCode >= 200 && res.statusCode < 300) { - body = JSON.parse(body) - } - callback(error, res, body) - } - ) - }, - - flushDoc(projectId, docId, callback) { - request.post( + async flushDoc(projectId, docId) { + return await fetchNothing( `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/flush`, - (error, res, body) => callback(error, res, body) + { method: 'POST' } ) }, - setDocLines(projectId, docId, lines, source, userId, undoing, callback) { - request.post( + async setDocLines(projectId, docId, lines, source, userId, undoing) { + return await fetchJson( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}`, { - url: `http://127.0.0.1:3003/project/${projectId}/doc/${docId}`, + method: 'POST', json: { lines, source, user_id: userId, undoing, }, - }, - (error, res, body) => callback(error, res, body) - ) - }, - - deleteDoc(projectId, docId, callback) { - request.del( - `http://127.0.0.1:3003/project/${projectId}/doc/${docId}`, - (error, res, body) => callback(error, res, body) - ) - }, - - flushProject(projectId, callback) { - request.post(`http://127.0.0.1:3003/project/${projectId}/flush`, callback) - }, - - deleteProject(projectId, callback) { - request.del(`http://127.0.0.1:3003/project/${projectId}`, callback) - }, - - deleteProjectOnShutdown(projectId, callback) { - request.del( - `http://127.0.0.1:3003/project/${projectId}?background=true&shutdown=true`, - callback - ) - }, - - flushOldProjects(callback) { - request.get( - 'http://127.0.0.1:3003/flush_queued_projects?min_delete_age=1', - callback - ) - }, - - acceptChange(projectId, docId, changeId, callback) { - request.post( - `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/change/${changeId}/accept`, - callback - ) - }, - - acceptChanges(projectId, docId, changeIds, callback) { - request.post( - { - url: `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/change/accept`, - json: { change_ids: changeIds }, - }, - callback - ) - }, - - rejectChanges(projectId, docId, changeIds, userId, callback) { - request.post( - { - url: `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/change/reject`, - json: { change_ids: changeIds, user_id: userId }, - }, - callback - ) - }, - - removeComment(projectId, docId, comment, callback) { - request.del( - `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/comment/${comment}`, - callback - ) - }, - - getProjectDocs(projectId, projectStateHash, callback) { - request.get( - `http://127.0.0.1:3003/project/${projectId}/doc?state=${projectStateHash}`, - (error, res, body) => { - if (body != null && res.statusCode >= 200 && res.statusCode < 300) { - body = JSON.parse(body) - } - callback(error, res, body) } ) }, - sendProjectUpdate(projectId, userId, updates, version, callback) { - request.post( - { - url: `http://127.0.0.1:3003/project/${projectId}`, - json: { userId, updates, version }, - }, - (error, res, body) => callback(error, res, body) + async deleteDoc(projectId, docId) { + return await fetchNothing( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}`, + { method: 'DELETE' } ) }, + + async flushProject(projectId) { + return await fetchNothing( + `http://127.0.0.1:3003/project/${projectId}/flush`, + { + method: 'POST', + } + ) + }, + + async deleteProject(projectId) { + return await fetchNothing(`http://127.0.0.1:3003/project/${projectId}`, { + method: 'DELETE', + }) + }, + + async deleteProjectOnShutdown(projectId) { + return await fetchNothing( + `http://127.0.0.1:3003/project/${projectId}?background=true&shutdown=true`, + { + method: 'DELETE', + } + ) + }, + + async flushOldProjects() { + await fetchNothing( + 'http://127.0.0.1:3003/flush_queued_projects?min_delete_age=1' + ) + }, + + async acceptChange(projectId, docId, changeId) { + await fetchNothing( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/change/${changeId}/accept`, + { method: 'POST' } + ) + }, + + async acceptChanges(projectId, docId, changeIds) { + await fetchNothing( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/change/accept`, + { + method: 'POST', + json: { change_ids: changeIds }, + } + ) + }, + + async rejectChanges(projectId, docId, changeIds, userId) { + return await fetchJson( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/change/reject`, + { + method: 'POST', + json: { change_ids: changeIds, user_id: userId }, + } + ) + }, + + async removeComment(projectId, docId, comment) { + await fetchNothing( + `http://127.0.0.1:3003/project/${projectId}/doc/${docId}/comment/${comment}`, + { method: 'DELETE' } + ) + }, + + async getProjectDocs(projectId, projectStateHash) { + return await fetchJson( + `http://127.0.0.1:3003/project/${projectId}/doc?state=${projectStateHash}` + ) + }, + + async sendProjectUpdate(projectId, userId, updates, version) { + await fetchNothing(`http://127.0.0.1:3003/project/${projectId}`, { + method: 'POST', + json: { userId, updates, version }, + }) + }, } diff --git a/services/document-updater/test/stress/js/run.js b/services/document-updater/test/stress/js/run.js deleted file mode 100644 index 1bda73c1aa..0000000000 --- a/services/document-updater/test/stress/js/run.js +++ /dev/null @@ -1,387 +0,0 @@ -/* eslint-disable - no-return-assign, - 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 - * DS202: Simplify dynamic range loops - * DS205: Consider reworking code to avoid use of IIFEs - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const DocUpdaterClient = require('../../acceptance/js/helpers/DocUpdaterClient') -// MockWebApi = require "../../acceptance/js/helpers/MockWebApi" -const assert = require('node:assert') -const async = require('async') - -const insert = function (string, pos, content) { - const result = string.slice(0, pos) + content + string.slice(pos) - return result -} - -const transform = function (op1, op2) { - if (op2.p < op1.p) { - return { - p: op1.p + op2.i.length, - i: op1.i, - } - } else { - return op1 - } -} - -class StressTestClient { - constructor(options) { - if (options == null) { - options = {} - } - this.options = options - if (this.options.updateDelay == null) { - this.options.updateDelay = 200 - } - this.project_id = this.options.project_id || DocUpdaterClient.randomId() - this.doc_id = this.options.doc_id || DocUpdaterClient.randomId() - this.pos = this.options.pos || 0 - this.content = this.options.content || '' - - this.client_id = DocUpdaterClient.randomId() - this.version = this.options.version || 0 - this.inflight_op = null - this.charCode = 0 - - this.counts = { - conflicts: 0, - local_updates: 0, - remote_updates: 0, - max_delay: 0, - } - - DocUpdaterClient.subscribeToAppliedOps((channel, update) => { - update = JSON.parse(update) - if (update.error != null) { - console.error(new Error(`Error from server: '${update.error}'`)) - return - } - if (update.doc_id === this.doc_id) { - return this.processReply(update) - } - }) - } - - sendUpdate() { - const data = String.fromCharCode(65 + (this.charCode++ % 26)) - this.content = insert(this.content, this.pos, data) - this.inflight_op = { - i: data, - p: this.pos++, - } - this.resendUpdate() - return (this.inflight_op_sent = Date.now()) - } - - resendUpdate() { - assert(this.inflight_op != null) - DocUpdaterClient.sendUpdate(this.project_id, this.doc_id, { - doc: this.doc_id, - op: [this.inflight_op], - v: this.version, - meta: { - source: this.client_id, - }, - dupIfSource: [this.client_id], - }) - return (this.update_timer = setTimeout(() => { - console.log( - `[${new Date()}] \t[${this.client_id.slice( - 0, - 4 - )}] WARN: Resending update after 5 seconds` - ) - return this.resendUpdate() - }, 5000)) - } - - processReply(update) { - if (update.op.v !== this.version) { - if (update.op.v < this.version) { - console.log( - `[${new Date()}] \t[${this.client_id.slice( - 0, - 4 - )}] WARN: Duplicate ack (already seen version)` - ) - return - } else { - console.error( - `[${new Date()}] \t[${this.client_id.slice( - 0, - 4 - )}] ERROR: Version jumped ahead (client: ${this.version}, op: ${ - update.op.v - })` - ) - } - } - this.version++ - if (update.op.meta.source === this.client_id) { - if (this.inflight_op != null) { - this.counts.local_updates++ - this.inflight_op = null - clearTimeout(this.update_timer) - const delay = Date.now() - this.inflight_op_sent - this.counts.max_delay = Math.max(this.counts.max_delay, delay) - return this.continue() - } else { - return console.log( - `[${new Date()}] \t[${this.client_id.slice( - 0, - 4 - )}] WARN: Duplicate ack` - ) - } - } else { - assert(update.op.op.length === 1) - this.counts.remote_updates++ - let externalOp = update.op.op[0] - if (this.inflight_op != null) { - this.counts.conflicts++ - this.inflight_op = transform(this.inflight_op, externalOp) - externalOp = transform(externalOp, this.inflight_op) - } - if (externalOp.p < this.pos) { - this.pos += externalOp.i.length - } - return (this.content = insert(this.content, externalOp.p, externalOp.i)) - } - } - - continue() { - if (this.updateCount > 0) { - this.updateCount-- - return setTimeout( - () => { - return this.sendUpdate() - }, - this.options.updateDelay * (0.5 + Math.random()) - ) - } else { - return this.updateCallback() - } - } - - runForNUpdates(n, callback) { - if (callback == null) { - callback = function () {} - } - this.updateCallback = callback - this.updateCount = n - return this.continue() - } - - check(callback) { - if (callback == null) { - callback = function () {} - } - return DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, body) => { - if (error != null) { - throw error - } - if (body.lines == null) { - return console.error( - `[${new Date()}] \t[${this.client_id.slice( - 0, - 4 - )}] ERROR: Invalid response from get doc (${this.doc_id})`, - body - ) - } - const content = body.lines.join('\n') - const { version } = body - if (content !== this.content) { - if (version === this.version) { - console.error( - `[${new Date()}] \t[${this.client_id.slice( - 0, - 4 - )}] Error: Client content does not match server.` - ) - console.error(`Server: ${content.split('a')}`) - console.error(`Client: ${this.content.split('a')}`) - } else { - console.error( - `[${new Date()}] \t[${this.client_id.slice( - 0, - 4 - )}] Error: Version mismatch (Server: '${version}', Client: '${ - this.version - }')` - ) - } - } - - if (!this.isContentValid(this.content)) { - const iterable = this.content.split('') - for (let i = 0; i < iterable.length; i++) { - const chunk = iterable[i] - if (chunk != null && chunk !== 'a') { - console.log(chunk, i) - } - } - throw new Error('bad content') - } - return callback() - } - ) - } - - isChunkValid(chunk) { - const char = 0 - for (let i = 0; i < chunk.length; i++) { - const letter = chunk[i] - if (letter.charCodeAt(0) !== 65 + (i % 26)) { - console.error( - `[${new Date()}] \t[${this.client_id.slice(0, 4)}] Invalid Chunk:`, - chunk - ) - return false - } - } - return true - } - - isContentValid(content) { - for (const chunk of Array.from(content.split('a'))) { - if (chunk != null && chunk !== '') { - if (!this.isChunkValid(chunk)) { - console.error( - `[${new Date()}] \t[${this.client_id.slice(0, 4)}] Invalid content`, - content - ) - return false - } - } - } - return true - } -} - -const checkDocument = function (projectId, docId, clients, callback) { - if (callback == null) { - callback = function () {} - } - const jobs = clients.map(client => cb => client.check(cb)) - return async.parallel(jobs, callback) -} - -const printSummary = function (docId, clients) { - const slot = require('cluster-key-slot') - const now = new Date() - console.log( - `[${now}] [${docId.slice(0, 4)} (slot: ${slot(docId)})] ${ - clients.length - } clients...` - ) - return (() => { - const result = [] - for (const client of Array.from(clients)) { - console.log( - `[${now}] \t[${client.client_id.slice(0, 4)}] { local: ${ - client.counts.local_updates - }, remote: ${client.counts.remote_updates}, conflicts: ${ - client.counts.conflicts - }, max_delay: ${client.counts.max_delay} }` - ) - result.push( - (client.counts = { - local_updates: 0, - remote_updates: 0, - conflicts: 0, - max_delay: 0, - }) - ) - } - return result - })() -} - -const CLIENT_COUNT = parseInt(process.argv[2], 10) -const UPDATE_DELAY = parseInt(process.argv[3], 10) -const SAMPLE_INTERVAL = parseInt(process.argv[4], 10) - -for (const docAndProjectId of Array.from(process.argv.slice(5))) { - ;(function (docAndProjectId) { - const [projectId, docId] = Array.from(docAndProjectId.split(':')) - console.log({ projectId, docId }) - return DocUpdaterClient.setDocLines( - projectId, - docId, - [new Array(CLIENT_COUNT + 2).join('a')], - null, - null, - error => { - if (error != null) { - throw error - } - return DocUpdaterClient.getDoc(projectId, docId, (error, res, body) => { - let runBatch - if (error != null) { - throw error - } - if (body.lines == null) { - return console.error( - `[${new Date()}] ERROR: Invalid response from get doc (${docId})`, - body - ) - } - const content = body.lines.join('\n') - const { version } = body - - const clients = [] - for ( - let pos = 1, end = CLIENT_COUNT, asc = end >= 1; - asc ? pos <= end : pos >= end; - asc ? pos++ : pos-- - ) { - ;(function (pos) { - const client = new StressTestClient({ - doc_id: docId, - project_id: projectId, - content, - pos, - version, - updateDelay: UPDATE_DELAY, - }) - return clients.push(client) - })(pos) - } - - return (runBatch = function () { - const jobs = clients.map( - client => cb => - client.runForNUpdates(SAMPLE_INTERVAL / UPDATE_DELAY, cb) - ) - return async.parallel(jobs, error => { - if (error != null) { - throw error - } - printSummary(docId, clients) - return checkDocument(projectId, docId, clients, error => { - if (error != null) { - throw error - } - return runBatch() - }) - }) - })() - }) - } - ) - })(docAndProjectId) -}