decaffeinate: Convert ApplyUpdateTests.coffee and 18 other files to JS

This commit is contained in:
decaffeinate
2020-06-23 18:30:29 +01:00
committed by Jakob Ackermann
parent 30a9c6ed2c
commit d318e4fd0e
19 changed files with 2448 additions and 1664 deletions

View File

@@ -1,222 +1,317 @@
async = require "async"
chai = require("chai")
expect = chai.expect
chai.should()
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* 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();
RealTimeClient = require "./helpers/RealTimeClient"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const FixturesManager = require("./helpers/FixturesManager");
settings = require "settings-sharelatex"
redis = require "redis-sharelatex"
rclient = redis.createClient(settings.redis.documentupdater)
const settings = require("settings-sharelatex");
const redis = require("redis-sharelatex");
const rclient = redis.createClient(settings.redis.documentupdater);
redisSettings = settings.redis
const redisSettings = settings.redis;
describe "applyOtUpdate", ->
before ->
@update = {
describe("applyOtUpdate", function() {
before(function() {
return this.update = {
op: [{i: "foo", p: 42}]
}
describe "when authorized", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
};});
describe("when authorized", function() {
before(function(done) {
return async.series([
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "readAndWrite"
}, (e, {@project_id, @user_id}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, cb
cb => {
return this.client.emit("joinDoc", this.doc_id, cb);
},
(cb) =>
@client.emit "applyOtUpdate", @doc_id, @update, cb
], done
it "should push the doc into the pending updates list", (done) ->
rclient.lrange "pending-updates-list", 0, -1, (error, [doc_id]) =>
doc_id.should.equal "#{@project_id}:#{@doc_id}"
done()
return null
it "should push the update into redis", (done) ->
rclient.lrange redisSettings.documentupdater.key_schema.pendingUpdates({@doc_id}), 0, -1, (error, [update]) =>
update = JSON.parse(update)
update.op.should.deep.equal @update.op
update.meta.should.deep.equal {
source: @client.publicId
user_id: @user_id
cb => {
return this.client.emit("applyOtUpdate", this.doc_id, this.update, cb);
}
done()
return null
after (done) ->
async.series [
(cb) => rclient.del "pending-updates-list", cb
(cb) => rclient.del "DocsWithPendingUpdates", "#{@project_id}:#{@doc_id}", cb
(cb) => rclient.del redisSettings.documentupdater.key_schema.pendingUpdates(@doc_id), cb
], done
], done);
});
describe "when authorized with a huge edit update", ->
before (done) ->
@update = {
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.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(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
t: "update is too large".repeat(1024 * 400) // >7MB
}
}
async.series [
(cb) =>
FixturesManager.setUpProject {
};
return async.series([
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "readAndWrite"
}, (e, {@project_id, @user_id}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
@client.on "otUpdateError", (@otUpdateError) =>
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
(cb) =>
@client.emit "joinDoc", @doc_id, cb
(cb) =>
@client.emit "applyOtUpdate", @doc_id, @update, (@error) =>
cb()
], done
it "should not return an error", ->
expect(@error).to.not.exist
it "should send an otUpdateError to the client", (done) ->
setTimeout () =>
expect(@otUpdateError).to.exist
done()
, 300
it "should disconnect the client", (done) ->
setTimeout () =>
@client.socket.connected.should.equal false
done()
, 300
it "should not put the update in redis", (done) ->
rclient.llen redisSettings.documentupdater.key_schema.pendingUpdates({@doc_id}), (error, len) =>
len.should.equal 0
done()
return null
describe "when authorized to read-only with an edit update", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "readOnly"
}, (e, {@project_id, @user_id}) =>
cb(e)
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
cb(e)
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
this.client.on("connectionAccepted", cb);
return this.client.on("otUpdateError", otUpdateError => {
this.otUpdateError = otUpdateError;
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
(cb) =>
@client.emit "joinDoc", @doc_id, cb
(cb) =>
@client.emit "applyOtUpdate", @doc_id, @update, (@error) =>
cb()
], done
it "should return an error", ->
expect(@error).to.exist
it "should disconnect the client", (done) ->
setTimeout () =>
@client.socket.connected.should.equal false
done()
, 300
it "should not put the update in redis", (done) ->
rclient.llen redisSettings.documentupdater.key_schema.pendingUpdates({@doc_id}), (error, len) =>
len.should.equal 0
done()
return null
describe "when authorized to read-only with a comment update", ->
before (done) ->
@comment_update = {
op: [{c: "foo", p: 42}]
}
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "readOnly"
}, (e, {@project_id, @user_id}) =>
cb(e)
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
cb(e)
});
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
(cb) =>
@client.emit "joinDoc", @doc_id, cb
(cb) =>
@client.emit "applyOtUpdate", @doc_id, @comment_update, cb
], done
it "should push the doc into the pending updates list", (done) ->
rclient.lrange "pending-updates-list", 0, -1, (error, [doc_id]) =>
doc_id.should.equal "#{@project_id}:#{@doc_id}"
done()
return null
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
it "should push the update into redis", (done) ->
rclient.lrange redisSettings.documentupdater.key_schema.pendingUpdates({@doc_id}), 0, -1, (error, [update]) =>
update = JSON.parse(update)
update.op.should.deep.equal @comment_update.op
update.meta.should.deep.equal {
source: @client.publicId
user_id: @user_id
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()
return null
], done);
});
after (done) ->
async.series [
(cb) => rclient.del "pending-updates-list", cb
(cb) => rclient.del "DocsWithPendingUpdates", "#{@project_id}:#{@doc_id}", cb
(cb) => rclient.del redisSettings.documentupdater.key_schema.pendingUpdates({@doc_id}), cb
], done
it("should not return an error", function() {
return expect(this.error).to.not.exist;
});
it("should send an otUpdateError to the client", function(done) {
return setTimeout(() => {
expect(this.otUpdateError).to.exist;
return done();
}
, 300);
});
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);
});
});
});

View File

@@ -1,146 +1,191 @@
chai = require("chai")
expect = chai.expect
chai.should()
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const chai = require("chai");
const {
expect
} = chai;
chai.should();
RealTimeClient = require "./helpers/RealTimeClient"
MockWebServer = require "./helpers/MockWebServer"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const MockWebServer = require("./helpers/MockWebServer");
const FixturesManager = require("./helpers/FixturesManager");
async = require "async"
const async = require("async");
describe "clientTracking", ->
describe "when a client updates its cursor location", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (error, {user_id, project_id}) => { this.user_id = user_id; this.project_id = project_id; return cb(); });
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA = RealTimeClient.connect()
@clientA.on "connectionAccepted", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connectionAccepted", cb);
},
(cb) =>
@clientB = RealTimeClient.connect()
@clientB.on "connectionAccepted", cb
cb => {
this.clientB = RealTimeClient.connect();
return this.clientB.on("connectionAccepted", cb);
},
(cb) =>
@clientA.emit "joinProject", {
project_id: @project_id
}, cb
cb => {
return this.clientA.emit("joinProject", {
project_id: this.project_id
}, cb);
},
(cb) =>
@clientA.emit "joinDoc", @doc_id, cb
cb => {
return this.clientA.emit("joinDoc", this.doc_id, cb);
},
(cb) =>
@clientB.emit "joinProject", {
project_id: @project_id
}, cb
cb => {
return this.clientB.emit("joinProject", {
project_id: this.project_id
}, cb);
},
(cb) =>
@updates = []
@clientB.on "clientTracking.clientUpdated", (data) =>
@updates.push data
cb => {
this.updates = [];
this.clientB.on("clientTracking.clientUpdated", data => {
return this.updates.push(data);
});
@clientA.emit "clientTracking.updatePosition", {
row: @row = 42
column: @column = 36
doc_id: @doc_id
}, (error) ->
throw error if error?
setTimeout cb, 300 # Give the message a chance to reach client B.
], done
return this.clientA.emit("clientTracking.updatePosition", {
row: (this.row = 42),
column: (this.column = 36),
doc_id: this.doc_id
}, function(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", ->
@updates.should.deep.equal [
it("should tell other clients about the update", function() {
return this.updates.should.deep.equal([
{
row: @row
column: @column
doc_id: @doc_id
id: @clientA.publicId
user_id: @user_id
row: this.row,
column: this.column,
doc_id: this.doc_id,
id: this.clientA.publicId,
user_id: this.user_id,
name: "Joe Bloggs"
}
]
]);
});
it "should record the update in getConnectedUsers", (done) ->
@clientB.emit "clientTracking.getConnectedUsers", (error, users) =>
for user in users
if user.client_id == @clientA.publicId
return it("should record the update in getConnectedUsers", function(done) {
return this.clientB.emit("clientTracking.getConnectedUsers", (error, users) => {
for (let user of Array.from(users)) {
if (user.client_id === this.clientA.publicId) {
expect(user.cursorData).to.deep.equal({
row: @row
column: @column
doc_id: @doc_id
})
return done()
throw new Error("user was never found")
row: this.row,
column: this.column,
doc_id: this.doc_id
});
return done();
}
}
throw new Error("user was never found");
});
});
});
describe "when an anonymous client updates its cursor location", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
project: { name: "Test Project" }
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}) => cb()
}, (error, {user_id, project_id}) => { this.user_id = user_id; this.project_id = project_id; return cb(); });
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA = RealTimeClient.connect()
@clientA.on "connectionAccepted", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connectionAccepted", cb);
},
(cb) =>
@clientA.emit "joinProject", {
project_id: @project_id
}, cb
cb => {
return this.clientA.emit("joinProject", {
project_id: this.project_id
}, cb);
},
(cb) =>
RealTimeClient.setSession({}, cb)
cb => {
return RealTimeClient.setSession({}, cb);
},
(cb) =>
@anonymous = RealTimeClient.connect()
@anonymous.on "connectionAccepted", cb
cb => {
this.anonymous = RealTimeClient.connect();
return this.anonymous.on("connectionAccepted", cb);
},
(cb) =>
@anonymous.emit "joinProject", {
project_id: @project_id
}, cb
cb => {
return this.anonymous.emit("joinProject", {
project_id: this.project_id
}, cb);
},
(cb) =>
@anonymous.emit "joinDoc", @doc_id, cb
cb => {
return this.anonymous.emit("joinDoc", this.doc_id, cb);
},
(cb) =>
@updates = []
@clientA.on "clientTracking.clientUpdated", (data) =>
@updates.push data
cb => {
this.updates = [];
this.clientA.on("clientTracking.clientUpdated", data => {
return this.updates.push(data);
});
@anonymous.emit "clientTracking.updatePosition", {
row: @row = 42
column: @column = 36
doc_id: @doc_id
}, (error) ->
throw error if error?
setTimeout cb, 300 # Give the message a chance to reach client B.
], done
return this.anonymous.emit("clientTracking.updatePosition", {
row: (this.row = 42),
column: (this.column = 36),
doc_id: this.doc_id
}, function(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", ->
@updates.should.deep.equal [
return it("should tell other clients about the update", function() {
return this.updates.should.deep.equal([
{
row: @row
column: @column
doc_id: @doc_id
id: @anonymous.publicId
user_id: "anonymous-user"
row: this.row,
column: this.column,
doc_id: this.doc_id,
id: this.anonymous.publicId,
user_id: "anonymous-user",
name: ""
}
]
]);
});
});
});

View File

@@ -1,81 +1,100 @@
RealTimeClient = require "./helpers/RealTimeClient"
FixturesManager = require "./helpers/FixturesManager"
/*
* decaffeinate suggestions:
* 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");
expect = require("chai").expect
const {
expect
} = require("chai");
async = require "async"
request = require "request"
const async = require("async");
const request = require("request");
Settings = require "settings-sharelatex"
const Settings = require("settings-sharelatex");
drain = (rate, callback) ->
request.post {
url: "http://localhost:3026/drain?rate=#{rate}"
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
}, (error, response, data) => callback(error, data));
return null;
};
describe "DrainManagerTests", ->
before (done) ->
FixturesManager.setUpProject {
privilegeLevel: "owner"
describe("DrainManagerTests", function() {
before(function(done) {
FixturesManager.setUpProject({
privilegeLevel: "owner",
project: {
name: "Test Project"
}
}, (e, {@project_id, @user_id}) => done()
return null
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return done(); });
return null;
});
before (done) ->
# cleanup to speedup reconnecting
@timeout(10000)
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", (done) ->
RealTimeClient.getConnectedClients (error, data) ->
return done(error) if error
expect(data.length).to.equal(0)
done()
// trigger and check cleanup
it("should have disconnected all previous clients", done => RealTimeClient.getConnectedClients(function(error, data) {
if (error) { return done(error); }
expect(data.length).to.equal(0);
return done();
}));
describe "with two clients in the project", ->
beforeEach (done) ->
async.series [
(cb) =>
@clientA = RealTimeClient.connect()
@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) =>
@clientB = RealTimeClient.connect()
@clientB.on "connectionAccepted", cb
cb => {
this.clientB = RealTimeClient.connect();
return this.clientB.on("connectionAccepted", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, cb
cb => {
return this.clientA.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@clientB.emit "joinProject", project_id: @project_id, cb
], done
cb => {
return this.clientB.emit("joinProject", {project_id: this.project_id}, cb);
}
], done);
});
describe "starting to drain", () ->
beforeEach (done) ->
async.parallel [
(cb) =>
@clientA.on "reconnectGracefully", cb
(cb) =>
@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 (done) ->
drain(0, done) # reset drain
afterEach(done => drain(0, done)); // reset drain
it "should not timeout", ->
expect(true).to.equal(true)
it("should not timeout", () => expect(true).to.equal(true));
it "should not have disconnected", ->
expect(@clientA.socket.connected).to.equal true
expect(@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);
});
});
});
});

View File

@@ -1,160 +1,209 @@
async = require "async"
{expect} = require("chai")
/*
* 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");
RealTimeClient = require "./helpers/RealTimeClient"
MockDocUpdaterServer = require "./helpers/MockDocUpdaterServer"
MockWebServer = require "./helpers/MockWebServer"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer");
const MockWebServer = require("./helpers/MockWebServer");
const FixturesManager = require("./helpers/FixturesManager");
settings = require "settings-sharelatex"
redis = require "redis-sharelatex"
rclient = redis.createClient(settings.redis.pubsub)
rclientRT = redis.createClient(settings.redis.realtime)
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", ->
before (done) ->
MockDocUpdaterServer.run done
describe("EarlyDisconnect", function() {
before(done => MockDocUpdaterServer.run(done));
describe "when the client disconnects before joinProject completes", ->
before () ->
# slow down web-api requests to force the race condition
@actualWebAPIjoinProject = joinProject = MockWebServer.joinProject
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 () ->
MockWebServer.joinProject = @actualWebAPIjoinProject
after(function() {
return MockWebServer.joinProject = this.actualWebAPIjoinProject;
});
beforeEach (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
beforeEach(function(done) {
return async.series([
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "owner",
project: {
name: "Test Project"
}
}, (e, {@project_id, @user_id}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connectionAccepted", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connectionAccepted", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (() ->)
# disconnect before joinProject completes
@clientA.on "disconnect", () -> cb()
@clientA.disconnect()
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
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
for attempt in Array.from(length: 5).map((_, i) -> i+1)
it "should not subscribe to the pub/sub channel anymore (race #{attempt})", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
expect(resp).to.not.include "editor-events:#{@project_id}"
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", ->
beforeEach (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connectionAccepted", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connectionAccepted", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA.emit "joinDoc", @doc_id, (() ->)
# disconnect before joinDoc completes
@clientA.on "disconnect", () -> cb()
@clientA.disconnect()
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
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
for attempt in Array.from(length: 20).map((_, i) -> i+1)
it "should not subscribe to the pub/sub channels anymore (race #{attempt})", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
expect(resp).to.not.include "editor-events:#{@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}`);
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
expect(resp).to.not.include "applied-ops:#{@doc_id}"
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;
}));
});
describe "when the client disconnects before clientTracking.updatePosition starts", ->
beforeEach (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connectionAccepted", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connectionAccepted", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA.emit "joinDoc", @doc_id, cb
cb => {
return this.clientA.emit("joinDoc", this.doc_id, cb);
},
(cb) =>
@clientA.emit "clientTracking.updatePosition", {
row: 42
column: 36
doc_id: @doc_id
}, (() ->)
# disconnect before updateClientPosition completes
@clientA.on "disconnect", () -> cb()
@clientA.disconnect()
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
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
for attempt in Array.from(length: 20).map((_, i) -> i+1)
it "should not show the client as connected (race #{attempt})", (done) ->
rclientRT.smembers KeysRT.clientsInProject({project_id: @project_id}), (err, results) ->
return done(err) if err
expect(results).to.deep.equal([])
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}), function(err, results) {
if (err) { return done(err); }
expect(results).to.deep.equal([]);
return done();
});
return null;
}));
});
});

View File

@@ -1,68 +1,91 @@
async = require('async')
expect = require('chai').expect
request = require('request').defaults({
/*
* decaffeinate suggestions:
* 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 request = require('request').defaults({
baseUrl: 'http://localhost:3026'
})
});
RealTimeClient = require "./helpers/RealTimeClient"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const FixturesManager = require("./helpers/FixturesManager");
describe 'HttpControllerTests', ->
describe 'without a user', ->
it 'should return 404 for the client view', (done) ->
client_id = 'not-existing'
request.get {
url: "/clients/#{client_id}"
json: true
}, (error, response, data) ->
return done(error) if error
expect(response.statusCode).to.equal(404)
done()
describe('HttpControllerTests', function() {
describe('without a user', () => it('should return 404 for the client view', function(done) {
const client_id = 'not-existing';
return request.get({
url: `/clients/${client_id}`,
json: true
}, function(error, response, data) {
if (error) { return done(error); }
expect(response.statusCode).to.equal(404);
return done();
});
}));
describe 'with a user and after joining a project', ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
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}) =>
cb(error)
}, (error, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(error);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {}, (error, {@doc_id}) =>
cb(error)
cb => {
return FixturesManager.setUpDoc(this.project_id, {}, (error, {doc_id}) => {
this.doc_id = doc_id;
return cb(error);
});
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", {@project_id}, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, cb
], done
cb => {
return this.client.emit("joinDoc", this.doc_id, cb);
}
], done);
});
it 'should send a client view', (done) ->
request.get {
url: "/clients/#{@client.socket.sessionid}"
return it('should send a client view', function(done) {
return request.get({
url: `/clients/${this.client.socket.sessionid}`,
json: true
}, (error, response, data) =>
return done(error) if 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
}, (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: @client.socket.sessionid,
client_id: this.client.socket.sessionid,
first_name: 'Joe',
last_name: 'Bloggs',
project_id: @project_id,
user_id: @user_id,
project_id: this.project_id,
user_id: this.user_id,
rooms: [
@project_id,
@doc_id,
this.project_id,
this.doc_id,
]
})
done()
});
return done();
});
});
});
});

View File

@@ -1,246 +1,351 @@
chai = require("chai")
expect = chai.expect
chai.should()
/*
* 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 chai = require("chai");
const {
expect
} = chai;
chai.should();
RealTimeClient = require "./helpers/RealTimeClient"
MockDocUpdaterServer = require "./helpers/MockDocUpdaterServer"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer");
const FixturesManager = require("./helpers/FixturesManager");
async = require "async"
const async = require("async");
describe "joinDoc", ->
before ->
@lines = ["test", "doc", "lines"]
@version = 42
@ops = ["mock", "doc", "ops"]
@ranges = {"mock": "ranges"}
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", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
describe("when authorised readAndWrite", function() {
before(function(done) {
return async.series([
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "readAndWrite"
}, (e, {@project_id, @user_id}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops, @ranges}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, (error, @returnedArgs...) => 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", ->
MockDocUpdaterServer.getDocument
.calledWith(@project_id, @doc_id, -1)
.should.equal true
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", ->
@returnedArgs.should.deep.equal [@lines, @version, @ops, @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]);
});
it "should have joined the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal true
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 readOnly", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
describe("when authorised readOnly", function() {
before(function(done) {
return async.series([
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "readOnly"
}, (e, {@project_id, @user_id}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops, @ranges}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, (error, @returnedArgs...) => 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", ->
MockDocUpdaterServer.getDocument
.calledWith(@project_id, @doc_id, -1)
.should.equal true
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", ->
@returnedArgs.should.deep.equal [@lines, @version, @ops, @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]);
});
it "should have joined the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal true
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", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
describe("when authorised as owner", function() {
before(function(done) {
return async.series([
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "owner"
}, (e, {@project_id, @user_id}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops, @ranges}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, (error, @returnedArgs...) => 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", ->
MockDocUpdaterServer.getDocument
.calledWith(@project_id, @doc_id, -1)
.should.equal true
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", ->
@returnedArgs.should.deep.equal [@lines, @version, @ops, @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]);
});
it "should have joined the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal true
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.
// 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", ->
before (done) ->
@fromVersion = 36
async.series [
(cb) =>
FixturesManager.setUpProject {
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}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops, @ranges}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, @fromVersion, (error, @returnedArgs...) => cb(error)
], done
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", ->
MockDocUpdaterServer.getDocument
.calledWith(@project_id, @doc_id, @fromVersion)
.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", ->
@returnedArgs.should.deep.equal [@lines, @version, @ops, @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]);
});
it "should have joined the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal true
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 "with options", ->
before (done) ->
@options = { encodeRanges: true }
async.series [
(cb) =>
FixturesManager.setUpProject {
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}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops, @ranges}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, @options, (error, @returnedArgs...) => 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 with the default fromVersion", ->
MockDocUpdaterServer.getDocument
.calledWith(@project_id, @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", ->
@returnedArgs.should.deep.equal [@lines, @version, @ops, @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]);
});
it "should have joined the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal true
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 "with fromVersion and options", ->
before (done) ->
@fromVersion = 36
@options = { encodeRanges: true }
async.series [
(cb) =>
FixturesManager.setUpProject {
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}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops, @ranges}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, @fromVersion, @options, (error, @returnedArgs...) => 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 with the fromVersion", ->
MockDocUpdaterServer.getDocument
.calledWith(@project_id, @doc_id, @fromVersion)
.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", ->
@returnedArgs.should.deep.equal [@lines, @version, @ops, @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]);
});
it "should have joined the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal true
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();
});
});
});
});

View File

@@ -1,108 +1,162 @@
chai = require("chai")
expect = chai.expect
chai.should()
/*
* 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 chai = require("chai");
const {
expect
} = chai;
chai.should();
RealTimeClient = require "./helpers/RealTimeClient"
MockWebServer = require "./helpers/MockWebServer"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const MockWebServer = require("./helpers/MockWebServer");
const FixturesManager = require("./helpers/FixturesManager");
async = require "async"
const async = require("async");
describe "joinProject", ->
describe "when authorized", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
cb(error)
], 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);
});
it "should get the project from web", ->
MockWebServer.joinProject
.calledWith(@project_id, @user_id)
.should.equal true
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", ->
@project.should.deep.equal {
it("should return the project", function() {
return this.project.should.deep.equal({
name: "Test Project"
}
});
});
it "should return the privilege level", ->
@privilegeLevel.should.equal "owner"
it("should return the privilege level", function() {
return this.privilegeLevel.should.equal("owner");
});
it "should return the protocolVersion", ->
@protocolVersion.should.equal 2
it("should return the protocolVersion", function() {
return this.protocolVersion.should.equal(2);
});
it "should have joined the project room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@project_id in client.rooms).to.equal true
done()
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();
});
});
it "should have marked the user as connected", (done) ->
@client.emit "clientTracking.getConnectedUsers", (error, users) =>
connected = false
for user in users
if user.client_id == @client.publicId and user.user_id == @user_id
connected = true
break
expect(connected).to.equal true
done()
return it("should have marked the user as connected", function(done) {
return this.client.emit("clientTracking.getConnectedUsers", (error, users) => {
let connected = false;
for (let 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", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: null
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}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, (@error, @project, @privilegeLevel, @protocolVersion) =>
cb()
], done
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", ->
@error.message.should.equal "not authorized"
it("should return an error", function() {
return this.error.message.should.equal("not authorized");
});
it "should not have joined the project room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@project_id in client.rooms).to.equal false
done()
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();
});
});
});
describe "when over rate limit", ->
before (done) ->
async.series [
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
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) =>
@client.emit "joinProject", project_id: 'rate-limited', (@error) =>
cb()
], done
cb => {
return this.client.emit("joinProject", {project_id: 'rate-limited'}, error => {
this.error = error;
return cb();
});
}
], done);
});
it "should return a TooManyRequests error code", ->
@error.message.should.equal "rate-limit hit when joining project"
@error.code.should.equal "TooManyRequests"
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");
});
});
});

View File

@@ -1,86 +1,121 @@
chai = require("chai")
expect = chai.expect
chai.should()
sinon = require("sinon")
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const chai = require("chai");
const {
expect
} = chai;
chai.should();
const sinon = require("sinon");
RealTimeClient = require "./helpers/RealTimeClient"
MockDocUpdaterServer = require "./helpers/MockDocUpdaterServer"
FixturesManager = require "./helpers/FixturesManager"
logger = require("logger-sharelatex")
const RealTimeClient = require("./helpers/RealTimeClient");
const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer");
const FixturesManager = require("./helpers/FixturesManager");
const logger = require("logger-sharelatex");
async = require "async"
const async = require("async");
describe "leaveDoc", ->
before ->
@lines = ["test", "doc", "lines"]
@version = 42
@ops = ["mock", "doc", "ops"]
sinon.spy(logger, "error")
sinon.spy(logger, "warn")
sinon.spy(logger, "log")
@other_doc_id = FixturesManager.getRandomId()
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 ->
logger.error.restore() # remove the spy
logger.warn.restore()
logger.log.restore()
after(function() {
logger.error.restore(); // remove the spy
logger.warn.restore();
return logger.log.restore();
});
describe "when joined to a doc", ->
beforeEach (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
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}) =>
cb(e)
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id, cb
cb => {
return this.client.emit("joinProject", {project_id: this.project_id}, cb);
},
(cb) =>
@client.emit "joinDoc", @doc_id, (error, @returnedArgs...) => cb(error)
], done
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", ->
beforeEach (done) ->
@client.emit "leaveDoc", @doc_id, (error) ->
throw error if error?
done()
describe("then leaving the doc", function() {
beforeEach(function(done) {
return this.client.emit("leaveDoc", this.doc_id, function(error) {
if (error != null) { throw error; }
return done();
});
});
it "should have left the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal false
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", ->
beforeEach (done) ->
@client.emit "leaveDoc", @doc_id, () ->
@client.emit "joinDoc", @doc_id, () ->
@client.emit "leaveDoc", @doc_id, (error) ->
throw error if error?
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, function() {});
this.client.emit("joinDoc", this.doc_id, function() {});
return this.client.emit("leaveDoc", this.doc_id, function(error) {
if (error != null) { throw error; }
return done();
});
});
it "should not trigger an error", ->
sinon.assert.neverCalledWith(logger.error, sinon.match.any, "not subscribed - shouldn't happen")
it("should not trigger an error", () => sinon.assert.neverCalledWith(logger.error, sinon.match.any, "not subscribed - shouldn't happen"));
it "should have left the doc room", (done) ->
RealTimeClient.getConnectedClient @client.socket.sessionid, (error, client) =>
expect(@doc_id in client.rooms).to.equal false
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 for a room the client has not joined ", ->
beforeEach (done) ->
@client.emit "leaveDoc", @other_doc_id, (error) ->
throw error if error?
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, function(error) {
if (error != null) { throw error; }
return done();
});
});
it "should trigger a low level message only", ->
sinon.assert.calledWith(logger.log, sinon.match.any, "ignoring request from client to leave room it is not in")
return it("should trigger a low level message only", () => sinon.assert.calledWith(logger.log, sinon.match.any, "ignoring request from client to leave room it is not in"));
});
});
});

View File

@@ -1,147 +1,206 @@
RealTimeClient = require "./helpers/RealTimeClient"
MockDocUpdaterServer = require "./helpers/MockDocUpdaterServer"
FixturesManager = require "./helpers/FixturesManager"
/*
* 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 RealTimeClient = require("./helpers/RealTimeClient");
const MockDocUpdaterServer = require("./helpers/MockDocUpdaterServer");
const FixturesManager = require("./helpers/FixturesManager");
async = require "async"
const async = require("async");
settings = require "settings-sharelatex"
redis = require "redis-sharelatex"
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", ->
before (done) ->
MockDocUpdaterServer.run done
describe("leaveProject", function() {
before(done => MockDocUpdaterServer.run(done));
describe "with other clients in the project", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connectionAccepted", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connectionAccepted", cb);
},
(cb) =>
@clientB = RealTimeClient.connect()
@clientB.on "connectionAccepted", cb
cb => {
this.clientB = RealTimeClient.connect();
this.clientB.on("connectionAccepted", cb);
@clientBDisconnectMessages = []
@clientB.on "clientTracking.clientDisconnected", (data) =>
@clientBDisconnectMessages.push data
this.clientBDisconnectMessages = [];
return this.clientB.on("clientTracking.clientDisconnected", data => {
return this.clientBDisconnectMessages.push(data);
});
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
@clientB.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA.emit "joinDoc", @doc_id, cb
(cb) =>
@clientB.emit "joinDoc", @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
@clientA.on "disconnect", () -> cb()
@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
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", ->
@clientBDisconnectMessages.should.deep.equal [@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", (done) ->
@clientB.emit "clientTracking.getConnectedUsers", (error, users) =>
for user in users
if user.client_id == @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 (let 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", ->
MockDocUpdaterServer.deleteProject
.calledWith(@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", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.include "editor-events:#{@project_id}"
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;
});
it "should remain subscribed to the applied-ops channels", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.include "applied-ops:#{@doc_id}"
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;
});
});
describe "with no other clients in the project", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connect", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connect", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
cb(e)
(cb) =>
@clientA.emit "joinDoc", @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
@clientA.on "disconnect", () -> cb()
@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
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", ->
MockDocUpdaterServer.deleteProject
.calledWith(@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", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.not.include "editor-events:#{@project_id}"
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;
});
it "should not subscribe to the applied-ops channels anymore", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.not.include "applied-ops:#{@doc_id}"
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;
});
});
});

View File

@@ -1,205 +1,277 @@
RealTimeClient = require "./helpers/RealTimeClient"
MockDocUpdaterServer = require "./helpers/MockDocUpdaterServer"
FixturesManager = require "./helpers/FixturesManager"
/*
* decaffeinate suggestions:
* 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");
async = require "async"
const async = require("async");
settings = require "settings-sharelatex"
redis = require "redis-sharelatex"
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", ->
before (done) ->
MockDocUpdaterServer.run done
describe("PubSubRace", function() {
before(done => MockDocUpdaterServer.run(done));
describe "when the client leaves a doc before joinDoc completes", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connect", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connect", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA.emit "joinDoc", @doc_id, () ->
# leave before joinDoc completes
@clientA.emit "leaveDoc", @doc_id, cb
cb => {
this.clientA.emit("joinDoc", this.doc_id, function() {});
// leave before joinDoc completes
return this.clientA.emit("leaveDoc", this.doc_id, cb);
},
(cb) =>
# wait for subscribe and unsubscribe
setTimeout cb, 100
], done
cb => {
// wait for subscribe and unsubscribe
return setTimeout(cb, 100);
}
], done);
});
it "should not subscribe to the applied-ops channels anymore", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.not.include "applied-ops:#{@doc_id}"
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", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connect", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connect", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, cb
cb => {
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
this.clientA.emit("joinDoc", this.doc_id, function() {});
return this.clientA.emit("leaveDoc", this.doc_id, cb);
},
(cb) =>
# wait for subscribe and unsubscribe
setTimeout cb, 100
], done
cb => {
// wait for subscribe and unsubscribe
return setTimeout(cb, 100);
}
], done);
});
it "should not subscribe to the applied-ops channels anymore", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.not.include "applied-ops:#{@doc_id}"
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", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connect", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connect", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, () ->
@clientA.emit "leaveDoc", @doc_id, () ->
@clientA.emit "joinDoc", @doc_id, cb
cb => {
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
this.clientA.emit("joinDoc", this.doc_id, function() {});
this.clientA.emit("leaveDoc", this.doc_id, function() {});
return this.clientA.emit("joinDoc", this.doc_id, cb);
},
(cb) =>
# wait for subscribe and unsubscribe
setTimeout cb, 100
], done
cb => {
// wait for subscribe and unsubscribe
return setTimeout(cb, 100);
}
], done);
});
it "should subscribe to the applied-ops channels", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.include "applied-ops:#{@doc_id}"
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;
});
});
describe "when the client disconnects before joinDoc completes", ->
before (done) ->
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
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}) => cb()
}, (e, {project_id, user_id}) => { this.project_id = project_id; this.user_id = user_id; return cb(); });
},
(cb) =>
@clientA = RealTimeClient.connect()
@clientA.on "connect", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connect", cb);
},
(cb) =>
@clientA.emit "joinProject", project_id: @project_id, (error, @project, @privilegeLevel, @protocolVersion) =>
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) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
joinDocCompleted = false
@clientA.emit "joinDoc", @doc_id, () ->
joinDocCompleted = true
# leave before joinDoc completes
setTimeout () =>
if joinDocCompleted
return cb(new Error('joinDocCompleted -- lower timeout'))
@clientA.on "disconnect", () -> cb()
@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
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", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.not.include "editor-events:#{@project_id}"
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;
});
it "should not subscribe to the applied-ops channels anymore", (done) ->
rclient.pubsub 'CHANNELS', (err, resp) =>
return done(err) if err
resp.should.not.include "applied-ops:#{@doc_id}"
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;
});
});
});

View File

@@ -1,208 +1,274 @@
chai = require("chai")
expect = chai.expect
chai.should()
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const chai = require("chai");
const {
expect
} = chai;
chai.should();
RealTimeClient = require "./helpers/RealTimeClient"
MockWebServer = require "./helpers/MockWebServer"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const MockWebServer = require("./helpers/MockWebServer");
const FixturesManager = require("./helpers/FixturesManager");
async = require "async"
const async = require("async");
settings = require "settings-sharelatex"
redis = require "redis-sharelatex"
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", ->
beforeEach (done) ->
@lines = ["test", "doc", "lines"]
@version = 42
@ops = ["mock", "doc", "ops"]
describe("receiveUpdate", function() {
beforeEach(function(done) {
this.lines = ["test", "doc", "lines"];
this.version = 42;
this.ops = ["mock", "doc", "ops"];
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
return async.series([
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "owner",
project: { name: "Test Project" }
}, (error, {@user_id, @project_id}) => cb()
}, (error, {user_id, project_id}) => { this.user_id = user_id; this.project_id = project_id; return cb(); });
},
(cb) =>
FixturesManager.setUpDoc @project_id, {@lines, @version, @ops}, (e, {@doc_id}) =>
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) =>
@clientA = RealTimeClient.connect()
@clientA.on "connectionAccepted", cb
cb => {
this.clientA = RealTimeClient.connect();
return this.clientA.on("connectionAccepted", cb);
},
(cb) =>
@clientB = RealTimeClient.connect()
@clientB.on "connectionAccepted", cb
cb => {
this.clientB = RealTimeClient.connect();
return this.clientB.on("connectionAccepted", cb);
},
(cb) =>
@clientA.emit "joinProject", {
project_id: @project_id
}, cb
cb => {
return this.clientA.emit("joinProject", {
project_id: this.project_id
}, cb);
},
(cb) =>
@clientA.emit "joinDoc", @doc_id, cb
cb => {
return this.clientA.emit("joinDoc", this.doc_id, cb);
},
(cb) =>
@clientB.emit "joinProject", {
project_id: @project_id
}, cb
cb => {
return this.clientB.emit("joinProject", {
project_id: this.project_id
}, cb);
},
(cb) =>
@clientB.emit "joinDoc", @doc_id, cb
cb => {
return this.clientB.emit("joinDoc", this.doc_id, cb);
},
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
cb => {
return FixturesManager.setUpProject({
privilegeLevel: "owner",
project: {name: "Test Project"}
}, (error, {user_id: @user_id_second, project_id: @project_id_second}) => cb()
}, (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(); });
},
(cb) =>
FixturesManager.setUpDoc @project_id_second, {@lines, @version, @ops}, (e, {doc_id: @doc_id_second}) =>
cb(e)
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) =>
@clientC = RealTimeClient.connect()
@clientC.on "connectionAccepted", cb
cb => {
this.clientC = RealTimeClient.connect();
return this.clientC.on("connectionAccepted", cb);
},
(cb) =>
@clientC.emit "joinProject", {
project_id: @project_id_second
}, cb
(cb) =>
@clientC.emit "joinDoc", @doc_id_second, 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) =>
@clientAUpdates = []
@clientA.on "otUpdateApplied", (update) => @clientAUpdates.push(update)
@clientBUpdates = []
@clientB.on "otUpdateApplied", (update) => @clientBUpdates.push(update)
@clientCUpdates = []
@clientC.on "otUpdateApplied", (update) => @clientCUpdates.push(update)
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));
@clientAErrors = []
@clientA.on "otUpdateError", (error) => @clientAErrors.push(error)
@clientBErrors = []
@clientB.on "otUpdateError", (error) => @clientBErrors.push(error)
@clientCErrors = []
@clientC.on "otUpdateError", (error) => @clientCErrors.push(error)
cb()
], done
afterEach () ->
@clientA?.disconnect()
@clientB?.disconnect()
@clientC?.disconnect()
describe "with an update from clientA", ->
beforeEach (done) ->
@update = {
doc_id: @doc_id
op:
meta:
source: @clientA.publicId
v: @version
doc: @doc_id
op: [{i: "foo", p: 50}]
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();
}
rclient.publish "applied-ops", JSON.stringify(@update)
setTimeout done, 200 # Give clients time to get message
it "should send the full op to clientB", ->
@clientBUpdates.should.deep.equal [@update.op]
it "should send an ack to clientA", ->
@clientAUpdates.should.deep.equal [{
v: @version, doc: @doc_id
}]
], done);
});
it "should send nothing to clientC", ->
@clientCUpdates.should.deep.equal []
afterEach(function() {
if (this.clientA != null) {
this.clientA.disconnect();
}
if (this.clientB != null) {
this.clientB.disconnect();
}
return (this.clientC != null ? this.clientC.disconnect() : undefined);
});
describe "with an update from clientC", ->
beforeEach (done) ->
@update = {
doc_id: @doc_id_second
op:
meta:
source: @clientC.publicId
v: @version
doc: @doc_id_second
op: [{i: "update from clientC", p: 50}]
}
rclient.publish "applied-ops", JSON.stringify(@update)
setTimeout done, 200 # Give clients time to get message
it "should send nothing to clientA", ->
@clientAUpdates.should.deep.equal []
it "should send nothing to clientB", ->
@clientBUpdates.should.deep.equal []
it "should send an ack to clientC", ->
@clientCUpdates.should.deep.equal [{
v: @version, doc: @doc_id_second
}]
describe "with an update from a remote client for project 1", ->
beforeEach (done) ->
@update = {
doc_id: @doc_id
op:
meta:
source: 'this-is-a-remote-client-id'
v: @version
doc: @doc_id
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(@update)
setTimeout done, 200 # Give clients time to get message
it "should send the full op to clientA", ->
@clientAUpdates.should.deep.equal [@update.op]
}
};
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", ->
@clientBUpdates.should.deep.equal [@update.op]
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
}]);
});
it "should send nothing to clientC", ->
@clientCUpdates.should.deep.equal []
return it("should send nothing to clientC", function() {
return this.clientCUpdates.should.deep.equal([]);
});
});
describe "with an error for the first project", ->
beforeEach (done) ->
rclient.publish "applied-ops", JSON.stringify({doc_id: @doc_id, error: @error = "something went wrong"})
setTimeout done, 200 # Give clients time to get message
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
it "should send the error to the clients in the first project", ->
@clientAErrors.should.deep.equal [@error]
@clientBErrors.should.deep.equal [@error]
it("should send nothing to clientA", function() {
return this.clientAUpdates.should.deep.equal([]);
});
it "should not send any errors to the client in the second project", ->
@clientCErrors.should.deep.equal []
it("should send nothing to clientB", function() {
return this.clientBUpdates.should.deep.equal([]);
});
it "should disconnect the clients of the first project", ->
@clientA.socket.connected.should.equal false
@clientB.socket.connected.should.equal false
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 not disconnect the client in the second project", ->
@clientC.socket.connected.should.equal true
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
describe "with an error for the second project", ->
beforeEach (done) ->
rclient.publish "applied-ops", JSON.stringify({doc_id: @doc_id_second, error: @error = "something went wrong"})
setTimeout done, 200 # Give clients time to get message
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]);
});
it "should not send any errors to the clients in the first project", ->
@clientAErrors.should.deep.equal []
@clientBErrors.should.deep.equal []
return it("should send nothing to clientC", function() {
return this.clientCUpdates.should.deep.equal([]);
});
});
it "should send the error to the client in the second project", ->
@clientCErrors.should.deep.equal [@error]
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 not disconnect the clients of the first project", ->
@clientA.socket.connected.should.equal true
@clientB.socket.connected.should.equal true
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 disconnect the client in the second project", ->
@clientC.socket.connected.should.equal false
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);
});
});
});

View File

@@ -1,76 +1,101 @@
async = require "async"
{expect} = require("chai")
/*
* decaffeinate suggestions:
* 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");
RealTimeClient = require "./helpers/RealTimeClient"
FixturesManager = require "./helpers/FixturesManager"
const RealTimeClient = require("./helpers/RealTimeClient");
const FixturesManager = require("./helpers/FixturesManager");
describe "Router", ->
describe "joinProject", ->
describe "when there is no callback provided", ->
after () ->
process.removeListener('unhandledRejection', @onUnhandled)
describe("Router", () => describe("joinProject", function() {
describe("when there is no callback provided", function() {
after(function() {
return process.removeListener('unhandledRejection', this.onUnhandled);
});
before (done) ->
@onUnhandled = (error) ->
done(error)
process.on('unhandledRejection', @onUnhandled)
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
project: {
name: "Test Project"
}
}, (e, {@project_id, @user_id}) =>
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"
}
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", project_id: @project_id
setTimeout(cb, 100)
], done
cb => {
this.client.emit("joinProject", {project_id: this.project_id});
return setTimeout(cb, 100);
}
], done);
});
it "should keep on going", ->
expect('still running').to.exist
return it("should keep on going", () => expect('still running').to.exist);
});
describe "when there are too many arguments", ->
after () ->
process.removeListener('unhandledRejection', @onUnhandled)
return describe("when there are too many arguments", function() {
after(function() {
return process.removeListener('unhandledRejection', this.onUnhandled);
});
before (done) ->
@onUnhandled = (error) ->
done(error)
process.on('unhandledRejection', @onUnhandled)
async.series [
(cb) =>
FixturesManager.setUpProject {
privilegeLevel: "owner"
project: {
name: "Test Project"
}
}, (e, {@project_id, @user_id}) =>
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"
}
}, (e, {project_id, user_id}) => {
this.project_id = project_id;
this.user_id = user_id;
return cb(e);
});
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client = RealTimeClient.connect()
@client.on "connectionAccepted", cb
cb => {
this.client = RealTimeClient.connect();
return this.client.on("connectionAccepted", cb);
},
(cb) =>
@client.emit "joinProject", 1, 2, 3, 4, 5, (@error) =>
cb()
], done
cb => {
return this.client.emit("joinProject", 1, 2, 3, 4, 5, error => {
this.error = error;
return cb();
});
}
], done);
});
it "should return an error message", ->
expect(@error.message).to.equal('unexpected arguments')
return it("should return an error message", function() {
return expect(this.error.message).to.equal('unexpected arguments');
});
});
}));

View File

@@ -1,67 +1,90 @@
RealTimeClient = require("./helpers/RealTimeClient")
Settings = require("settings-sharelatex")
{expect} = require('chai')
/*
* decaffeinate suggestions:
* 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');
describe 'SessionSockets', ->
before ->
@checkSocket = (fn) ->
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', ->
before ->
RealTimeClient.cookie = null
describe('without cookies', function() {
before(() => RealTimeClient.cookie = null);
it 'should return a lookup error', (done) ->
@checkSocket (error) ->
expect(error).to.exist
expect(error.message).to.equal('invalid session')
done()
return it('should return a lookup error', function(done) {
return this.checkSocket(function(error) {
expect(error).to.exist;
expect(error.message).to.equal('invalid session');
return done();
});
});
});
describe 'with a different cookie', ->
before ->
RealTimeClient.cookie = "some.key=someValue"
describe('with a different cookie', function() {
before(() => RealTimeClient.cookie = "some.key=someValue");
it 'should return a lookup error', (done) ->
@checkSocket (error) ->
expect(error).to.exist
expect(error.message).to.equal('invalid session')
done()
return it('should return a lookup error', function(done) {
return this.checkSocket(function(error) {
expect(error).to.exist;
expect(error.message).to.equal('invalid session');
return done();
});
});
});
describe 'with an invalid cookie', ->
before (done) ->
RealTimeClient.setSession {}, (error) ->
return done(error) if error
RealTimeClient.cookie = "#{Settings.cookieName}=#{
describe('with an invalid cookie', function() {
before(function(done) {
RealTimeClient.setSession({}, function(error) {
if (error) { return done(error); }
RealTimeClient.cookie = `${Settings.cookieName}=${
RealTimeClient.cookie.slice(17, 49)
}"
done()
return null
}`;
return done();
});
return null;
});
it 'should return a lookup error', (done) ->
@checkSocket (error) ->
expect(error).to.exist
expect(error.message).to.equal('invalid session')
done()
return it('should return a lookup error', function(done) {
return this.checkSocket(function(error) {
expect(error).to.exist;
expect(error.message).to.equal('invalid session');
return done();
});
});
});
describe 'with a valid cookie and no matching session', ->
before ->
RealTimeClient.cookie = "#{Settings.cookieName}=unknownId"
describe('with a valid cookie and no matching session', function() {
before(() => RealTimeClient.cookie = `${Settings.cookieName}=unknownId`);
it 'should return a lookup error', (done) ->
@checkSocket (error) ->
expect(error).to.exist
expect(error.message).to.equal('invalid session')
done()
return it('should return a lookup error', function(done) {
return this.checkSocket(function(error) {
expect(error).to.exist;
expect(error.message).to.equal('invalid session');
return done();
});
});
});
describe 'with a valid cookie and a matching session', ->
before (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;
});
it 'should not return an error', (done) ->
@checkSocket (error) ->
expect(error).to.not.exist
done()
return it('should not return an error', function(done) {
return this.checkSocket(function(error) {
expect(error).to.not.exist;
return done();
});
});
});
});

View File

@@ -1,35 +1,51 @@
chai = require("chai")
expect = chai.expect
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const chai = require("chai");
const {
expect
} = chai;
RealTimeClient = require "./helpers/RealTimeClient"
const RealTimeClient = require("./helpers/RealTimeClient");
describe "Session", ->
describe "with an established session", ->
before (done) ->
@user_id = "mock-user-id"
RealTimeClient.setSession {
user: { _id: @user_id }
}, (error) =>
throw error if error?
@client = RealTimeClient.connect()
return done()
return null
describe("Session", () => 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", (done) ->
disconnected = false
@client.on "disconnect", () ->
disconnected = true
setTimeout () =>
expect(disconnected).to.equal false
done()
, 500
it "should appear in the list of connected clients", (done) ->
RealTimeClient.getConnectedClients (error, clients) =>
included = false
for client in clients
if client.client_id == @client.socket.sessionid
included = true
break
expect(included).to.equal true
done()
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 (let client of Array.from(clients)) {
if (client.client_id === this.client.socket.sessionid) {
included = true;
break;
}
}
expect(included).to.equal(true);
return done();
});
});
}));

View File

@@ -1,48 +1,67 @@
RealTimeClient = require "./RealTimeClient"
MockWebServer = require "./MockWebServer"
MockDocUpdaterServer = require "./MockDocUpdaterServer"
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let FixturesManager;
const RealTimeClient = require("./RealTimeClient");
const MockWebServer = require("./MockWebServer");
const MockDocUpdaterServer = require("./MockDocUpdaterServer");
module.exports = FixturesManager =
setUpProject: (options = {}, callback = (error, data) ->) ->
options.user_id ||= FixturesManager.getRandomId()
options.project_id ||= FixturesManager.getRandomId()
options.project ||= { name: "Test Project" }
{project_id, user_id, privilegeLevel, project, publicAccess} = options
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;
privileges = {}
privileges[user_id] = privilegeLevel
if publicAccess
privileges["anonymous-user"] = publicAccess
const privileges = {};
privileges[user_id] = privilegeLevel;
if (publicAccess) {
privileges["anonymous-user"] = publicAccess;
}
MockWebServer.createMockProject(project_id, privileges, project)
MockWebServer.run (error) =>
throw error if error?
RealTimeClient.setSession {
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"
_id: user_id,
first_name: "Joe",
last_name: "Bloggs"
}
}, (error) =>
throw error if error?
callback null, {project_id, user_id, privilegeLevel, project}
}, error => {
if (error != null) { throw error; }
return callback(null, {project_id, user_id, privilegeLevel, project});
});
});
},
setUpDoc: (project_id, options = {}, callback = (error, data) ->) ->
options.doc_id ||= FixturesManager.getRandomId()
options.lines ||= ["doc", "lines"]
options.version ||= 42
options.ops ||= ["mock", "ops"]
{doc_id, lines, version, ops, ranges} = options
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}
MockDocUpdaterServer.run (error) =>
throw error if error?
callback null, {project_id, doc_id, lines, version, ops}
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: () ->
getRandomId() {
return require("crypto")
.createHash("sha1")
.update(Math.random().toString())
.digest("hex")
.slice(0,24)
.slice(0,24);
}
});

View File

@@ -1,46 +1,65 @@
sinon = require "sinon"
express = require "express"
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let MockDocUpdaterServer;
const sinon = require("sinon");
const express = require("express");
module.exports = MockDocUpdaterServer =
docs: {}
module.exports = (MockDocUpdaterServer = {
docs: {},
createMockDoc: (project_id, doc_id, data) ->
MockDocUpdaterServer.docs["#{project_id}:#{doc_id}"] = data
createMockDoc(project_id, doc_id, data) {
return MockDocUpdaterServer.docs[`${project_id}:${doc_id}`] = data;
},
getDocument: (project_id, doc_id, fromVersion, callback = (error, data) ->) ->
callback(
null, MockDocUpdaterServer.docs["#{project_id}:#{doc_id}"]
)
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)
deleteProject: sinon.stub().callsArg(1),
getDocumentRequest: (req, res, next) ->
{project_id, doc_id} = req.params
{fromVersion} = req.query
fromVersion = parseInt(fromVersion, 10)
MockDocUpdaterServer.getDocument project_id, doc_id, fromVersion, (error, data) ->
return next(error) if error?
res.json data
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, function(error, data) {
if (error != null) { return next(error); }
return res.json(data);
});
},
deleteProjectRequest: (req, res, next) ->
{project_id} = req.params
MockDocUpdaterServer.deleteProject project_id, (error) ->
return next(error) if error?
res.sendStatus 204
deleteProjectRequest(req, res, next) {
const {project_id} = req.params;
return MockDocUpdaterServer.deleteProject(project_id, function(error) {
if (error != null) { return next(error); }
return res.sendStatus(204);
});
},
running: false
run: (callback = (error) ->) ->
if MockDocUpdaterServer.running
return callback()
app = express()
app.get "/project/:project_id/doc/:doc_id", MockDocUpdaterServer.getDocumentRequest
app.delete "/project/:project_id", MockDocUpdaterServer.deleteProjectRequest
app.listen 3003, (error) ->
MockDocUpdaterServer.running = true
callback(error)
.on "error", (error) ->
console.error "error starting MockDocUpdaterServer:", error.message
process.exit(1)
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, function(error) {
MockDocUpdaterServer.running = true;
return callback(error);
}).on("error", function(error) {
console.error("error starting MockDocUpdaterServer:", error.message);
return process.exit(1);
});
}
});
sinon.spy MockDocUpdaterServer, "getDocument"
sinon.spy(MockDocUpdaterServer, "getDocument");

View File

@@ -1,46 +1,64 @@
sinon = require "sinon"
express = require "express"
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let MockWebServer;
const sinon = require("sinon");
const express = require("express");
module.exports = MockWebServer =
projects: {}
privileges: {}
module.exports = (MockWebServer = {
projects: {},
privileges: {},
createMockProject: (project_id, privileges, project) ->
MockWebServer.privileges[project_id] = privileges
MockWebServer.projects[project_id] = project
createMockProject(project_id, privileges, project) {
MockWebServer.privileges[project_id] = privileges;
return MockWebServer.projects[project_id] = project;
},
joinProject: (project_id, user_id, callback = (error, project, privilegeLevel) ->) ->
callback(
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) ->
{project_id} = req.params
{user_id} = req.query
if project_id == 'rate-limited'
res.status(429).send()
else
MockWebServer.joinProject project_id, user_id, (error, project, privilegeLevel) ->
return next(error) if error?
res.json {
project: project
privilegeLevel: privilegeLevel
}
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, function(error, project, privilegeLevel) {
if (error != null) { return next(error); }
return res.json({
project,
privilegeLevel
});
});
}
},
running: false
run: (callback = (error) ->) ->
if MockWebServer.running
return callback()
app = express()
app.post "/project/:project_id/join", MockWebServer.joinProjectRequest
app.listen 3000, (error) ->
MockWebServer.running = true
callback(error)
.on "error", (error) ->
console.error "error starting MockWebServer:", error.message
process.exit(1)
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, function(error) {
MockWebServer.running = true;
return callback(error);
}).on("error", function(error) {
console.error("error starting MockWebServer:", error.message);
return process.exit(1);
});
}
});
sinon.spy MockWebServer, "joinProject"
sinon.spy(MockWebServer, "joinProject");

View File

@@ -1,75 +1,94 @@
XMLHttpRequest = require("../../libs/XMLHttpRequest").XMLHttpRequest
io = require("socket.io-client")
async = require("async")
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let Client;
const {
XMLHttpRequest
} = require("../../libs/XMLHttpRequest");
const io = require("socket.io-client");
const async = require("async");
request = require "request"
Settings = require "settings-sharelatex"
redis = require "redis-sharelatex"
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);
uid = require('uid-safe').sync
signature = require("cookie-signature")
const uid = require('uid-safe').sync;
const signature = require("cookie-signature");
io.util.request = () ->
xhr = new XMLHttpRequest()
_open = xhr.open
xhr.open = () =>
_open.apply(xhr, arguments)
if Client.cookie?
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);
}
}.bind(this);
return xhr;
};
module.exports = Client =
cookie: null
module.exports = (Client = {
cookie: null,
setSession: (session, callback = (error) ->) ->
sessionId = uid(24)
session.cookie = {}
rclient.set "sess:" + sessionId, JSON.stringify(session), (error) ->
return callback(error) if error?
secret = Settings.security.sessionSecret
cookieKey = 's:' + signature.sign(sessionId, secret)
Client.cookie = "#{Settings.cookieName}=#{cookieKey}"
callback()
setSession(session, callback) {
if (callback == null) { callback = function(error) {}; }
const sessionId = uid(24);
session.cookie = {};
return rclient.set("sess:" + sessionId, JSON.stringify(session), function(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 = (error) ->) ->
Client.cookie = null
callback()
unsetSession(callback) {
if (callback == null) { callback = function(error) {}; }
Client.cookie = null;
return callback();
},
connect: (cookie) ->
client = io.connect("http://localhost:3026", 'force new connection': true)
client.on 'connectionAccepted', (_, publicId) ->
client.publicId = publicId
return client
connect(cookie) {
const client = io.connect("http://localhost:3026", {'force new connection': true});
client.on('connectionAccepted', (_, publicId) => client.publicId = publicId);
return client;
},
getConnectedClients: (callback = (error, clients) ->) ->
request.get {
url: "http://localhost:3026/clients"
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
}, (error, response, data) => callback(error, data));
},
getConnectedClient: (client_id, callback = (error, clients) ->) ->
request.get {
url: "http://localhost:3026/clients/#{client_id}"
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
}, (error, response, data) => callback(error, data));
},
disconnectClient: (client_id, callback) ->
request.post {
url: "http://localhost:3026/client/#{client_id}/disconnect"
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
}, (error, response, data) => callback(error, data));
return null;
},
disconnectAllClients: (callback) ->
Client.getConnectedClients (error, clients) ->
async.each clients, (clientView, cb) ->
Client.disconnectClient clientView.client_id, cb
, callback
disconnectAllClients(callback) {
return Client.getConnectedClients((error, clients) => async.each(clients, (clientView, cb) => Client.disconnectClient(clientView.client_id, cb)
, callback));
}
});

View File

@@ -1,23 +1,46 @@
app = require('../../../../app')
logger = require("logger-sharelatex")
Settings = require("settings-sharelatex")
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const app = require('../../../../app');
const logger = require("logger-sharelatex");
const Settings = require("settings-sharelatex");
module.exports =
running: false
initing: false
callbacks: []
ensureRunning: (callback = (error) ->) ->
if @running
return callback()
else if @initing
@callbacks.push callback
else
@initing = true
@callbacks.push callback
app.listen Settings.internal?.realtime?.port, "localhost", (error) =>
throw error if error?
@running = true
logger.log("clsi running in dev mode")
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");
for callback in @callbacks
callback()
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;
}