overleaf/services/real-time/test/acceptance/coffee/EarlyDisconnect.js

215 lines
6.7 KiB
JavaScript

/* eslint-disable
camelcase,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* 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(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);
});
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}, (() => {}));
// 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, (() => {}));
// 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
}, (() => {}));
// 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}), (err, results) => {
if (err) { return done(err); }
expect(results).to.deep.equal([]);
return done();
});
return null;
}));
});
});