/* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * 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 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; describe("EarlyDisconnect", function() { before(done => 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); }); 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(); }); }, cb => { this.clientA = RealTimeClient.connect(); return this.clientA.on("connectionAccepted", cb); }, cb => { this.clientA.emit("joinProject", {project_id: this.project_id}, (function() {})); // disconnect before joinProject completes this.clientA.on("disconnect", () => cb()); return this.clientA.disconnect(); }, 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; })); }); 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 => { 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 => { this.clientA.emit("joinDoc", this.doc_id, (function() {})); // disconnect before joinDoc completes this.clientA.on("disconnect", () => cb()); return this.clientA.disconnect(); }, 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}`); 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(); }); }, 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 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 => { this.clientA.emit("clientTracking.updatePosition", { row: 42, column: 36, doc_id: this.doc_id }, (function() {})); // disconnect before updateClientPosition completes this.clientA.on("disconnect", () => cb()); return this.clientA.disconnect(); }, 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}), function(err, results) { if (err) { return done(err); } expect(results).to.deep.equal([]); return done(); }); return null; })); }); });