diff --git a/services/real-time/test/acceptance/js/ApplyUpdateTests.js b/services/real-time/test/acceptance/js/ApplyUpdateTests.js index bc9d315b0f..2c5b753f29 100644 --- a/services/real-time/test/acceptance/js/ApplyUpdateTests.js +++ b/services/real-time/test/acceptance/js/ApplyUpdateTests.js @@ -12,313 +12,436 @@ * DS201: Simplify complex destructure assignments * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const async = require("async"); -const chai = require("chai"); -const { - expect -} = chai; -chai.should(); +const async = require('async') +const chai = require('chai') +const { expect } = chai +chai.should() -const RealTimeClient = require("./helpers/RealTimeClient"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const FixturesManager = require('./helpers/FixturesManager') -const settings = require("settings-sharelatex"); -const redis = require("redis-sharelatex"); -const rclient = redis.createClient(settings.redis.documentupdater); +const settings = require('settings-sharelatex') +const redis = require('redis-sharelatex') +const rclient = redis.createClient(settings.redis.documentupdater) -const redisSettings = settings.redis; +const redisSettings = settings.redis -describe("applyOtUpdate", function() { - before(function() { - return this.update = { - op: [{i: "foo", p: 42}] - };}); - describe("when authorized", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readAndWrite" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, +describe('applyOtUpdate', function () { + before(function () { + return (this.update = { + op: [{ i: 'foo', p: 42 }] + }) + }) + describe('when authorized', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, cb); - }, - - cb => { - return this.client.emit("applyOtUpdate", this.doc_id, this.update, cb); - } - ], done); - }); - - it("should push the doc into the pending updates list", function(done) { - rclient.lrange("pending-updates-list", 0, -1, (error, ...rest) => { - const [doc_id] = Array.from(rest[0]); - doc_id.should.equal(`${this.project_id}:${this.doc_id}`); - return done(); - }); - return null; - }); + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - it("should push the update into redis", function(done) { - rclient.lrange(redisSettings.documentupdater.key_schema.pendingUpdates({doc_id: this.doc_id}), 0, -1, (error, ...rest) => { - let [update] = Array.from(rest[0]); - update = JSON.parse(update); - update.op.should.deep.equal(this.update.op); - update.meta.should.deep.equal({ - source: this.client.publicId, - user_id: this.user_id - }); - return done(); - }); - return null; - }); + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - return after(function(done) { - return async.series([ - cb => rclient.del("pending-updates-list", cb), - cb => rclient.del("DocsWithPendingUpdates", `${this.project_id}:${this.doc_id}`, cb), - cb => rclient.del(redisSettings.documentupdater.key_schema.pendingUpdates(this.doc_id), cb) - ], done); - }); - }); - - describe("when authorized with a huge edit update", function() { - before(function(done) { - this.update = { - op: { - p: 12, - t: "update is too large".repeat(1024 * 400) // >7MB - } - }; - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readAndWrite" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return this.client.emit('joinDoc', this.doc_id, cb) + }, - cb => { - this.client = RealTimeClient.connect(); - this.client.on("connectionAccepted", cb); - return this.client.on("otUpdateError", otUpdateError => { - this.otUpdateError = otUpdateError; - - }); - }, + (cb) => { + return this.client.emit( + 'applyOtUpdate', + this.doc_id, + this.update, + cb + ) + } + ], + done + ) + }) - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, + it('should push the doc into the pending updates list', function (done) { + rclient.lrange('pending-updates-list', 0, -1, (error, ...rest) => { + const [doc_id] = Array.from(rest[0]) + doc_id.should.equal(`${this.project_id}:${this.doc_id}`) + return done() + }) + return null + }) - cb => { - return this.client.emit("joinDoc", this.doc_id, cb); - }, + it('should push the update into redis', function (done) { + rclient.lrange( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id + }), + 0, + -1, + (error, ...rest) => { + let [update] = Array.from(rest[0]) + update = JSON.parse(update) + update.op.should.deep.equal(this.update.op) + update.meta.should.deep.equal({ + source: this.client.publicId, + user_id: this.user_id + }) + return done() + } + ) + return null + }) - cb => { - return this.client.emit("applyOtUpdate", this.doc_id, this.update, error => { - this.error = error; - return cb(); - }); - } - ], done); - }); + return after(function (done) { + return async.series( + [ + (cb) => rclient.del('pending-updates-list', cb), + (cb) => + rclient.del( + 'DocsWithPendingUpdates', + `${this.project_id}:${this.doc_id}`, + cb + ), + (cb) => + rclient.del( + redisSettings.documentupdater.key_schema.pendingUpdates( + this.doc_id + ), + cb + ) + ], + done + ) + }) + }) - it("should not return an error", function() { - return expect(this.error).to.not.exist; - }); + describe('when authorized with a huge edit update', function () { + before(function (done) { + this.update = { + op: { + p: 12, + t: 'update is too large'.repeat(1024 * 400) // >7MB + } + } + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, - it("should send an otUpdateError to the client", function(done) { - return setTimeout(() => { - expect(this.otUpdateError).to.exist; - return done(); - } - , 300); - }); + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - it("should disconnect the client", function(done) { - return setTimeout(() => { - this.client.socket.connected.should.equal(false); - return done(); - } - , 300); - }); + (cb) => { + this.client = RealTimeClient.connect() + this.client.on('connectionAccepted', cb) + return this.client.on('otUpdateError', (otUpdateError) => { + this.otUpdateError = otUpdateError + }) + }, - return it("should not put the update in redis", function(done) { - rclient.llen(redisSettings.documentupdater.key_schema.pendingUpdates({doc_id: this.doc_id}), (error, len) => { - len.should.equal(0); - return done(); - }); - return null; - }); - }); + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - describe("when authorized to read-only with an edit update", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readOnly" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return this.client.emit('joinDoc', this.doc_id, cb) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, cb); - }, - - cb => { - return this.client.emit("applyOtUpdate", this.doc_id, this.update, error => { - this.error = error; - return cb(); - }); - } - ], done); - }); - - it("should return an error", function() { - return expect(this.error).to.exist; - }); - - it("should disconnect the client", function(done) { - return setTimeout(() => { - this.client.socket.connected.should.equal(false); - return done(); - } - , 300); - }); - - return it("should not put the update in redis", function(done) { - rclient.llen(redisSettings.documentupdater.key_schema.pendingUpdates({doc_id: this.doc_id}), (error, len) => { - len.should.equal(0); - return done(); - }); - return null; - }); - }); - - return describe("when authorized to read-only with a comment update", function() { - before(function(done) { - this.comment_update = { - op: [{c: "foo", p: 42}] - }; - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readOnly" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return this.client.emit( + 'applyOtUpdate', + this.doc_id, + this.update, + (error) => { + this.error = error + return cb() + } + ) + } + ], + done + ) + }) - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, cb); - }, - - cb => { - return this.client.emit("applyOtUpdate", this.doc_id, this.comment_update, cb); - } - ], done); - }); - - it("should push the doc into the pending updates list", function(done) { - rclient.lrange("pending-updates-list", 0, -1, (error, ...rest) => { - const [doc_id] = Array.from(rest[0]); - doc_id.should.equal(`${this.project_id}:${this.doc_id}`); - return done(); - }); - return null; - }); + it('should not return an error', function () { + return expect(this.error).to.not.exist + }) - it("should push the update into redis", function(done) { - rclient.lrange(redisSettings.documentupdater.key_schema.pendingUpdates({doc_id: this.doc_id}), 0, -1, (error, ...rest) => { - let [update] = Array.from(rest[0]); - update = JSON.parse(update); - update.op.should.deep.equal(this.comment_update.op); - update.meta.should.deep.equal({ - source: this.client.publicId, - user_id: this.user_id - }); - return done(); - }); - return null; - }); + it('should send an otUpdateError to the client', function (done) { + return setTimeout(() => { + expect(this.otUpdateError).to.exist + return done() + }, 300) + }) - return after(function(done) { - return async.series([ - cb => rclient.del("pending-updates-list", cb), - cb => rclient.del("DocsWithPendingUpdates", `${this.project_id}:${this.doc_id}`, cb), - cb => rclient.del(redisSettings.documentupdater.key_schema.pendingUpdates({doc_id: this.doc_id}), cb) - ], done); - }); - }); -}); + it('should disconnect the client', function (done) { + return setTimeout(() => { + this.client.socket.connected.should.equal(false) + return done() + }, 300) + }) + + return it('should not put the update in redis', function (done) { + rclient.llen( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id + }), + (error, len) => { + len.should.equal(0) + return done() + } + ) + return null + }) + }) + + describe('when authorized to read-only with an edit update', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readOnly' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, + + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, + + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, + + (cb) => { + return this.client.emit('joinDoc', this.doc_id, cb) + }, + + (cb) => { + return this.client.emit( + 'applyOtUpdate', + this.doc_id, + this.update, + (error) => { + this.error = error + return cb() + } + ) + } + ], + done + ) + }) + + it('should return an error', function () { + return expect(this.error).to.exist + }) + + it('should disconnect the client', function (done) { + return setTimeout(() => { + this.client.socket.connected.should.equal(false) + return done() + }, 300) + }) + + return it('should not put the update in redis', function (done) { + rclient.llen( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id + }), + (error, len) => { + len.should.equal(0) + return done() + } + ) + return null + }) + }) + + return describe('when authorized to read-only with a comment update', function () { + before(function (done) { + this.comment_update = { + op: [{ c: 'foo', p: 42 }] + } + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readOnly' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, + + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, + + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, + + (cb) => { + return this.client.emit('joinDoc', this.doc_id, cb) + }, + + (cb) => { + return this.client.emit( + 'applyOtUpdate', + this.doc_id, + this.comment_update, + cb + ) + } + ], + done + ) + }) + + it('should push the doc into the pending updates list', function (done) { + rclient.lrange('pending-updates-list', 0, -1, (error, ...rest) => { + const [doc_id] = Array.from(rest[0]) + doc_id.should.equal(`${this.project_id}:${this.doc_id}`) + return done() + }) + return null + }) + + it('should push the update into redis', function (done) { + rclient.lrange( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id + }), + 0, + -1, + (error, ...rest) => { + let [update] = Array.from(rest[0]) + update = JSON.parse(update) + update.op.should.deep.equal(this.comment_update.op) + update.meta.should.deep.equal({ + source: this.client.publicId, + user_id: this.user_id + }) + return done() + } + ) + return null + }) + + return after(function (done) { + return async.series( + [ + (cb) => rclient.del('pending-updates-list', cb), + (cb) => + rclient.del( + 'DocsWithPendingUpdates', + `${this.project_id}:${this.doc_id}`, + cb + ), + (cb) => + rclient.del( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id + }), + cb + ) + ], + done + ) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/ClientTrackingTests.js b/services/real-time/test/acceptance/js/ClientTrackingTests.js index 75e23d0719..079baadb58 100644 --- a/services/real-time/test/acceptance/js/ClientTrackingTests.js +++ b/services/real-time/test/acceptance/js/ClientTrackingTests.js @@ -12,187 +12,244 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const chai = require("chai"); -const { - expect -} = chai; -chai.should(); +const chai = require('chai') +const { expect } = chai +chai.should() -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockWebServer = require("./helpers/MockWebServer"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockWebServer = require('./helpers/MockWebServer') +const FixturesManager = require('./helpers/FixturesManager') -const async = require("async"); +const async = require('async') -describe("clientTracking", function() { - describe("when a client updates its cursor location", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { name: "Test Project" } - }, (error, {user_id, project_id}) => { this.user_id = user_id; this.project_id = project_id; return cb(); }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, - - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, - - cb => { - this.clientB = RealTimeClient.connect(); - return this.clientB.on("connectionAccepted", cb); - }, - - cb => { - return this.clientA.emit("joinProject", { - project_id: this.project_id - }, cb); - }, - - cb => { - return this.clientA.emit("joinDoc", this.doc_id, cb); - }, - - cb => { - return this.clientB.emit("joinProject", { - project_id: this.project_id - }, cb); - }, - - cb => { - this.updates = []; - this.clientB.on("clientTracking.clientUpdated", data => { - return this.updates.push(data); - }); +describe('clientTracking', function () { + describe('when a client updates its cursor location', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { name: 'Test Project' } + }, + (error, { user_id, project_id }) => { + this.user_id = user_id + this.project_id = project_id + return cb() + } + ) + }, - return this.clientA.emit("clientTracking.updatePosition", { - row: (this.row = 42), - column: (this.column = 36), - doc_id: this.doc_id - }, (error) => { - if (error != null) { throw error; } - return setTimeout(cb, 300); - }); - } // Give the message a chance to reach client B. - ], done); - }); - - it("should tell other clients about the update", function() { - return this.updates.should.deep.equal([ - { - row: this.row, - column: this.column, - doc_id: this.doc_id, - id: this.clientA.publicId, - user_id: this.user_id, - name: "Joe Bloggs" - } - ]); - }); - - return it("should record the update in getConnectedUsers", function(done) { - return this.clientB.emit("clientTracking.getConnectedUsers", (error, users) => { - for (const user of Array.from(users)) { - if (user.client_id === this.clientA.publicId) { - expect(user.cursorData).to.deep.equal({ - row: this.row, - column: this.column, - doc_id: this.doc_id - }); - return done(); - } - } - throw new Error("user was never found"); - }); - }); - }); - - return describe("when an anonymous client updates its cursor location", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { name: "Test Project" }, - publicAccess: "readAndWrite" - }, (error, {user_id, project_id}) => { this.user_id = user_id; this.project_id = project_id; return cb(); }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, - - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - return this.clientA.emit("joinProject", { - project_id: this.project_id - }, cb); - }, - - cb => { - return RealTimeClient.setSession({}, cb); - }, - - cb => { - this.anonymous = RealTimeClient.connect(); - return this.anonymous.on("connectionAccepted", cb); - }, - - cb => { - return this.anonymous.emit("joinProject", { - project_id: this.project_id - }, cb); - }, - - cb => { - return this.anonymous.emit("joinDoc", this.doc_id, cb); - }, - - cb => { - this.updates = []; - this.clientA.on("clientTracking.clientUpdated", data => { - return this.updates.push(data); - }); + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, - return this.anonymous.emit("clientTracking.updatePosition", { - row: (this.row = 42), - column: (this.column = 36), - doc_id: this.doc_id - }, (error) => { - if (error != null) { throw error; } - return setTimeout(cb, 300); - }); - } // Give the message a chance to reach client B. - ], done); - }); - - return it("should tell other clients about the update", function() { - return this.updates.should.deep.equal([ - { - row: this.row, - column: this.column, - doc_id: this.doc_id, - id: this.anonymous.publicId, - user_id: "anonymous-user", - name: "" - } - ]); - }); -}); -}); + (cb) => { + this.clientB = RealTimeClient.connect() + return this.clientB.on('connectionAccepted', cb) + }, + + (cb) => { + return this.clientA.emit( + 'joinProject', + { + project_id: this.project_id + }, + cb + ) + }, + + (cb) => { + return this.clientA.emit('joinDoc', this.doc_id, cb) + }, + + (cb) => { + return this.clientB.emit( + 'joinProject', + { + project_id: this.project_id + }, + cb + ) + }, + + (cb) => { + this.updates = [] + this.clientB.on('clientTracking.clientUpdated', (data) => { + return this.updates.push(data) + }) + + return this.clientA.emit( + 'clientTracking.updatePosition', + { + row: (this.row = 42), + column: (this.column = 36), + doc_id: this.doc_id + }, + (error) => { + if (error != null) { + throw error + } + return setTimeout(cb, 300) + } + ) + } // Give the message a chance to reach client B. + ], + done + ) + }) + + it('should tell other clients about the update', function () { + return this.updates.should.deep.equal([ + { + row: this.row, + column: this.column, + doc_id: this.doc_id, + id: this.clientA.publicId, + user_id: this.user_id, + name: 'Joe Bloggs' + } + ]) + }) + + return it('should record the update in getConnectedUsers', function (done) { + return this.clientB.emit( + 'clientTracking.getConnectedUsers', + (error, users) => { + for (const user of Array.from(users)) { + if (user.client_id === this.clientA.publicId) { + expect(user.cursorData).to.deep.equal({ + row: this.row, + column: this.column, + doc_id: this.doc_id + }) + return done() + } + } + throw new Error('user was never found') + } + ) + }) + }) + + return describe('when an anonymous client updates its cursor location', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { name: 'Test Project' }, + publicAccess: 'readAndWrite' + }, + (error, { user_id, project_id }) => { + this.user_id = user_id + this.project_id = project_id + return cb() + } + ) + }, + + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, + + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, + + (cb) => { + return this.clientA.emit( + 'joinProject', + { + project_id: this.project_id + }, + cb + ) + }, + + (cb) => { + return RealTimeClient.setSession({}, cb) + }, + + (cb) => { + this.anonymous = RealTimeClient.connect() + return this.anonymous.on('connectionAccepted', cb) + }, + + (cb) => { + return this.anonymous.emit( + 'joinProject', + { + project_id: this.project_id + }, + cb + ) + }, + + (cb) => { + return this.anonymous.emit('joinDoc', this.doc_id, cb) + }, + + (cb) => { + this.updates = [] + this.clientA.on('clientTracking.clientUpdated', (data) => { + return this.updates.push(data) + }) + + return this.anonymous.emit( + 'clientTracking.updatePosition', + { + row: (this.row = 42), + column: (this.column = 36), + doc_id: this.doc_id + }, + (error) => { + if (error != null) { + throw error + } + return setTimeout(cb, 300) + } + ) + } // Give the message a chance to reach client B. + ], + done + ) + }) + + return it('should tell other clients about the update', function () { + return this.updates.should.deep.equal([ + { + row: this.row, + column: this.column, + doc_id: this.doc_id, + id: this.anonymous.publicId, + user_id: 'anonymous-user', + name: '' + } + ]) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/DrainManagerTests.js b/services/real-time/test/acceptance/js/DrainManagerTests.js index 1ff4a4afbb..d312d34aa9 100644 --- a/services/real-time/test/acceptance/js/DrainManagerTests.js +++ b/services/real-time/test/acceptance/js/DrainManagerTests.js @@ -8,98 +8,128 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const RealTimeClient = require("./helpers/RealTimeClient"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const FixturesManager = require('./helpers/FixturesManager') -const { - expect -} = require("chai"); +const { expect } = require('chai') -const async = require("async"); -const request = require("request"); +const async = require('async') +const request = require('request') -const Settings = require("settings-sharelatex"); +const Settings = require('settings-sharelatex') -const drain = function(rate, callback) { - request.post({ - url: `http://localhost:3026/drain?rate=${rate}`, - auth: { - user: Settings.internal.realTime.user, - pass: Settings.internal.realTime.pass - } - }, (error, response, data) => callback(error, data)); - return null; -}; +const drain = function (rate, callback) { + request.post( + { + url: `http://localhost:3026/drain?rate=${rate}`, + auth: { + user: Settings.internal.realTime.user, + pass: Settings.internal.realTime.pass + } + }, + (error, response, data) => callback(error, data) + ) + return null +} -describe("DrainManagerTests", function() { - before(function(done) { - FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return done(); }); - return null; - }); +describe('DrainManagerTests', function () { + before(function (done) { + FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return done() + } + ) + return null + }) - before(function(done) { - // cleanup to speedup reconnecting - this.timeout(10000); - return RealTimeClient.disconnectAllClients(done); - }); + before(function (done) { + // cleanup to speedup reconnecting + this.timeout(10000) + return RealTimeClient.disconnectAllClients(done) + }) - // trigger and check cleanup - it("should have disconnected all previous clients", function(done) { return RealTimeClient.getConnectedClients((error, data) => { - if (error) { return done(error); } - expect(data.length).to.equal(0); - return done(); - }); }); + // trigger and check cleanup + it('should have disconnected all previous clients', function (done) { + return RealTimeClient.getConnectedClients((error, data) => { + if (error) { + return done(error) + } + expect(data.length).to.equal(0) + return done() + }) + }) - return describe("with two clients in the project", function() { - beforeEach(function(done) { - return async.series([ - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, + return describe('with two clients in the project', function () { + beforeEach(function (done) { + return async.series( + [ + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, - cb => { - this.clientB = RealTimeClient.connect(); - return this.clientB.on("connectionAccepted", cb); - }, + (cb) => { + this.clientB = RealTimeClient.connect() + return this.clientB.on('connectionAccepted', cb) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, cb); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - cb => { - return this.clientB.emit("joinProject", {project_id: this.project_id}, cb); - } - ], done); - }); + (cb) => { + return this.clientB.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + } + ], + done + ) + }) - return describe("starting to drain", function() { - beforeEach(function(done) { - return async.parallel([ - cb => { - return this.clientA.on("reconnectGracefully", cb); - }, - cb => { - return this.clientB.on("reconnectGracefully", cb); - }, + return describe('starting to drain', function () { + beforeEach(function (done) { + return async.parallel( + [ + (cb) => { + return this.clientA.on('reconnectGracefully', cb) + }, + (cb) => { + return this.clientB.on('reconnectGracefully', cb) + }, - cb => drain(2, cb) - ], done); - }); + (cb) => drain(2, cb) + ], + done + ) + }) - afterEach(function(done) { return drain(0, done); }); // reset drain + afterEach(function (done) { + return drain(0, done) + }) // reset drain - it("should not timeout", function() { return expect(true).to.equal(true); }); + it('should not timeout', function () { + return expect(true).to.equal(true) + }) - return it("should not have disconnected", function() { - expect(this.clientA.socket.connected).to.equal(true); - return expect(this.clientB.socket.connected).to.equal(true); - }); - }); - }); -}); + return it('should not have disconnected', function () { + expect(this.clientA.socket.connected).to.equal(true) + return expect(this.clientB.socket.connected).to.equal(true) + }) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/EarlyDisconnect.js b/services/real-time/test/acceptance/js/EarlyDisconnect.js index 427d77040b..25e8fbc427 100644 --- a/services/real-time/test/acceptance/js/EarlyDisconnect.js +++ b/services/real-time/test/acceptance/js/EarlyDisconnect.js @@ -10,206 +10,279 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const async = require("async"); -const {expect} = require("chai"); +const async = require('async') +const { expect } = require('chai') -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer"); -const MockWebServer = require("./helpers/MockWebServer"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockDocUpdaterServer = require('./helpers/MockDocUpdaterServer') +const MockWebServer = require('./helpers/MockWebServer') +const FixturesManager = require('./helpers/FixturesManager') -const settings = require("settings-sharelatex"); -const redis = require("redis-sharelatex"); -const rclient = redis.createClient(settings.redis.pubsub); -const rclientRT = redis.createClient(settings.redis.realtime); -const KeysRT = settings.redis.realtime.key_schema; +const settings = require('settings-sharelatex') +const redis = require('redis-sharelatex') +const rclient = redis.createClient(settings.redis.pubsub) +const rclientRT = redis.createClient(settings.redis.realtime) +const KeysRT = settings.redis.realtime.key_schema -describe("EarlyDisconnect", function() { - before(function(done) { return MockDocUpdaterServer.run(done); }); +describe('EarlyDisconnect', function () { + before(function (done) { + return MockDocUpdaterServer.run(done) + }) - describe("when the client disconnects before joinProject completes", function() { - before(function() { - // slow down web-api requests to force the race condition - let joinProject; - this.actualWebAPIjoinProject = (joinProject = MockWebServer.joinProject); - return MockWebServer.joinProject = (project_id, user_id, cb) => setTimeout(() => joinProject(project_id, user_id, cb) - , 300); - }); + describe('when the client disconnects before joinProject completes', function () { + before(function () { + // slow down web-api requests to force the race condition + let joinProject + this.actualWebAPIjoinProject = joinProject = MockWebServer.joinProject + return (MockWebServer.joinProject = (project_id, user_id, cb) => + setTimeout(() => joinProject(project_id, user_id, cb), 300)) + }) - after(function() { - return MockWebServer.joinProject = this.actualWebAPIjoinProject; - }); + after(function () { + return (MockWebServer.joinProject = this.actualWebAPIjoinProject) + }) - beforeEach(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + beforeEach(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, - cb => { - this.clientA.emit("joinProject", {project_id: this.project_id}, (() => {})); - // disconnect before joinProject completes - this.clientA.on("disconnect", () => cb()); - return this.clientA.disconnect(); - }, + (cb) => { + this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + () => {} + ) + // disconnect before joinProject completes + this.clientA.on('disconnect', () => cb()) + return this.clientA.disconnect() + }, - cb => { - // wait for joinDoc and subscribe - return setTimeout(cb, 500); - } - ], done); - }); + (cb) => { + // wait for joinDoc and subscribe + return setTimeout(cb, 500) + } + ], + done + ) + }) - // we can force the race condition, there is no need to repeat too often - return Array.from(Array.from({length: 5}).map((_, i) => i+1)).map((attempt) => - it(`should not subscribe to the pub/sub channel anymore (race ${attempt})`, function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - expect(resp).to.not.include(`editor-events:${this.project_id}`); - return done(); - }); - return null; - })); - }); + // we can force the race condition, there is no need to repeat too often + return Array.from(Array.from({ length: 5 }).map((_, i) => i + 1)).map( + (attempt) => + it(`should not subscribe to the pub/sub channel anymore (race ${attempt})`, function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + expect(resp).to.not.include(`editor-events:${this.project_id}`) + return done() + }) + return null + }) + ) + }) - describe("when the client disconnects before joinDoc completes", function() { - beforeEach(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + describe('when the client disconnects before joinDoc completes', function () { + beforeEach(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - this.clientA.emit("joinDoc", this.doc_id, (() => {})); - // disconnect before joinDoc completes - this.clientA.on("disconnect", () => cb()); - return this.clientA.disconnect(); - }, + (cb) => { + this.clientA.emit('joinDoc', this.doc_id, () => {}) + // disconnect before joinDoc completes + this.clientA.on('disconnect', () => cb()) + return this.clientA.disconnect() + }, - cb => { - // wait for subscribe and unsubscribe - return setTimeout(cb, 100); - } - ], done); - }); + (cb) => { + // wait for subscribe and unsubscribe + return setTimeout(cb, 100) + } + ], + done + ) + }) - // we can not force the race condition, so we have to try many times - return Array.from(Array.from({length: 20}).map((_, i) => i+1)).map((attempt) => - it(`should not subscribe to the pub/sub channels anymore (race ${attempt})`, function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - expect(resp).to.not.include(`editor-events:${this.project_id}`); + // we can not force the race condition, so we have to try many times + return Array.from(Array.from({ length: 20 }).map((_, i) => i + 1)).map( + (attempt) => + it(`should not subscribe to the pub/sub channels anymore (race ${attempt})`, function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + expect(resp).to.not.include(`editor-events:${this.project_id}`) - return rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - expect(resp).to.not.include(`applied-ops:${this.doc_id}`); - return done(); - }); - }); - return null; - })); - }); + return rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + expect(resp).to.not.include(`applied-ops:${this.doc_id}`) + return done() + }) + }) + return null + }) + ) + }) - return describe("when the client disconnects before clientTracking.updatePosition starts", function() { - beforeEach(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + return describe('when the client disconnects before clientTracking.updatePosition starts', function () { + beforeEach(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - return this.clientA.emit("joinDoc", this.doc_id, cb); - }, + (cb) => { + return this.clientA.emit('joinDoc', this.doc_id, cb) + }, - cb => { - this.clientA.emit("clientTracking.updatePosition", { - row: 42, - column: 36, - doc_id: this.doc_id - }, (() => {})); - // disconnect before updateClientPosition completes - this.clientA.on("disconnect", () => cb()); - return this.clientA.disconnect(); - }, + (cb) => { + this.clientA.emit( + 'clientTracking.updatePosition', + { + row: 42, + column: 36, + doc_id: this.doc_id + }, + () => {} + ) + // disconnect before updateClientPosition completes + this.clientA.on('disconnect', () => cb()) + return this.clientA.disconnect() + }, - cb => { - // wait for updateClientPosition - return setTimeout(cb, 100); - } - ], done); - }); + (cb) => { + // wait for updateClientPosition + return setTimeout(cb, 100) + } + ], + done + ) + }) - // we can not force the race condition, so we have to try many times - return Array.from(Array.from({length: 20}).map((_, i) => i+1)).map((attempt) => - it(`should not show the client as connected (race ${attempt})`, function(done) { - rclientRT.smembers(KeysRT.clientsInProject({project_id: this.project_id}), (err, results) => { - if (err) { return done(err); } - expect(results).to.deep.equal([]); - return done(); - }); - return null; - })); - }); -}); + // we can not force the race condition, so we have to try many times + return Array.from(Array.from({ length: 20 }).map((_, i) => i + 1)).map( + (attempt) => + it(`should not show the client as connected (race ${attempt})`, function (done) { + rclientRT.smembers( + KeysRT.clientsInProject({ project_id: this.project_id }), + (err, results) => { + if (err) { + return done(err) + } + expect(results).to.deep.equal([]) + return done() + } + ) + return null + }) + ) + }) +}) diff --git a/services/real-time/test/acceptance/js/HttpControllerTests.js b/services/real-time/test/acceptance/js/HttpControllerTests.js index c701a91e20..5d40a30def 100644 --- a/services/real-time/test/acceptance/js/HttpControllerTests.js +++ b/services/real-time/test/acceptance/js/HttpControllerTests.js @@ -8,89 +8,110 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const async = require('async'); -const { - expect -} = require('chai'); +const async = require('async') +const { expect } = require('chai') const request = require('request').defaults({ - baseUrl: 'http://localhost:3026' -}); + baseUrl: 'http://localhost:3026' +}) -const RealTimeClient = require("./helpers/RealTimeClient"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const FixturesManager = require('./helpers/FixturesManager') -describe('HttpControllerTests', function() { - describe('without a user', function() { return it('should return 404 for the client view', function(done) { - const client_id = 'not-existing'; - return request.get({ - url: `/clients/${client_id}`, - json: true - }, (error, response, data) => { - if (error) { return done(error); } - expect(response.statusCode).to.equal(404); - return done(); - }); - }); }); +describe('HttpControllerTests', function () { + describe('without a user', function () { + return it('should return 404 for the client view', function (done) { + const client_id = 'not-existing' + return request.get( + { + url: `/clients/${client_id}`, + json: true + }, + (error, response, data) => { + if (error) { + return done(error) + } + expect(response.statusCode).to.equal(404) + return done() + } + ) + }) + }) - return describe('with a user and after joining a project', function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner" - }, (error, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(error); - }); - }, + return describe('with a user and after joining a project', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner' + }, + (error, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {}, (error, {doc_id}) => { - this.doc_id = doc_id; - return cb(error); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + {}, + (error, { doc_id }) => { + this.doc_id = doc_id + return cb(error) + } + ) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - cb => { - return this.client.emit("joinDoc", this.doc_id, cb); - } - ], done); - }); + (cb) => { + return this.client.emit('joinDoc', this.doc_id, cb) + } + ], + done + ) + }) - return it('should send a client view', function(done) { - return request.get({ - url: `/clients/${this.client.socket.sessionid}`, - json: true - }, (error, response, data) => { - if (error) { return done(error); } - expect(response.statusCode).to.equal(200); - expect(data.connected_time).to.exist; - delete data.connected_time; - // .email is not set in the session - delete data.email; - expect(data).to.deep.equal({ - client_id: this.client.socket.sessionid, - first_name: 'Joe', - last_name: 'Bloggs', - project_id: this.project_id, - user_id: this.user_id, - rooms: [ - this.project_id, - this.doc_id, - ] - }); - return done(); - }); - }); - }); -}); + return it('should send a client view', function (done) { + return request.get( + { + url: `/clients/${this.client.socket.sessionid}`, + json: true + }, + (error, response, data) => { + if (error) { + return done(error) + } + expect(response.statusCode).to.equal(200) + expect(data.connected_time).to.exist + delete data.connected_time + // .email is not set in the session + delete data.email + expect(data).to.deep.equal({ + client_id: this.client.socket.sessionid, + first_name: 'Joe', + last_name: 'Bloggs', + project_id: this.project_id, + user_id: this.user_id, + rooms: [this.project_id, this.doc_id] + }) + return done() + } + ) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/JoinDocTests.js b/services/real-time/test/acceptance/js/JoinDocTests.js index f55af76820..217731a1df 100644 --- a/services/real-time/test/acceptance/js/JoinDocTests.js +++ b/services/real-time/test/acceptance/js/JoinDocTests.js @@ -11,348 +11,555 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const chai = require("chai"); -const { - expect -} = chai; -chai.should(); +const chai = require('chai') +const { expect } = chai +chai.should() -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockDocUpdaterServer = require('./helpers/MockDocUpdaterServer') +const FixturesManager = require('./helpers/FixturesManager') -const async = require("async"); +const async = require('async') -describe("joinDoc", function() { - before(function() { - this.lines = ["test", "doc", "lines"]; - this.version = 42; - this.ops = ["mock", "doc", "ops"]; - return this.ranges = {"mock": "ranges"};}); - - describe("when authorised readAndWrite", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readAndWrite" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops, ranges: this.ranges}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, +describe('joinDoc', function () { + before(function () { + this.lines = ['test', 'doc', 'lines'] + this.version = 42 + this.ops = ['mock', 'doc', 'ops'] + return (this.ranges = { mock: 'ranges' }) + }) - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, (error, ...rest) => { [...this.returnedArgs] = Array.from(rest); return cb(error); }); - } - ], done); - }); + describe('when authorised readAndWrite', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, - it("should get the doc from the doc updater", function() { - return MockDocUpdaterServer.getDocument - .calledWith(this.project_id, this.doc_id, -1) - .should.equal(true); - }); - - it("should return the doc lines, version, ranges and ops", function() { - return this.returnedArgs.should.deep.equal([this.lines, this.version, this.ops, this.ranges]); - }); - - return it("should have joined the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true); - return done(); - }); - }); - }); - - describe("when authorised readOnly", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readOnly" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops, ranges: this.ranges}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges + }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, (error, ...rest) => { [...this.returnedArgs] = Array.from(rest); return cb(error); }); - } - ], done); - }); + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - it("should get the doc from the doc updater", function() { - return MockDocUpdaterServer.getDocument - .calledWith(this.project_id, this.doc_id, -1) - .should.equal(true); - }); - - it("should return the doc lines, version, ranges and ops", function() { - return this.returnedArgs.should.deep.equal([this.lines, this.version, this.ops, this.ranges]); - }); - - return it("should have joined the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true); - return done(); - }); - }); - }); - - describe("when authorised as owner", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops, ranges: this.ranges}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, (error, ...rest) => { [...this.returnedArgs] = Array.from(rest); return cb(error); }); - } - ], done); - }); + (cb) => { + return this.client.emit( + 'joinDoc', + this.doc_id, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + return cb(error) + } + ) + } + ], + done + ) + }) - it("should get the doc from the doc updater", function() { - return MockDocUpdaterServer.getDocument - .calledWith(this.project_id, this.doc_id, -1) - .should.equal(true); - }); - - it("should return the doc lines, version, ranges and ops", function() { - return this.returnedArgs.should.deep.equal([this.lines, this.version, this.ops, this.ranges]); - }); - - return it("should have joined the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true); - return done(); - }); - }); - }); + it('should get the doc from the doc updater', function () { + return MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, -1) + .should.equal(true) + }) - // It is impossible to write an acceptance test to test joining an unauthorized - // project, since joinProject already catches that. If you can join a project, - // then you can join a doc in that project. - - describe("with a fromVersion", function() { - before(function(done) { - this.fromVersion = 36; - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readAndWrite" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops, ranges: this.ranges}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + it('should return the doc lines, version, ranges and ops', function () { + return this.returnedArgs.should.deep.equal([ + this.lines, + this.version, + this.ops, + this.ranges + ]) + }) - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, this.fromVersion, (error, ...rest) => { [...this.returnedArgs] = Array.from(rest); return cb(error); }); - } - ], done); - }); + return it('should have joined the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true) + return done() + } + ) + }) + }) - it("should get the doc from the doc updater with the fromVersion", function() { - return MockDocUpdaterServer.getDocument - .calledWith(this.project_id, this.doc_id, this.fromVersion) - .should.equal(true); - }); - - it("should return the doc lines, version, ranges and ops", function() { - return this.returnedArgs.should.deep.equal([this.lines, this.version, this.ops, this.ranges]); - }); - - return it("should have joined the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true); - return done(); - }); - }); - }); + describe('when authorised readOnly', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readOnly' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, - describe("with options", function() { - before(function(done) { - this.options = { encodeRanges: true }; - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readAndWrite" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges + }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops, ranges: this.ranges}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, + (cb) => { + return this.client.emit( + 'joinDoc', + this.doc_id, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + return cb(error) + } + ) + } + ], + done + ) + }) - cb => { - return this.client.emit("joinDoc", this.doc_id, this.options, (error, ...rest) => { [...this.returnedArgs] = Array.from(rest); return cb(error); }); - } - ], done); - }); + it('should get the doc from the doc updater', function () { + return MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, -1) + .should.equal(true) + }) - it("should get the doc from the doc updater with the default fromVersion", function() { - return MockDocUpdaterServer.getDocument - .calledWith(this.project_id, this.doc_id, -1) - .should.equal(true); - }); + it('should return the doc lines, version, ranges and ops', function () { + return this.returnedArgs.should.deep.equal([ + this.lines, + this.version, + this.ops, + this.ranges + ]) + }) - it("should return the doc lines, version, ranges and ops", function() { - return this.returnedArgs.should.deep.equal([this.lines, this.version, this.ops, this.ranges]); - }); + return it('should have joined the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true) + return done() + } + ) + }) + }) - return it("should have joined the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true); - return done(); - }); - }); - }); + describe('when authorised as owner', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, - return describe("with fromVersion and options", function() { - before(function(done) { - this.fromVersion = 36; - this.options = { encodeRanges: true }; - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readAndWrite" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges + }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops, ranges: this.ranges}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, + (cb) => { + return this.client.emit( + 'joinDoc', + this.doc_id, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + return cb(error) + } + ) + } + ], + done + ) + }) - cb => { - return this.client.emit("joinDoc", this.doc_id, this.fromVersion, this.options, (error, ...rest) => { [...this.returnedArgs] = Array.from(rest); return cb(error); }); - } - ], done); - }); + it('should get the doc from the doc updater', function () { + return MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, -1) + .should.equal(true) + }) - it("should get the doc from the doc updater with the fromVersion", function() { - return MockDocUpdaterServer.getDocument - .calledWith(this.project_id, this.doc_id, this.fromVersion) - .should.equal(true); - }); + it('should return the doc lines, version, ranges and ops', function () { + return this.returnedArgs.should.deep.equal([ + this.lines, + this.version, + this.ops, + this.ranges + ]) + }) - it("should return the doc lines, version, ranges and ops", function() { - return this.returnedArgs.should.deep.equal([this.lines, this.version, this.ops, this.ranges]); - }); + return it('should have joined the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true) + return done() + } + ) + }) + }) - return it("should have joined the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true); - return done(); - }); - }); - }); -}); + // It is impossible to write an acceptance test to test joining an unauthorized + // project, since joinProject already catches that. If you can join a project, + // then you can join a doc in that project. + + describe('with a fromVersion', function () { + before(function (done) { + this.fromVersion = 36 + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, + + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges + }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, + + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, + + (cb) => { + return this.client.emit( + 'joinDoc', + this.doc_id, + this.fromVersion, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + return cb(error) + } + ) + } + ], + done + ) + }) + + it('should get the doc from the doc updater with the fromVersion', function () { + return MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, this.fromVersion) + .should.equal(true) + }) + + it('should return the doc lines, version, ranges and ops', function () { + return this.returnedArgs.should.deep.equal([ + this.lines, + this.version, + this.ops, + this.ranges + ]) + }) + + return it('should have joined the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true) + return done() + } + ) + }) + }) + + describe('with options', function () { + before(function (done) { + this.options = { encodeRanges: true } + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, + + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges + }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, + + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, + + (cb) => { + return this.client.emit( + 'joinDoc', + this.doc_id, + this.options, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + return cb(error) + } + ) + } + ], + done + ) + }) + + it('should get the doc from the doc updater with the default fromVersion', function () { + return MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, -1) + .should.equal(true) + }) + + it('should return the doc lines, version, ranges and ops', function () { + return this.returnedArgs.should.deep.equal([ + this.lines, + this.version, + this.ops, + this.ranges + ]) + }) + + return it('should have joined the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true) + return done() + } + ) + }) + }) + + return describe('with fromVersion and options', function () { + before(function (done) { + this.fromVersion = 36 + this.options = { encodeRanges: true } + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, + + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges + }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, + + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, + + (cb) => { + return this.client.emit( + 'joinDoc', + this.doc_id, + this.fromVersion, + this.options, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + return cb(error) + } + ) + } + ], + done + ) + }) + + it('should get the doc from the doc updater with the fromVersion', function () { + return MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, this.fromVersion) + .should.equal(true) + }) + + it('should return the doc lines, version, ranges and ops', function () { + return this.returnedArgs.should.deep.equal([ + this.lines, + this.version, + this.ops, + this.ranges + ]) + }) + + return it('should have joined the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(true) + return done() + } + ) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/JoinProjectTests.js b/services/real-time/test/acceptance/js/JoinProjectTests.js index f84af3bba5..051c33d0c7 100644 --- a/services/real-time/test/acceptance/js/JoinProjectTests.js +++ b/services/real-time/test/acceptance/js/JoinProjectTests.js @@ -10,159 +10,199 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const chai = require("chai"); -const { - expect -} = chai; -chai.should(); +const chai = require('chai') +const { expect } = chai +chai.should() -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockWebServer = require("./helpers/MockWebServer"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockWebServer = require('./helpers/MockWebServer') +const FixturesManager = require('./helpers/FixturesManager') -const async = require("async"); +const async = require('async') -describe("joinProject", function() { - describe("when authorized", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, +describe('joinProject', function () { + describe('when authorized', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - } - ], done); - }); - - it("should get the project from web", function() { - return MockWebServer.joinProject - .calledWith(this.project_id, this.user_id) - .should.equal(true); - }); - - it("should return the project", function() { - return this.project.should.deep.equal({ - name: "Test Project" - }); - }); - - it("should return the privilege level", function() { - return this.privilegeLevel.should.equal("owner"); - }); - - it("should return the protocolVersion", function() { - return this.protocolVersion.should.equal(2); - }); - - it("should have joined the project room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.project_id)).to.equal(true); - return done(); - }); - }); - - return it("should have marked the user as connected", function(done) { - return this.client.emit("clientTracking.getConnectedUsers", (error, users) => { - let connected = false; - for (const user of Array.from(users)) { - if ((user.client_id === this.client.publicId) && (user.user_id === this.user_id)) { - connected = true; - break; - } - } - expect(connected).to.equal(true); - return done(); - }); - }); - }); - - describe("when not authorized", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: null, - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.error = error; - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(); - }); - } - ], done); - }); - - it("should return an error", function() { - return this.error.message.should.equal("not authorized"); - }); - - return it("should not have joined the project room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.project_id)).to.equal(false); - return done(); - }); - }); - }); + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + } + ], + done + ) + }) - return describe("when over rate limit", function() { - before(function(done) { - return async.series([ - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, + it('should get the project from web', function () { + return MockWebServer.joinProject + .calledWith(this.project_id, this.user_id) + .should.equal(true) + }) - cb => { - return this.client.emit("joinProject", {project_id: 'rate-limited'}, error => { - this.error = error; - return cb(); - }); - } - ], done); - }); + it('should return the project', function () { + return this.project.should.deep.equal({ + name: 'Test Project' + }) + }) - return it("should return a TooManyRequests error code", function() { - this.error.message.should.equal("rate-limit hit when joining project"); - return this.error.code.should.equal("TooManyRequests"); - }); - }); -}); + it('should return the privilege level', function () { + return this.privilegeLevel.should.equal('owner') + }) + it('should return the protocolVersion', function () { + return this.protocolVersion.should.equal(2) + }) + + it('should have joined the project room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.project_id)).to.equal( + true + ) + return done() + } + ) + }) + + return it('should have marked the user as connected', function (done) { + return this.client.emit( + 'clientTracking.getConnectedUsers', + (error, users) => { + let connected = false + for (const user of Array.from(users)) { + if ( + user.client_id === this.client.publicId && + user.user_id === this.user_id + ) { + connected = true + break + } + } + expect(connected).to.equal(true) + return done() + } + ) + }) + }) + + describe('when not authorized', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: null, + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, + + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.error = error + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb() + } + ) + } + ], + done + ) + }) + + it('should return an error', function () { + return this.error.message.should.equal('not authorized') + }) + + return it('should not have joined the project room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.project_id)).to.equal( + false + ) + return done() + } + ) + }) + }) + + return describe('when over rate limit', function () { + before(function (done) { + return async.series( + [ + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: 'rate-limited' }, + (error) => { + this.error = error + return cb() + } + ) + } + ], + done + ) + }) + + return it('should return a TooManyRequests error code', function () { + this.error.message.should.equal('rate-limit hit when joining project') + return this.error.code.should.equal('TooManyRequests') + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/LeaveDocTests.js b/services/real-time/test/acceptance/js/LeaveDocTests.js index 3f396e3df5..a842087522 100644 --- a/services/real-time/test/acceptance/js/LeaveDocTests.js +++ b/services/real-time/test/acceptance/js/LeaveDocTests.js @@ -13,117 +13,164 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const chai = require("chai"); -const { - expect -} = chai; -chai.should(); -const sinon = require("sinon"); +const chai = require('chai') +const { expect } = chai +chai.should() +const sinon = require('sinon') -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer"); -const FixturesManager = require("./helpers/FixturesManager"); -const logger = require("logger-sharelatex"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockDocUpdaterServer = require('./helpers/MockDocUpdaterServer') +const FixturesManager = require('./helpers/FixturesManager') +const logger = require('logger-sharelatex') -const async = require("async"); +const async = require('async') -describe("leaveDoc", function() { - before(function() { - this.lines = ["test", "doc", "lines"]; - this.version = 42; - this.ops = ["mock", "doc", "ops"]; - sinon.spy(logger, "error"); - sinon.spy(logger, "warn"); - sinon.spy(logger, "log"); - return this.other_doc_id = FixturesManager.getRandomId(); - }); - - after(function() { - logger.error.restore(); // remove the spy - logger.warn.restore(); - return logger.log.restore(); - }); +describe('leaveDoc', function () { + before(function () { + this.lines = ['test', 'doc', 'lines'] + this.version = 42 + this.ops = ['mock', 'doc', 'ops'] + sinon.spy(logger, 'error') + sinon.spy(logger, 'warn') + sinon.spy(logger, 'log') + return (this.other_doc_id = FixturesManager.getRandomId()) + }) - return describe("when joined to a doc", function() { - beforeEach(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "readAndWrite" - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, - - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", {project_id: this.project_id}, cb); - }, - - cb => { - return this.client.emit("joinDoc", this.doc_id, (error, ...rest) => { [...this.returnedArgs] = Array.from(rest); return cb(error); }); - } - ], done); - }); - - describe("then leaving the doc", function() { - beforeEach(function(done) { - return this.client.emit("leaveDoc", this.doc_id, (error) => { - if (error != null) { throw error; } - return done(); - }); - }); - - return it("should have left the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(false); - return done(); - }); - }); - }); + after(function () { + logger.error.restore() // remove the spy + logger.warn.restore() + return logger.log.restore() + }) - describe("when sending a leaveDoc request before the previous joinDoc request has completed", function() { - beforeEach(function(done) { - this.client.emit("leaveDoc", this.doc_id, () => {}); - this.client.emit("joinDoc", this.doc_id, () => {}); - return this.client.emit("leaveDoc", this.doc_id, (error) => { - if (error != null) { throw error; } - return done(); - }); - }); + return describe('when joined to a doc', function () { + beforeEach(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite' + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) + } + ) + }, - it("should not trigger an error", function() { return sinon.assert.neverCalledWith(logger.error, sinon.match.any, "not subscribed - shouldn't happen"); }); + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - return it("should have left the doc room", function(done) { - return RealTimeClient.getConnectedClient(this.client.socket.sessionid, (error, client) => { - expect(Array.from(client.rooms).includes(this.doc_id)).to.equal(false); - return done(); - }); - }); - }); + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - return describe("when sending a leaveDoc for a room the client has not joined ", function() { - beforeEach(function(done) { - return this.client.emit("leaveDoc", this.other_doc_id, (error) => { - if (error != null) { throw error; } - return done(); - }); - }); + (cb) => { + return this.client.emit( + 'joinProject', + { project_id: this.project_id }, + cb + ) + }, - return it("should trigger a low level message only", function() { return sinon.assert.calledWith(logger.log, sinon.match.any, "ignoring request from client to leave room it is not in"); }); - }); - }); -}); + (cb) => { + return this.client.emit( + 'joinDoc', + this.doc_id, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + return cb(error) + } + ) + } + ], + done + ) + }) + + describe('then leaving the doc', function () { + beforeEach(function (done) { + return this.client.emit('leaveDoc', this.doc_id, (error) => { + if (error != null) { + throw error + } + return done() + }) + }) + + return it('should have left the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal( + false + ) + return done() + } + ) + }) + }) + + describe('when sending a leaveDoc request before the previous joinDoc request has completed', function () { + beforeEach(function (done) { + this.client.emit('leaveDoc', this.doc_id, () => {}) + this.client.emit('joinDoc', this.doc_id, () => {}) + return this.client.emit('leaveDoc', this.doc_id, (error) => { + if (error != null) { + throw error + } + return done() + }) + }) + + it('should not trigger an error', function () { + return sinon.assert.neverCalledWith( + logger.error, + sinon.match.any, + "not subscribed - shouldn't happen" + ) + }) + + return it('should have left the doc room', function (done) { + return RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + expect(Array.from(client.rooms).includes(this.doc_id)).to.equal( + false + ) + return done() + } + ) + }) + }) + + return describe('when sending a leaveDoc for a room the client has not joined ', function () { + beforeEach(function (done) { + return this.client.emit('leaveDoc', this.other_doc_id, (error) => { + if (error != null) { + throw error + } + return done() + }) + }) + + return it('should trigger a low level message only', function () { + return sinon.assert.calledWith( + logger.log, + sinon.match.any, + 'ignoring request from client to leave room it is not in' + ) + }) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/LeaveProjectTests.js b/services/real-time/test/acceptance/js/LeaveProjectTests.js index 36a17fe081..61976d481f 100644 --- a/services/real-time/test/acceptance/js/LeaveProjectTests.js +++ b/services/real-time/test/acceptance/js/LeaveProjectTests.js @@ -11,203 +11,260 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockDocUpdaterServer = require('./helpers/MockDocUpdaterServer') +const FixturesManager = require('./helpers/FixturesManager') -const async = require("async"); +const async = require('async') -const settings = require("settings-sharelatex"); -const redis = require("redis-sharelatex"); -const rclient = redis.createClient(settings.redis.pubsub); +const settings = require('settings-sharelatex') +const redis = require('redis-sharelatex') +const rclient = redis.createClient(settings.redis.pubsub) -describe("leaveProject", function() { - before(function(done) { return MockDocUpdaterServer.run(done); }); +describe('leaveProject', function () { + before(function (done) { + return MockDocUpdaterServer.run(done) + }) - describe("with other clients in the project", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + describe('with other clients in the project', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, - cb => { - this.clientB = RealTimeClient.connect(); - this.clientB.on("connectionAccepted", cb); + (cb) => { + this.clientB = RealTimeClient.connect() + this.clientB.on('connectionAccepted', cb) - this.clientBDisconnectMessages = []; - return this.clientB.on("clientTracking.clientDisconnected", data => { - return this.clientBDisconnectMessages.push(data); - }); - }, + this.clientBDisconnectMessages = [] + return this.clientB.on( + 'clientTracking.clientDisconnected', + (data) => { + return this.clientBDisconnectMessages.push(data) + } + ) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return this.clientB.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientB.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - return this.clientA.emit("joinDoc", this.doc_id, cb); - }, - cb => { - return this.clientB.emit("joinDoc", this.doc_id, cb); - }, + (cb) => { + return this.clientA.emit('joinDoc', this.doc_id, cb) + }, + (cb) => { + return this.clientB.emit('joinDoc', this.doc_id, cb) + }, - cb => { - // leaveProject is called when the client disconnects - this.clientA.on("disconnect", () => cb()); - return this.clientA.disconnect(); - }, + (cb) => { + // leaveProject is called when the client disconnects + this.clientA.on('disconnect', () => cb()) + return this.clientA.disconnect() + }, - cb => { - // The API waits a little while before flushing changes - return setTimeout(done, 1000); - } + (cb) => { + // The API waits a little while before flushing changes + return setTimeout(done, 1000) + } + ], + done + ) + }) - ], done); - }); + it('should emit a disconnect message to the room', function () { + return this.clientBDisconnectMessages.should.deep.equal([ + this.clientA.publicId + ]) + }) - it("should emit a disconnect message to the room", function() { - return this.clientBDisconnectMessages.should.deep.equal([this.clientA.publicId]); - }); + it('should no longer list the client in connected users', function (done) { + return this.clientB.emit( + 'clientTracking.getConnectedUsers', + (error, users) => { + for (const user of Array.from(users)) { + if (user.client_id === this.clientA.publicId) { + throw 'Expected clientA to not be listed in connected users' + } + } + return done() + } + ) + }) - it("should no longer list the client in connected users", function(done) { - return this.clientB.emit("clientTracking.getConnectedUsers", (error, users) => { - for (const user of Array.from(users)) { - if (user.client_id === this.clientA.publicId) { - throw "Expected clientA to not be listed in connected users"; - } - } - return done(); - }); - }); + it('should not flush the project to the document updater', function () { + return MockDocUpdaterServer.deleteProject + .calledWith(this.project_id) + .should.equal(false) + }) - it("should not flush the project to the document updater", function() { - return MockDocUpdaterServer.deleteProject - .calledWith(this.project_id) - .should.equal(false); - }); + it('should remain subscribed to the editor-events channels', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.include(`editor-events:${this.project_id}`) + return done() + }) + return null + }) - it("should remain subscribed to the editor-events channels", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.include(`editor-events:${this.project_id}`); - return done(); - }); - return null; - }); + return it('should remain subscribed to the applied-ops channels', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.include(`applied-ops:${this.doc_id}`) + return done() + }) + return null + }) + }) - return it("should remain subscribed to the applied-ops channels", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.include(`applied-ops:${this.doc_id}`); - return done(); - }); - return null; - }); - }); + return describe('with no other clients in the project', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - return describe("with no other clients in the project", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connect', cb) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connect", cb); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, + (cb) => { + return this.clientA.emit('joinDoc', this.doc_id, cb) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, - cb => { - return this.clientA.emit("joinDoc", this.doc_id, cb); - }, + (cb) => { + // leaveProject is called when the client disconnects + this.clientA.on('disconnect', () => cb()) + return this.clientA.disconnect() + }, - cb => { - // leaveProject is called when the client disconnects - this.clientA.on("disconnect", () => cb()); - return this.clientA.disconnect(); - }, + (cb) => { + // The API waits a little while before flushing changes + return setTimeout(done, 1000) + } + ], + done + ) + }) - cb => { - // The API waits a little while before flushing changes - return setTimeout(done, 1000); - } - ], done); - }); + it('should flush the project to the document updater', function () { + return MockDocUpdaterServer.deleteProject + .calledWith(this.project_id) + .should.equal(true) + }) - it("should flush the project to the document updater", function() { - return MockDocUpdaterServer.deleteProject - .calledWith(this.project_id) - .should.equal(true); - }); + it('should not subscribe to the editor-events channels anymore', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.not.include(`editor-events:${this.project_id}`) + return done() + }) + return null + }) - it("should not subscribe to the editor-events channels anymore", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.not.include(`editor-events:${this.project_id}`); - return done(); - }); - return null; - }); - - return it("should not subscribe to the applied-ops channels anymore", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.not.include(`applied-ops:${this.doc_id}`); - return done(); - }); - return null; - }); - }); -}); + return it('should not subscribe to the applied-ops channels anymore', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.not.include(`applied-ops:${this.doc_id}`) + return done() + }) + return null + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/PubSubRace.js b/services/real-time/test/acceptance/js/PubSubRace.js index 34d526d820..a824ef3e82 100644 --- a/services/real-time/test/acceptance/js/PubSubRace.js +++ b/services/real-time/test/acceptance/js/PubSubRace.js @@ -9,275 +9,365 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockDocUpdaterServer = require('./helpers/MockDocUpdaterServer') +const FixturesManager = require('./helpers/FixturesManager') -const async = require("async"); +const async = require('async') -const settings = require("settings-sharelatex"); -const redis = require("redis-sharelatex"); -const rclient = redis.createClient(settings.redis.pubsub); +const settings = require('settings-sharelatex') +const redis = require('redis-sharelatex') +const rclient = redis.createClient(settings.redis.pubsub) -describe("PubSubRace", function() { - before(function(done) { return MockDocUpdaterServer.run(done); }); +describe('PubSubRace', function () { + before(function (done) { + return MockDocUpdaterServer.run(done) + }) - describe("when the client leaves a doc before joinDoc completes", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + describe('when the client leaves a doc before joinDoc completes', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connect", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connect', cb) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - this.clientA.emit("joinDoc", this.doc_id, () => {}); - // leave before joinDoc completes - return this.clientA.emit("leaveDoc", this.doc_id, cb); - }, + (cb) => { + this.clientA.emit('joinDoc', this.doc_id, () => {}) + // leave before joinDoc completes + return this.clientA.emit('leaveDoc', this.doc_id, cb) + }, - cb => { - // wait for subscribe and unsubscribe - return setTimeout(cb, 100); - } - ], done); - }); + (cb) => { + // wait for subscribe and unsubscribe + return setTimeout(cb, 100) + } + ], + done + ) + }) - return it("should not subscribe to the applied-ops channels anymore", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.not.include(`applied-ops:${this.doc_id}`); - return done(); - }); - return null; - }); - }); + return it('should not subscribe to the applied-ops channels anymore', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.not.include(`applied-ops:${this.doc_id}`) + return done() + }) + return null + }) + }) - describe("when the client emits joinDoc and leaveDoc requests frequently and leaves eventually", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + describe('when the client emits joinDoc and leaveDoc requests frequently and leaves eventually', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connect", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connect', cb) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - this.clientA.emit("joinDoc", this.doc_id, () => {}); - return this.clientA.emit("leaveDoc", this.doc_id, cb); - }, + (cb) => { + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + this.clientA.emit('joinDoc', this.doc_id, () => {}) + return this.clientA.emit('leaveDoc', this.doc_id, cb) + }, - cb => { - // wait for subscribe and unsubscribe - return setTimeout(cb, 100); - } - ], done); - }); + (cb) => { + // wait for subscribe and unsubscribe + return setTimeout(cb, 100) + } + ], + done + ) + }) - return it("should not subscribe to the applied-ops channels anymore", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.not.include(`applied-ops:${this.doc_id}`); - return done(); - }); - return null; - }); - }); + return it('should not subscribe to the applied-ops channels anymore', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.not.include(`applied-ops:${this.doc_id}`) + return done() + }) + return null + }) + }) - describe("when the client emits joinDoc and leaveDoc requests frequently and remains in the doc", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + describe('when the client emits joinDoc and leaveDoc requests frequently and remains in the doc', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connect", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connect', cb) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - this.clientA.emit("joinDoc", this.doc_id, () => {}); - this.clientA.emit("leaveDoc", this.doc_id, () => {}); - return this.clientA.emit("joinDoc", this.doc_id, cb); - }, + (cb) => { + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + this.clientA.emit('joinDoc', this.doc_id, () => {}) + this.clientA.emit('leaveDoc', this.doc_id, () => {}) + return this.clientA.emit('joinDoc', this.doc_id, cb) + }, - cb => { - // wait for subscribe and unsubscribe - return setTimeout(cb, 100); - } - ], done); - }); + (cb) => { + // wait for subscribe and unsubscribe + return setTimeout(cb, 100) + } + ], + done + ) + }) - return it("should subscribe to the applied-ops channels", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.include(`applied-ops:${this.doc_id}`); - return done(); - }); - return null; - }); - }); + return it('should subscribe to the applied-ops channels', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.include(`applied-ops:${this.doc_id}`) + return done() + }) + return null + }) + }) - return describe("when the client disconnects before joinDoc completes", function() { - before(function(done) { - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); }); - }, + return describe('when the client disconnects before joinDoc completes', function () { + before(function (done) { + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } + }, + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb() + } + ) + }, - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connect", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connect', cb) + }, - cb => { - return this.clientA.emit("joinProject", {project_id: this.project_id}, (error, project, privilegeLevel, protocolVersion) => { - this.project = project; - this.privilegeLevel = privilegeLevel; - this.protocolVersion = protocolVersion; - return cb(error); - }); - }, + (cb) => { + return this.clientA.emit( + 'joinProject', + { project_id: this.project_id }, + (error, project, privilegeLevel, protocolVersion) => { + this.project = project + this.privilegeLevel = privilegeLevel + this.protocolVersion = protocolVersion + return cb(error) + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - let joinDocCompleted = false; - this.clientA.emit("joinDoc", this.doc_id, () => joinDocCompleted = true); - // leave before joinDoc completes - return setTimeout(() => { - if (joinDocCompleted) { - return cb(new Error('joinDocCompleted -- lower timeout')); - } - this.clientA.on("disconnect", () => cb()); - return this.clientA.disconnect(); - } - // socket.io processes joinDoc and disconnect with different delays: - // - joinDoc goes through two process.nextTick - // - disconnect goes through one process.nextTick - // We have to inject the disconnect event into a different event loop - // cycle. - , 3); - }, + (cb) => { + let joinDocCompleted = false + this.clientA.emit( + 'joinDoc', + this.doc_id, + () => (joinDocCompleted = true) + ) + // leave before joinDoc completes + return setTimeout( + () => { + if (joinDocCompleted) { + return cb(new Error('joinDocCompleted -- lower timeout')) + } + this.clientA.on('disconnect', () => cb()) + return this.clientA.disconnect() + }, + // socket.io processes joinDoc and disconnect with different delays: + // - joinDoc goes through two process.nextTick + // - disconnect goes through one process.nextTick + // We have to inject the disconnect event into a different event loop + // cycle. + 3 + ) + }, - cb => { - // wait for subscribe and unsubscribe - return setTimeout(cb, 100); - } - ], done); - }); + (cb) => { + // wait for subscribe and unsubscribe + return setTimeout(cb, 100) + } + ], + done + ) + }) - it("should not subscribe to the editor-events channels anymore", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.not.include(`editor-events:${this.project_id}`); - return done(); - }); - return null; - }); + it('should not subscribe to the editor-events channels anymore', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.not.include(`editor-events:${this.project_id}`) + return done() + }) + return null + }) - return it("should not subscribe to the applied-ops channels anymore", function(done) { - rclient.pubsub('CHANNELS', (err, resp) => { - if (err) { return done(err); } - resp.should.not.include(`applied-ops:${this.doc_id}`); - return done(); - }); - return null; - }); - }); -}); + return it('should not subscribe to the applied-ops channels anymore', function (done) { + rclient.pubsub('CHANNELS', (err, resp) => { + if (err) { + return done(err) + } + resp.should.not.include(`applied-ops:${this.doc_id}`) + return done() + }) + return null + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/ReceiveUpdateTests.js b/services/real-time/test/acceptance/js/ReceiveUpdateTests.js index d141b27745..9c65be19f9 100644 --- a/services/real-time/test/acceptance/js/ReceiveUpdateTests.js +++ b/services/real-time/test/acceptance/js/ReceiveUpdateTests.js @@ -11,271 +11,339 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const chai = require("chai"); -const { - expect -} = chai; -chai.should(); +const chai = require('chai') +const { expect } = chai +chai.should() -const RealTimeClient = require("./helpers/RealTimeClient"); -const MockWebServer = require("./helpers/MockWebServer"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const MockWebServer = require('./helpers/MockWebServer') +const FixturesManager = require('./helpers/FixturesManager') -const async = require("async"); +const async = require('async') -const settings = require("settings-sharelatex"); -const redis = require("redis-sharelatex"); -const rclient = redis.createClient(settings.redis.pubsub); +const settings = require('settings-sharelatex') +const redis = require('redis-sharelatex') +const rclient = redis.createClient(settings.redis.pubsub) -describe("receiveUpdate", function() { - beforeEach(function(done) { - this.lines = ["test", "doc", "lines"]; - this.version = 42; - this.ops = ["mock", "doc", "ops"]; - - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { name: "Test Project" } - }, (error, {user_id, project_id}) => { this.user_id = user_id; this.project_id = project_id; return cb(); }); - }, - - cb => { - return FixturesManager.setUpDoc(this.project_id, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id}) => { - this.doc_id = doc_id; - return cb(e); - }); - }, - - cb => { - this.clientA = RealTimeClient.connect(); - return this.clientA.on("connectionAccepted", cb); - }, - - cb => { - this.clientB = RealTimeClient.connect(); - return this.clientB.on("connectionAccepted", cb); - }, - - cb => { - return this.clientA.emit("joinProject", { - project_id: this.project_id - }, cb); - }, - - cb => { - return this.clientA.emit("joinDoc", this.doc_id, cb); - }, - - cb => { - return this.clientB.emit("joinProject", { - project_id: this.project_id - }, cb); - }, - - cb => { - return this.clientB.emit("joinDoc", this.doc_id, cb); - }, +describe('receiveUpdate', function () { + beforeEach(function (done) { + this.lines = ['test', 'doc', 'lines'] + this.version = 42 + this.ops = ['mock', 'doc', 'ops'] - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: {name: "Test Project"} - }, (error, {user_id: user_id_second, project_id: project_id_second}) => { this.user_id_second = user_id_second; this.project_id_second = project_id_second; return cb(); }); - }, + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { name: 'Test Project' } + }, + (error, { user_id, project_id }) => { + this.user_id = user_id + this.project_id = project_id + return cb() + } + ) + }, - cb => { - return FixturesManager.setUpDoc(this.project_id_second, {lines: this.lines, version: this.version, ops: this.ops}, (e, {doc_id: doc_id_second}) => { - this.doc_id_second = doc_id_second; - return cb(e); - }); - }, + (cb) => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id }) => { + this.doc_id = doc_id + return cb(e) + } + ) + }, - cb => { - this.clientC = RealTimeClient.connect(); - return this.clientC.on("connectionAccepted", cb); - }, + (cb) => { + this.clientA = RealTimeClient.connect() + return this.clientA.on('connectionAccepted', cb) + }, - cb => { - return this.clientC.emit("joinProject", { - project_id: this.project_id_second - }, cb); - }, - cb => { - return this.clientC.emit("joinDoc", this.doc_id_second, cb); - }, + (cb) => { + this.clientB = RealTimeClient.connect() + return this.clientB.on('connectionAccepted', cb) + }, - cb => { - this.clientAUpdates = []; - this.clientA.on("otUpdateApplied", update => this.clientAUpdates.push(update)); - this.clientBUpdates = []; - this.clientB.on("otUpdateApplied", update => this.clientBUpdates.push(update)); - this.clientCUpdates = []; - this.clientC.on("otUpdateApplied", update => this.clientCUpdates.push(update)); + (cb) => { + return this.clientA.emit( + 'joinProject', + { + project_id: this.project_id + }, + cb + ) + }, - this.clientAErrors = []; - this.clientA.on("otUpdateError", error => this.clientAErrors.push(error)); - this.clientBErrors = []; - this.clientB.on("otUpdateError", error => this.clientBErrors.push(error)); - this.clientCErrors = []; - this.clientC.on("otUpdateError", error => this.clientCErrors.push(error)); - return cb(); - } - ], done); - }); + (cb) => { + return this.clientA.emit('joinDoc', this.doc_id, cb) + }, - afterEach(function() { - if (this.clientA != null) { - this.clientA.disconnect(); - } - if (this.clientB != null) { - this.clientB.disconnect(); - } - return (this.clientC != null ? this.clientC.disconnect() : undefined); - }); + (cb) => { + return this.clientB.emit( + 'joinProject', + { + project_id: this.project_id + }, + cb + ) + }, - describe("with an update from clientA", function() { - beforeEach(function(done) { - this.update = { - doc_id: this.doc_id, - op: { - meta: { - source: this.clientA.publicId - }, - v: this.version, - doc: this.doc_id, - op: [{i: "foo", p: 50}] - } - }; - rclient.publish("applied-ops", JSON.stringify(this.update)); - return setTimeout(done, 200); - }); // Give clients time to get message - - it("should send the full op to clientB", function() { - return this.clientBUpdates.should.deep.equal([this.update.op]); - }); - - it("should send an ack to clientA", function() { - return this.clientAUpdates.should.deep.equal([{ - v: this.version, doc: this.doc_id - }]); - }); + (cb) => { + return this.clientB.emit('joinDoc', this.doc_id, cb) + }, - return it("should send nothing to clientC", function() { - return this.clientCUpdates.should.deep.equal([]); - }); -}); + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { name: 'Test Project' } + }, + ( + error, + { user_id: user_id_second, project_id: project_id_second } + ) => { + this.user_id_second = user_id_second + this.project_id_second = project_id_second + return cb() + } + ) + }, - describe("with an update from clientC", function() { - beforeEach(function(done) { - this.update = { - doc_id: this.doc_id_second, - op: { - meta: { - source: this.clientC.publicId - }, - v: this.version, - doc: this.doc_id_second, - op: [{i: "update from clientC", p: 50}] - } - }; - rclient.publish("applied-ops", JSON.stringify(this.update)); - return setTimeout(done, 200); - }); // Give clients time to get message + (cb) => { + return FixturesManager.setUpDoc( + this.project_id_second, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id: doc_id_second }) => { + this.doc_id_second = doc_id_second + return cb(e) + } + ) + }, - it("should send nothing to clientA", function() { - return this.clientAUpdates.should.deep.equal([]); - }); + (cb) => { + this.clientC = RealTimeClient.connect() + return this.clientC.on('connectionAccepted', cb) + }, - it("should send nothing to clientB", function() { - return this.clientBUpdates.should.deep.equal([]); - }); + (cb) => { + return this.clientC.emit( + 'joinProject', + { + project_id: this.project_id_second + }, + cb + ) + }, + (cb) => { + return this.clientC.emit('joinDoc', this.doc_id_second, cb) + }, - return it("should send an ack to clientC", function() { - return this.clientCUpdates.should.deep.equal([{ - v: this.version, doc: this.doc_id_second - }]); - }); -}); + (cb) => { + this.clientAUpdates = [] + this.clientA.on('otUpdateApplied', (update) => + this.clientAUpdates.push(update) + ) + this.clientBUpdates = [] + this.clientB.on('otUpdateApplied', (update) => + this.clientBUpdates.push(update) + ) + this.clientCUpdates = [] + this.clientC.on('otUpdateApplied', (update) => + this.clientCUpdates.push(update) + ) - describe("with an update from a remote client for project 1", function() { - beforeEach(function(done) { - this.update = { - doc_id: this.doc_id, - op: { - meta: { - source: 'this-is-a-remote-client-id' - }, - v: this.version, - doc: this.doc_id, - op: [{i: "foo", p: 50}] - } - }; - rclient.publish("applied-ops", JSON.stringify(this.update)); - return setTimeout(done, 200); - }); // Give clients time to get message + this.clientAErrors = [] + this.clientA.on('otUpdateError', (error) => + this.clientAErrors.push(error) + ) + this.clientBErrors = [] + this.clientB.on('otUpdateError', (error) => + this.clientBErrors.push(error) + ) + this.clientCErrors = [] + this.clientC.on('otUpdateError', (error) => + this.clientCErrors.push(error) + ) + return cb() + } + ], + done + ) + }) - it("should send the full op to clientA", function() { - return this.clientAUpdates.should.deep.equal([this.update.op]); - }); - - it("should send the full op to clientB", function() { - return this.clientBUpdates.should.deep.equal([this.update.op]); - }); + afterEach(function () { + if (this.clientA != null) { + this.clientA.disconnect() + } + if (this.clientB != null) { + this.clientB.disconnect() + } + return this.clientC != null ? this.clientC.disconnect() : undefined + }) - return it("should send nothing to clientC", function() { - return this.clientCUpdates.should.deep.equal([]); - }); -}); + describe('with an update from clientA', function () { + beforeEach(function (done) { + this.update = { + doc_id: this.doc_id, + op: { + meta: { + source: this.clientA.publicId + }, + v: this.version, + doc: this.doc_id, + op: [{ i: 'foo', p: 50 }] + } + } + rclient.publish('applied-ops', JSON.stringify(this.update)) + return setTimeout(done, 200) + }) // Give clients time to get message - describe("with an error for the first project", function() { - beforeEach(function(done) { - rclient.publish("applied-ops", JSON.stringify({doc_id: this.doc_id, error: (this.error = "something went wrong")})); - return setTimeout(done, 200); - }); // Give clients time to get message + it('should send the full op to clientB', function () { + return this.clientBUpdates.should.deep.equal([this.update.op]) + }) - it("should send the error to the clients in the first project", function() { - this.clientAErrors.should.deep.equal([this.error]); - return this.clientBErrors.should.deep.equal([this.error]); - }); + it('should send an ack to clientA', function () { + return this.clientAUpdates.should.deep.equal([ + { + v: this.version, + doc: this.doc_id + } + ]) + }) - it("should not send any errors to the client in the second project", function() { - return this.clientCErrors.should.deep.equal([]); - }); + return it('should send nothing to clientC', function () { + return this.clientCUpdates.should.deep.equal([]) + }) + }) - it("should disconnect the clients of the first project", function() { - this.clientA.socket.connected.should.equal(false); - return this.clientB.socket.connected.should.equal(false); - }); + describe('with an update from clientC', function () { + beforeEach(function (done) { + this.update = { + doc_id: this.doc_id_second, + op: { + meta: { + source: this.clientC.publicId + }, + v: this.version, + doc: this.doc_id_second, + op: [{ i: 'update from clientC', p: 50 }] + } + } + rclient.publish('applied-ops', JSON.stringify(this.update)) + return setTimeout(done, 200) + }) // Give clients time to get message - return it("should not disconnect the client in the second project", function() { - return this.clientC.socket.connected.should.equal(true); - }); - }); + it('should send nothing to clientA', function () { + return this.clientAUpdates.should.deep.equal([]) + }) - return describe("with an error for the second project", function() { - beforeEach(function(done) { - rclient.publish("applied-ops", JSON.stringify({doc_id: this.doc_id_second, error: (this.error = "something went wrong")})); - return setTimeout(done, 200); - }); // Give clients time to get message + it('should send nothing to clientB', function () { + return this.clientBUpdates.should.deep.equal([]) + }) - it("should not send any errors to the clients in the first project", function() { - this.clientAErrors.should.deep.equal([]); - return this.clientBErrors.should.deep.equal([]); - }); + return it('should send an ack to clientC', function () { + return this.clientCUpdates.should.deep.equal([ + { + v: this.version, + doc: this.doc_id_second + } + ]) + }) + }) - it("should send the error to the client in the second project", function() { - return this.clientCErrors.should.deep.equal([this.error]); - }); + describe('with an update from a remote client for project 1', function () { + beforeEach(function (done) { + this.update = { + doc_id: this.doc_id, + op: { + meta: { + source: 'this-is-a-remote-client-id' + }, + v: this.version, + doc: this.doc_id, + op: [{ i: 'foo', p: 50 }] + } + } + rclient.publish('applied-ops', JSON.stringify(this.update)) + return setTimeout(done, 200) + }) // Give clients time to get message - it("should not disconnect the clients of the first project", function() { - this.clientA.socket.connected.should.equal(true); - return this.clientB.socket.connected.should.equal(true); - }); + it('should send the full op to clientA', function () { + return this.clientAUpdates.should.deep.equal([this.update.op]) + }) - return it("should disconnect the client in the second project", function() { - return this.clientC.socket.connected.should.equal(false); - }); - }); -}); + it('should send the full op to clientB', function () { + return this.clientBUpdates.should.deep.equal([this.update.op]) + }) + + return it('should send nothing to clientC', function () { + return this.clientCUpdates.should.deep.equal([]) + }) + }) + + describe('with an error for the first project', function () { + beforeEach(function (done) { + rclient.publish( + 'applied-ops', + JSON.stringify({ + doc_id: this.doc_id, + error: (this.error = 'something went wrong') + }) + ) + return setTimeout(done, 200) + }) // Give clients time to get message + + it('should send the error to the clients in the first project', function () { + this.clientAErrors.should.deep.equal([this.error]) + return this.clientBErrors.should.deep.equal([this.error]) + }) + + it('should not send any errors to the client in the second project', function () { + return this.clientCErrors.should.deep.equal([]) + }) + + it('should disconnect the clients of the first project', function () { + this.clientA.socket.connected.should.equal(false) + return this.clientB.socket.connected.should.equal(false) + }) + + return it('should not disconnect the client in the second project', function () { + return this.clientC.socket.connected.should.equal(true) + }) + }) + + return describe('with an error for the second project', function () { + beforeEach(function (done) { + rclient.publish( + 'applied-ops', + JSON.stringify({ + doc_id: this.doc_id_second, + error: (this.error = 'something went wrong') + }) + ) + return setTimeout(done, 200) + }) // Give clients time to get message + + it('should not send any errors to the clients in the first project', function () { + this.clientAErrors.should.deep.equal([]) + return this.clientBErrors.should.deep.equal([]) + }) + + it('should send the error to the client in the second project', function () { + return this.clientCErrors.should.deep.equal([this.error]) + }) + + it('should not disconnect the clients of the first project', function () { + this.clientA.socket.connected.should.equal(true) + return this.clientB.socket.connected.should.equal(true) + }) + + return it('should disconnect the client in the second project', function () { + return this.clientC.socket.connected.should.equal(false) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/RouterTests.js b/services/real-time/test/acceptance/js/RouterTests.js index 844a4061cf..729947281c 100644 --- a/services/real-time/test/acceptance/js/RouterTests.js +++ b/services/real-time/test/acceptance/js/RouterTests.js @@ -8,99 +8,114 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const async = require("async"); -const {expect} = require("chai"); +const async = require('async') +const { expect } = require('chai') -const RealTimeClient = require("./helpers/RealTimeClient"); -const FixturesManager = require("./helpers/FixturesManager"); +const RealTimeClient = require('./helpers/RealTimeClient') +const FixturesManager = require('./helpers/FixturesManager') +describe('Router', function () { + return describe('joinProject', function () { + describe('when there is no callback provided', function () { + after(function () { + return process.removeListener('unhandledRejection', this.onUnhandled) + }) -describe("Router", function() { return describe("joinProject", function() { - describe("when there is no callback provided", function() { - after(function() { - return process.removeListener('unhandledRejection', this.onUnhandled); - }); - - before(function(done) { - this.onUnhandled = error => done(error); - process.on('unhandledRejection', this.onUnhandled); - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); + before(function (done) { + this.onUnhandled = (error) => done(error) + process.on('unhandledRejection', this.onUnhandled) + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } }, - - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - this.client.emit("joinProject", {project_id: this.project_id}); - return setTimeout(cb, 100); + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) } - ], done); - }); + ) + }, - return it("should keep on going", function() { return expect('still running').to.exist; }); - }); + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - return describe("when there are too many arguments", function() { - after(function() { - return process.removeListener('unhandledRejection', this.onUnhandled); - }); + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, - before(function(done) { - this.onUnhandled = error => done(error); - process.on('unhandledRejection', this.onUnhandled); - return async.series([ - cb => { - return FixturesManager.setUpProject({ - privilegeLevel: "owner", - project: { - name: "Test Project" - } - }, (e, {project_id, user_id}) => { - this.project_id = project_id; - this.user_id = user_id; - return cb(e); - }); + (cb) => { + this.client.emit('joinProject', { project_id: this.project_id }) + return setTimeout(cb, 100) + } + ], + done + ) + }) + + return it('should keep on going', function () { + return expect('still running').to.exist + }) + }) + + return describe('when there are too many arguments', function () { + after(function () { + return process.removeListener('unhandledRejection', this.onUnhandled) + }) + + before(function (done) { + this.onUnhandled = (error) => done(error) + process.on('unhandledRejection', this.onUnhandled) + return async.series( + [ + (cb) => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { + name: 'Test Project' + } }, - - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - this.client = RealTimeClient.connect(); - return this.client.on("connectionAccepted", cb); - }, - - cb => { - return this.client.emit("joinProject", 1, 2, 3, 4, 5, error => { - this.error = error; - return cb(); - }); + (e, { project_id, user_id }) => { + this.project_id = project_id + this.user_id = user_id + return cb(e) } - ], done); - }); + ) + }, - return it("should return an error message", function() { - return expect(this.error.message).to.equal('unexpected arguments'); - }); - }); -}); }); + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + this.client = RealTimeClient.connect() + return this.client.on('connectionAccepted', cb) + }, + + (cb) => { + return this.client.emit('joinProject', 1, 2, 3, 4, 5, (error) => { + this.error = error + return cb() + }) + } + ], + done + ) + }) + + return it('should return an error message', function () { + return expect(this.error.message).to.equal('unexpected arguments') + }) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/SessionSocketsTests.js b/services/real-time/test/acceptance/js/SessionSocketsTests.js index 93f00cd516..45f62195e5 100644 --- a/services/real-time/test/acceptance/js/SessionSocketsTests.js +++ b/services/real-time/test/acceptance/js/SessionSocketsTests.js @@ -8,88 +8,96 @@ * DS102: Remove unnecessary code created because of implicit returns * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const RealTimeClient = require("./helpers/RealTimeClient"); -const Settings = require("settings-sharelatex"); -const {expect} = require('chai'); +const RealTimeClient = require('./helpers/RealTimeClient') +const Settings = require('settings-sharelatex') +const { expect } = require('chai') -describe('SessionSockets', function() { - before(function() { - return this.checkSocket = function(fn) { - const client = RealTimeClient.connect(); - client.on('connectionAccepted', fn); - client.on('connectionRejected', fn); - return null; - }; - }); +describe('SessionSockets', function () { + before(function () { + return (this.checkSocket = function (fn) { + const client = RealTimeClient.connect() + client.on('connectionAccepted', fn) + client.on('connectionRejected', fn) + return null + }) + }) - describe('without cookies', function() { - before(function() { return RealTimeClient.cookie = null; }); + describe('without cookies', function () { + before(function () { + return (RealTimeClient.cookie = null) + }) - return it('should return a lookup error', function(done) { - return this.checkSocket((error) => { - expect(error).to.exist; - expect(error.message).to.equal('invalid session'); - return done(); - }); - }); - }); + return it('should return a lookup error', function (done) { + return this.checkSocket((error) => { + expect(error).to.exist + expect(error.message).to.equal('invalid session') + return done() + }) + }) + }) - describe('with a different cookie', function() { - before(function() { return RealTimeClient.cookie = "some.key=someValue"; }); + describe('with a different cookie', function () { + before(function () { + return (RealTimeClient.cookie = 'some.key=someValue') + }) - return it('should return a lookup error', function(done) { - return this.checkSocket((error) => { - expect(error).to.exist; - expect(error.message).to.equal('invalid session'); - return done(); - }); - }); - }); + return it('should return a lookup error', function (done) { + return this.checkSocket((error) => { + expect(error).to.exist + expect(error.message).to.equal('invalid session') + return done() + }) + }) + }) - describe('with an invalid cookie', function() { - before(function(done) { - RealTimeClient.setSession({}, (error) => { - if (error) { return done(error); } - RealTimeClient.cookie = `${Settings.cookieName}=${ - RealTimeClient.cookie.slice(17, 49) - }`; - return done(); - }); - return null; - }); + describe('with an invalid cookie', function () { + before(function (done) { + RealTimeClient.setSession({}, (error) => { + if (error) { + return done(error) + } + RealTimeClient.cookie = `${ + Settings.cookieName + }=${RealTimeClient.cookie.slice(17, 49)}` + return done() + }) + return null + }) - return it('should return a lookup error', function(done) { - return this.checkSocket((error) => { - expect(error).to.exist; - expect(error.message).to.equal('invalid session'); - return done(); - }); - }); - }); + return it('should return a lookup error', function (done) { + return this.checkSocket((error) => { + expect(error).to.exist + expect(error.message).to.equal('invalid session') + return done() + }) + }) + }) - describe('with a valid cookie and no matching session', function() { - before(function() { return RealTimeClient.cookie = `${Settings.cookieName}=unknownId`; }); + describe('with a valid cookie and no matching session', function () { + before(function () { + return (RealTimeClient.cookie = `${Settings.cookieName}=unknownId`) + }) - return it('should return a lookup error', function(done) { - return this.checkSocket((error) => { - expect(error).to.exist; - expect(error.message).to.equal('invalid session'); - return done(); - }); - }); - }); + return it('should return a lookup error', function (done) { + return this.checkSocket((error) => { + expect(error).to.exist + expect(error.message).to.equal('invalid session') + return done() + }) + }) + }) - return describe('with a valid cookie and a matching session', function() { - before(function(done) { - RealTimeClient.setSession({}, done); - return null; - }); + return describe('with a valid cookie and a matching session', function () { + before(function (done) { + RealTimeClient.setSession({}, done) + return null + }) - return it('should not return an error', function(done) { - return this.checkSocket((error) => { - expect(error).to.not.exist; - return done(); - }); - }); - }); -}); + return it('should not return an error', function (done) { + return this.checkSocket((error) => { + expect(error).to.not.exist + return done() + }) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/SessionTests.js b/services/real-time/test/acceptance/js/SessionTests.js index d9614784f2..941a59f4b9 100644 --- a/services/real-time/test/acceptance/js/SessionTests.js +++ b/services/real-time/test/acceptance/js/SessionTests.js @@ -11,47 +11,51 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const chai = require("chai"); -const { - expect -} = chai; +const chai = require('chai') +const { expect } = chai -const RealTimeClient = require("./helpers/RealTimeClient"); +const RealTimeClient = require('./helpers/RealTimeClient') -describe("Session", function() { return describe("with an established session", function() { - before(function(done) { - this.user_id = "mock-user-id"; - RealTimeClient.setSession({ - user: { _id: this.user_id } - }, error => { - if (error != null) { throw error; } - this.client = RealTimeClient.connect(); - return done(); - }); - return null; - }); - - it("should not get disconnected", function(done) { - let disconnected = false; - this.client.on("disconnect", () => disconnected = true); - return setTimeout(() => { - expect(disconnected).to.equal(false); - return done(); +describe('Session', function () { + return describe('with an established session', function () { + before(function (done) { + this.user_id = 'mock-user-id' + RealTimeClient.setSession( + { + user: { _id: this.user_id } + }, + (error) => { + if (error != null) { + throw error + } + this.client = RealTimeClient.connect() + return done() } - , 500); - }); - - return it("should appear in the list of connected clients", function(done) { - return RealTimeClient.getConnectedClients((error, clients) => { - let included = false; - for (const client of Array.from(clients)) { - if (client.client_id === this.client.socket.sessionid) { - included = true; - break; - } - } - expect(included).to.equal(true); - return done(); - }); - }); -}); }); + ) + return null + }) + + it('should not get disconnected', function (done) { + let disconnected = false + this.client.on('disconnect', () => (disconnected = true)) + return setTimeout(() => { + expect(disconnected).to.equal(false) + return done() + }, 500) + }) + + return it('should appear in the list of connected clients', function (done) { + return RealTimeClient.getConnectedClients((error, clients) => { + let included = false + for (const client of Array.from(clients)) { + if (client.client_id === this.client.socket.sessionid) { + included = true + break + } + } + expect(included).to.equal(true) + return done() + }) + }) + }) +}) diff --git a/services/real-time/test/acceptance/js/helpers/FixturesManager.js b/services/real-time/test/acceptance/js/helpers/FixturesManager.js index 81d9dc40af..3e72961cbf 100644 --- a/services/real-time/test/acceptance/js/helpers/FixturesManager.js +++ b/services/real-time/test/acceptance/js/helpers/FixturesManager.js @@ -10,64 +10,110 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let FixturesManager; -const RealTimeClient = require("./RealTimeClient"); -const MockWebServer = require("./MockWebServer"); -const MockDocUpdaterServer = require("./MockDocUpdaterServer"); +let FixturesManager +const RealTimeClient = require('./RealTimeClient') +const MockWebServer = require('./MockWebServer') +const MockDocUpdaterServer = require('./MockDocUpdaterServer') -module.exports = (FixturesManager = { - setUpProject(options, callback) { - if (options == null) { options = {}; } - if (callback == null) { callback = function(error, data) {}; } - if (!options.user_id) { options.user_id = FixturesManager.getRandomId(); } - if (!options.project_id) { options.project_id = FixturesManager.getRandomId(); } - if (!options.project) { options.project = { name: "Test Project" }; } - const {project_id, user_id, privilegeLevel, project, publicAccess} = options; - - const privileges = {}; - privileges[user_id] = privilegeLevel; - if (publicAccess) { - privileges["anonymous-user"] = publicAccess; - } - - MockWebServer.createMockProject(project_id, privileges, project); - return MockWebServer.run(error => { - if (error != null) { throw error; } - return RealTimeClient.setSession({ - user: { - _id: user_id, - first_name: "Joe", - last_name: "Bloggs" - } - }, error => { - if (error != null) { throw error; } - return callback(null, {project_id, user_id, privilegeLevel, project}); - }); - }); - }, - - setUpDoc(project_id, options, callback) { - if (options == null) { options = {}; } - if (callback == null) { callback = function(error, data) {}; } - if (!options.doc_id) { options.doc_id = FixturesManager.getRandomId(); } - if (!options.lines) { options.lines = ["doc", "lines"]; } - if (!options.version) { options.version = 42; } - if (!options.ops) { options.ops = ["mock", "ops"]; } - const {doc_id, lines, version, ops, ranges} = options; - - MockDocUpdaterServer.createMockDoc(project_id, doc_id, {lines, version, ops, ranges}); - return MockDocUpdaterServer.run(error => { - if (error != null) { throw error; } - return callback(null, {project_id, doc_id, lines, version, ops}); - }); - }, - - getRandomId() { - return require("crypto") - .createHash("sha1") - .update(Math.random().toString()) - .digest("hex") - .slice(0,24); - } -}); - \ No newline at end of file +module.exports = FixturesManager = { + setUpProject(options, callback) { + if (options == null) { + options = {} + } + if (callback == null) { + callback = function (error, data) {} + } + if (!options.user_id) { + options.user_id = FixturesManager.getRandomId() + } + if (!options.project_id) { + options.project_id = FixturesManager.getRandomId() + } + if (!options.project) { + options.project = { name: 'Test Project' } + } + const { + project_id, + user_id, + privilegeLevel, + project, + publicAccess + } = options + + const privileges = {} + privileges[user_id] = privilegeLevel + if (publicAccess) { + privileges['anonymous-user'] = publicAccess + } + + MockWebServer.createMockProject(project_id, privileges, project) + return MockWebServer.run((error) => { + if (error != null) { + throw error + } + return RealTimeClient.setSession( + { + user: { + _id: user_id, + first_name: 'Joe', + last_name: 'Bloggs' + } + }, + (error) => { + if (error != null) { + throw error + } + return callback(null, { + project_id, + user_id, + privilegeLevel, + project + }) + } + ) + }) + }, + + setUpDoc(project_id, options, callback) { + if (options == null) { + options = {} + } + if (callback == null) { + callback = function (error, data) {} + } + if (!options.doc_id) { + options.doc_id = FixturesManager.getRandomId() + } + if (!options.lines) { + options.lines = ['doc', 'lines'] + } + if (!options.version) { + options.version = 42 + } + if (!options.ops) { + options.ops = ['mock', 'ops'] + } + const { doc_id, lines, version, ops, ranges } = options + + MockDocUpdaterServer.createMockDoc(project_id, doc_id, { + lines, + version, + ops, + ranges + }) + return MockDocUpdaterServer.run((error) => { + if (error != null) { + throw error + } + return callback(null, { project_id, doc_id, lines, version, ops }) + }) + }, + + getRandomId() { + return require('crypto') + .createHash('sha1') + .update(Math.random().toString()) + .digest('hex') + .slice(0, 24) + } +} diff --git a/services/real-time/test/acceptance/js/helpers/MockDocUpdaterServer.js b/services/real-time/test/acceptance/js/helpers/MockDocUpdaterServer.js index 2ce05c4279..f9dcc57bf7 100644 --- a/services/real-time/test/acceptance/js/helpers/MockDocUpdaterServer.js +++ b/services/real-time/test/acceptance/js/helpers/MockDocUpdaterServer.js @@ -11,62 +11,80 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let MockDocUpdaterServer; -const sinon = require("sinon"); -const express = require("express"); +let MockDocUpdaterServer +const sinon = require('sinon') +const express = require('express') -module.exports = (MockDocUpdaterServer = { - docs: {}, - - createMockDoc(project_id, doc_id, data) { - return MockDocUpdaterServer.docs[`${project_id}:${doc_id}`] = data; - }, - - getDocument(project_id, doc_id, fromVersion, callback) { - if (callback == null) { callback = function(error, data) {}; } - return callback( - null, MockDocUpdaterServer.docs[`${project_id}:${doc_id}`] - ); - }, - - deleteProject: sinon.stub().callsArg(1), - - getDocumentRequest(req, res, next) { - const {project_id, doc_id} = req.params; - let {fromVersion} = req.query; - fromVersion = parseInt(fromVersion, 10); - return MockDocUpdaterServer.getDocument(project_id, doc_id, fromVersion, (error, data) => { - if (error != null) { return next(error); } - return res.json(data); - }); - }, - - deleteProjectRequest(req, res, next) { - const {project_id} = req.params; - return MockDocUpdaterServer.deleteProject(project_id, (error) => { - if (error != null) { return next(error); } - return res.sendStatus(204); - }); - }, - - running: false, - run(callback) { - if (callback == null) { callback = function(error) {}; } - if (MockDocUpdaterServer.running) { - return callback(); - } - const app = express(); - app.get("/project/:project_id/doc/:doc_id", MockDocUpdaterServer.getDocumentRequest); - app.delete("/project/:project_id", MockDocUpdaterServer.deleteProjectRequest); - return app.listen(3003, (error) => { - MockDocUpdaterServer.running = true; - return callback(error); - }).on("error", (error) => { - console.error("error starting MockDocUpdaterServer:", error.message); - return process.exit(1); - }); - } -}); +module.exports = MockDocUpdaterServer = { + docs: {}, - -sinon.spy(MockDocUpdaterServer, "getDocument"); + createMockDoc(project_id, doc_id, data) { + return (MockDocUpdaterServer.docs[`${project_id}:${doc_id}`] = data) + }, + + getDocument(project_id, doc_id, fromVersion, callback) { + if (callback == null) { + callback = function (error, data) {} + } + return callback(null, MockDocUpdaterServer.docs[`${project_id}:${doc_id}`]) + }, + + deleteProject: sinon.stub().callsArg(1), + + getDocumentRequest(req, res, next) { + const { project_id, doc_id } = req.params + let { fromVersion } = req.query + fromVersion = parseInt(fromVersion, 10) + return MockDocUpdaterServer.getDocument( + project_id, + doc_id, + fromVersion, + (error, data) => { + if (error != null) { + return next(error) + } + return res.json(data) + } + ) + }, + + deleteProjectRequest(req, res, next) { + const { project_id } = req.params + return MockDocUpdaterServer.deleteProject(project_id, (error) => { + if (error != null) { + return next(error) + } + return res.sendStatus(204) + }) + }, + + running: false, + run(callback) { + if (callback == null) { + callback = function (error) {} + } + if (MockDocUpdaterServer.running) { + return callback() + } + const app = express() + app.get( + '/project/:project_id/doc/:doc_id', + MockDocUpdaterServer.getDocumentRequest + ) + app.delete( + '/project/:project_id', + MockDocUpdaterServer.deleteProjectRequest + ) + return app + .listen(3003, (error) => { + MockDocUpdaterServer.running = true + return callback(error) + }) + .on('error', (error) => { + console.error('error starting MockDocUpdaterServer:', error.message) + return process.exit(1) + }) + } +} + +sinon.spy(MockDocUpdaterServer, 'getDocument') diff --git a/services/real-time/test/acceptance/js/helpers/MockWebServer.js b/services/real-time/test/acceptance/js/helpers/MockWebServer.js index ea928a42ab..a2cf5af50b 100644 --- a/services/real-time/test/acceptance/js/helpers/MockWebServer.js +++ b/services/real-time/test/acceptance/js/helpers/MockWebServer.js @@ -11,61 +11,72 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let MockWebServer; -const sinon = require("sinon"); -const express = require("express"); +let MockWebServer +const sinon = require('sinon') +const express = require('express') -module.exports = (MockWebServer = { - projects: {}, - privileges: {}, - - createMockProject(project_id, privileges, project) { - MockWebServer.privileges[project_id] = privileges; - return MockWebServer.projects[project_id] = project; - }, - - joinProject(project_id, user_id, callback) { - if (callback == null) { callback = function(error, project, privilegeLevel) {}; } - return callback( - null, - MockWebServer.projects[project_id], - MockWebServer.privileges[project_id][user_id] - ); - }, - - joinProjectRequest(req, res, next) { - const {project_id} = req.params; - const {user_id} = req.query; - if (project_id === 'rate-limited') { - return res.status(429).send(); - } else { - return MockWebServer.joinProject(project_id, user_id, (error, project, privilegeLevel) => { - if (error != null) { return next(error); } - return res.json({ - project, - privilegeLevel - }); - }); - } - }, - - running: false, - run(callback) { - if (callback == null) { callback = function(error) {}; } - if (MockWebServer.running) { - return callback(); - } - const app = express(); - app.post("/project/:project_id/join", MockWebServer.joinProjectRequest); - return app.listen(3000, (error) => { - MockWebServer.running = true; - return callback(error); - }).on("error", (error) => { - console.error("error starting MockWebServer:", error.message); - return process.exit(1); - }); - } -}); +module.exports = MockWebServer = { + projects: {}, + privileges: {}, - -sinon.spy(MockWebServer, "joinProject"); + createMockProject(project_id, privileges, project) { + MockWebServer.privileges[project_id] = privileges + return (MockWebServer.projects[project_id] = project) + }, + + joinProject(project_id, user_id, callback) { + if (callback == null) { + callback = function (error, project, privilegeLevel) {} + } + return callback( + null, + MockWebServer.projects[project_id], + MockWebServer.privileges[project_id][user_id] + ) + }, + + joinProjectRequest(req, res, next) { + const { project_id } = req.params + const { user_id } = req.query + if (project_id === 'rate-limited') { + return res.status(429).send() + } else { + return MockWebServer.joinProject( + project_id, + user_id, + (error, project, privilegeLevel) => { + if (error != null) { + return next(error) + } + return res.json({ + project, + privilegeLevel + }) + } + ) + } + }, + + running: false, + run(callback) { + if (callback == null) { + callback = function (error) {} + } + if (MockWebServer.running) { + return callback() + } + const app = express() + app.post('/project/:project_id/join', MockWebServer.joinProjectRequest) + return app + .listen(3000, (error) => { + MockWebServer.running = true + return callback(error) + }) + .on('error', (error) => { + console.error('error starting MockWebServer:', error.message) + return process.exit(1) + }) + } +} + +sinon.spy(MockWebServer, 'joinProject') diff --git a/services/real-time/test/acceptance/js/helpers/RealTimeClient.js b/services/real-time/test/acceptance/js/helpers/RealTimeClient.js index c5ad0d3c5b..ab8e222d93 100644 --- a/services/real-time/test/acceptance/js/helpers/RealTimeClient.js +++ b/services/real-time/test/acceptance/js/helpers/RealTimeClient.js @@ -11,91 +11,121 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let Client; -const { - XMLHttpRequest -} = require("../../libs/XMLHttpRequest"); -const io = require("socket.io-client"); -const async = require("async"); +let Client +const { XMLHttpRequest } = require('../../libs/XMLHttpRequest') +const io = require('socket.io-client') +const async = require('async') -const request = require("request"); -const Settings = require("settings-sharelatex"); -const redis = require("redis-sharelatex"); -const rclient = redis.createClient(Settings.redis.websessions); +const request = require('request') +const Settings = require('settings-sharelatex') +const redis = require('redis-sharelatex') +const rclient = redis.createClient(Settings.redis.websessions) -const uid = require('uid-safe').sync; -const signature = require("cookie-signature"); +const uid = require('uid-safe').sync +const signature = require('cookie-signature') -io.util.request = function() { - const xhr = new XMLHttpRequest(); - const _open = xhr.open; - xhr.open = function() { - _open.apply(xhr, arguments); - if (Client.cookie != null) { - return xhr.setRequestHeader("Cookie", Client.cookie); - } - }; - return xhr; -}; +io.util.request = function () { + const xhr = new XMLHttpRequest() + const _open = xhr.open + xhr.open = function () { + _open.apply(xhr, arguments) + if (Client.cookie != null) { + return xhr.setRequestHeader('Cookie', Client.cookie) + } + } + return xhr +} -module.exports = (Client = { - cookie: null, +module.exports = Client = { + cookie: null, - setSession(session, callback) { - if (callback == null) { callback = function(error) {}; } - const sessionId = uid(24); - session.cookie = {}; - return rclient.set("sess:" + sessionId, JSON.stringify(session), (error) => { - if (error != null) { return callback(error); } - const secret = Settings.security.sessionSecret; - const cookieKey = 's:' + signature.sign(sessionId, secret); - Client.cookie = `${Settings.cookieName}=${cookieKey}`; - return callback(); - }); - }, - - unsetSession(callback) { - if (callback == null) { callback = function(error) {}; } - Client.cookie = null; - return callback(); - }, - - connect(cookie) { - const client = io.connect("http://localhost:3026", {'force new connection': true}); - client.on('connectionAccepted', (_, publicId) => client.publicId = publicId); - return client; - }, - - getConnectedClients(callback) { - if (callback == null) { callback = function(error, clients) {}; } - return request.get({ - url: "http://localhost:3026/clients", - json: true - }, (error, response, data) => callback(error, data)); - }, - - getConnectedClient(client_id, callback) { - if (callback == null) { callback = function(error, clients) {}; } - return request.get({ - url: `http://localhost:3026/clients/${client_id}`, - json: true - }, (error, response, data) => callback(error, data)); - }, + setSession(session, callback) { + if (callback == null) { + callback = function (error) {} + } + const sessionId = uid(24) + session.cookie = {} + return rclient.set( + 'sess:' + sessionId, + JSON.stringify(session), + (error) => { + if (error != null) { + return callback(error) + } + const secret = Settings.security.sessionSecret + const cookieKey = 's:' + signature.sign(sessionId, secret) + Client.cookie = `${Settings.cookieName}=${cookieKey}` + return callback() + } + ) + }, + unsetSession(callback) { + if (callback == null) { + callback = function (error) {} + } + Client.cookie = null + return callback() + }, - disconnectClient(client_id, callback) { - request.post({ - url: `http://localhost:3026/client/${client_id}/disconnect`, - auth: { - user: Settings.internal.realTime.user, - pass: Settings.internal.realTime.pass - } - }, (error, response, data) => callback(error, data)); - return null; - }, + connect(cookie) { + const client = io.connect('http://localhost:3026', { + 'force new connection': true + }) + client.on( + 'connectionAccepted', + (_, publicId) => (client.publicId = publicId) + ) + return client + }, - disconnectAllClients(callback) { - return Client.getConnectedClients((error, clients) => async.each(clients, (clientView, cb) => Client.disconnectClient(clientView.client_id, cb) - , callback)); - } -}); + getConnectedClients(callback) { + if (callback == null) { + callback = function (error, clients) {} + } + return request.get( + { + url: 'http://localhost:3026/clients', + json: true + }, + (error, response, data) => callback(error, data) + ) + }, + + getConnectedClient(client_id, callback) { + if (callback == null) { + callback = function (error, clients) {} + } + return request.get( + { + url: `http://localhost:3026/clients/${client_id}`, + json: true + }, + (error, response, data) => callback(error, data) + ) + }, + + disconnectClient(client_id, callback) { + request.post( + { + url: `http://localhost:3026/client/${client_id}/disconnect`, + auth: { + user: Settings.internal.realTime.user, + pass: Settings.internal.realTime.pass + } + }, + (error, response, data) => callback(error, data) + ) + return null + }, + + disconnectAllClients(callback) { + return Client.getConnectedClients((error, clients) => + async.each( + clients, + (clientView, cb) => Client.disconnectClient(clientView.client_id, cb), + callback + ) + ) + } +} diff --git a/services/real-time/test/acceptance/js/helpers/RealtimeServer.js b/services/real-time/test/acceptance/js/helpers/RealtimeServer.js index 480836d1dd..950c4b966d 100644 --- a/services/real-time/test/acceptance/js/helpers/RealtimeServer.js +++ b/services/real-time/test/acceptance/js/helpers/RealtimeServer.js @@ -12,40 +12,53 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const app = require('../../../../app'); -const logger = require("logger-sharelatex"); -const Settings = require("settings-sharelatex"); +const app = require('../../../../app') +const logger = require('logger-sharelatex') +const Settings = require('settings-sharelatex') module.exports = { - running: false, - initing: false, - callbacks: [], - ensureRunning(callback) { - if (callback == null) { callback = function(error) {}; } - if (this.running) { - return callback(); - } else if (this.initing) { - return this.callbacks.push(callback); - } else { - this.initing = true; - this.callbacks.push(callback); - return app.listen(__guard__(Settings.internal != null ? Settings.internal.realtime : undefined, x => x.port), "localhost", error => { - if (error != null) { throw error; } - this.running = true; - logger.log("clsi running in dev mode"); + running: false, + initing: false, + callbacks: [], + ensureRunning(callback) { + if (callback == null) { + callback = function (error) {} + } + if (this.running) { + return callback() + } else if (this.initing) { + return this.callbacks.push(callback) + } else { + this.initing = true + this.callbacks.push(callback) + return app.listen( + __guard__( + Settings.internal != null ? Settings.internal.realtime : undefined, + (x) => x.port + ), + 'localhost', + (error) => { + if (error != null) { + throw error + } + this.running = true + logger.log('clsi running in dev mode') - return (() => { - const result = []; - for (callback of Array.from(this.callbacks)) { - result.push(callback()); - } - return result; - })(); - }); - } - } -}; + return (() => { + const result = [] + for (callback of Array.from(this.callbacks)) { + result.push(callback()) + } + return result + })() + } + ) + } + } +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +}