Merge pull request #3 from sharelatex/ja-track-changes

Clean up dead code and add acceptance tests
This commit is contained in:
James Allen 2016-12-15 13:58:17 +00:00 committed by GitHub
commit ce506f0675
22 changed files with 190 additions and 974 deletions

View file

@ -3,6 +3,7 @@
app.js
app/js/
test/unit/js/
test/acceptance/js/
public/build/
node_modules/

View file

@ -2,6 +2,11 @@ module.exports = (grunt) ->
# Project configuration.
grunt.initConfig
forever:
app:
options:
index: "app.js"
execute:
app:
src: "app.js"
@ -30,7 +35,7 @@ module.exports = (grunt) ->
dest: './',
ext: '.js'
server_tests:
unit_tests:
expand: true,
flatten: false,
cwd: 'test/unit/coffee',
@ -38,10 +43,18 @@ module.exports = (grunt) ->
dest: 'test/unit/js/',
ext: '.js'
acceptance_tests:
expand: true,
flatten: false,
cwd: 'test/acceptance/coffee',
src: ['**/*.coffee'],
dest: 'test/acceptance/js/',
ext: '.js'
watch:
server_coffee:
files: ['app/**/*.coffee', 'test/unit/**/*.coffee']
tasks: ['compile:server', 'compile:server_tests', 'mochaTest']
tasks: ['compile:server', 'compile:unit_tests', 'mochaTest']
client_coffee:
files: ['public/**/*.coffee']
@ -101,7 +114,12 @@ module.exports = (grunt) ->
options:
reporter: process.env.MOCHA_RUNNER || "spec"
grep: grunt.option("grep")
src: ['test/**/*.js']
src: ['test/unit/**/*.js']
acceptance:
options:
reporter: process.env.MOCHA_RUNNER || "spec"
grep: grunt.option("grep")
src: ['test/acceptance/**/*.js']
plato:
your_task:
@ -121,6 +139,7 @@ module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-plato'
grunt.loadNpmTasks 'grunt-execute'
grunt.loadNpmTasks 'grunt-bunyan'
grunt.loadNpmTasks 'grunt-forever'
grunt.registerTask 'compile', ['clean', 'copy', 'coffee', 'less', 'jade', 'requirejs']
@ -128,4 +147,5 @@ module.exports = (grunt) ->
grunt.registerTask 'default', ['compile', 'bunyan', 'execute']
grunt.registerTask 'compileAndCompress', ['compile', 'uglify']
grunt.registerTask 'test:unit', ['compile', 'mochaTest:unit']
grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance']

View file

@ -1,23 +0,0 @@
async = require "async"
logger = require "logger-sharelatex"
WebApiManager = require("../WebApi/WebApiManager")
UserFormatter = require("../Users/UserFormatter")
module.exports = AuthenticationController =
authClient: (client, data, callback = (error) ->) ->
logger.log auth_token: data.auth_token, "authenticating user"
WebApiManager.getUserDetailsFromAuthToken data.auth_token, (error, user) =>
if error?
logger.error data: data, client_id: client.id, err: error, "error authenticating user"
return callback("something went wrong")
logger.log user: user, auth_token: data.auth_token, "authenticated user"
user = UserFormatter.formatUserForClientSide user
jobs = []
for key, value of user
do (key, value) ->
jobs.push (callback) -> client.set key, value, callback
jobs.push (callback) -> client.set "auth_token", data.auth_token, callback
async.series jobs, (error, results) =>
callback(error, user)

View file

@ -1,24 +0,0 @@
WebApiManager = require "../WebApi/WebApiManager"
SocketManager = require "../Sockets/SocketManager"
module.exports = AuthorizationManager =
canClientJoinProjectRoom: (client, project_id, callback = (error, authorized) ->) ->
client.get "auth_token", (error, auth_token) ->
return callback(error) if error?
WebApiManager.getProjectCollaborators project_id, auth_token, (error, collaborators) ->
return callback(error) if error?
client.get "id", (error, user_id) ->
return callback(error) if error?
authorized = false
for collaborator in collaborators
if collaborator.id == user_id
authorized = true
break
callback null, authorized
canClientSendMessageToRoom: (client, room_id, callback = (error, authorized) ->) ->
SocketManager.isClientInRoom(client, room_id, callback)
canClientReadMessagesInRoom: (client, room_id, callback = (error, authorized) ->) ->
SocketManager.isClientInRoom(client, room_id, callback)

View file

@ -1,84 +0,0 @@
logger = require "logger-sharelatex"
metrics = require "metrics-sharelatex"
MessageManager = require "./MessageManager"
MessageFormatter = require "./MessageFormatter"
SocketManager = require "../Sockets/SocketManager"
AuthorizationManager = require "../Authorization/AuthorizationManager"
module.exports = MessageController =
DEFAULT_MESSAGE_LIMIT: 50
sendMessage: (client, data, callback = (error) ->) ->
content = data?.message?.content
room_id = data?.room?.id
return callback("malformed message") if not (content? and room_id?)
client.get "id", (error, user_id) ->
logger.log user_id: user_id, room_id: room_id, "sending message"
AuthorizationManager.canClientSendMessageToRoom client, room_id, (error, authorized) ->
if error?
logger.err err:error, user_id:user_id, "something went wrong checking if canClientSendMessageToRoom"
return callback("something went wrong")
if authorized
SocketManager.getClientAttributes client, ["id"], (error, values) ->
if error?
logger.err err:error, user_id:user_id, "something went wrong getClientAttributes"
return callback("something went wrong")
newMessageOpts =
content: content
room_id: room_id
user_id: values[0]
timestamp: Date.now()
MessageManager.createMessage newMessageOpts, (error, message) ->
if error?
logger.err err:error, user_id:user_id, "something went wrong createMessage"
return callback("something went wrong")
MessageManager.populateMessagesWithUsers [message], (error, messages) ->
if error?
logger.err err:error, user_id:user_id, "something went wrong populateMessagesWithUsers"
return callback("something went wrong")
message = MessageFormatter.formatMessageForClientSide(messages[0])
message.room =
id: room_id
SocketManager.emitToRoom data.room.id, "messageReceived", message:message
metrics.inc "editor.instant-message"
logger.log user_id: user_id, room_id: room_id, "sent message"
callback()
else
logger.log user_id: user_id, room_id: room_id, "unauthorized attempt to send message"
callback("unknown room")
getMessages: (client, data, callback = (error, messages) ->) ->
room_id = data?.room?.id
return callback("malformed message") if not room_id?
client.get "id", (error, user_id) ->
logger.log user_id: user_id, room_id: room_id, "getting messages"
AuthorizationManager.canClientReadMessagesInRoom client, room_id, (error, authorized) ->
if error?
logger.err err:error, user_id:user_id, "something went canClientReadMessagesInRoom"
return callback("something went wrong")
if authorized
query = room_id: room_id
if data.before?
query.timestamp = $lt: data.before
options =
order_by: "timestamp"
sort_order: -1
limit: data.limit || MessageController.DEFAULT_MESSAGE_LIMIT
MessageManager.getMessages query, options, (error, messages) ->
if error?
logger.err err:error, user_id:user_id, "something went getMessages"
return callback("something went wrong")
MessageManager.populateMessagesWithUsers messages, (error, messages) ->
if error?
logger.err err:error, user_id:user_id, "something went populateMessagesWithUsers"
return callback("something went wrong")
messages = MessageFormatter.formatMessagesForClientSide messages
logger.log user_id: user_id, room_id: room_id, "got messages"
callback null, messages
else
logger.log user_id: user_id, room_id: room_id, "unauthorized attempt to get messages"
callback("unknown room")

View file

@ -1,101 +0,0 @@
async = require "async"
logger = require "logger-sharelatex"
AuthorizationManager = require "../Authorization/AuthorizationManager"
RoomManager = require "../Rooms/RoomManager"
SocketManager = require "../Sockets/SocketManager"
module.exports = RoomController =
joinRoom: (client, data, callback = (error) ->) ->
if !data.room?.project_id?
return callback("unknown room")
project_id = data.room.project_id
client.get "id", (error, id) ->
logger.log user_id: id, project_id: project_id, "joining room"
AuthorizationManager.canClientJoinProjectRoom client, project_id, (error, authorized) ->
return callback("something went wrong") if error?
if authorized
RoomManager.findOrCreateRoom project_id: project_id, (error, room) ->
return callback("something went wrong") if error?
room_id = room._id.toString()
RoomController._addClientToRoom client, room_id, (error) ->
return callback("something went wrong") if error?
RoomController._getClientsInRoom room_id, (error, clients) ->
return callback("something went wrong") if error?
logger.log user_id: id, project_id: project_id, room_id: room_id, "joined room"
roomDetails =
room:
id: room_id
connectedUsers: clients
callback null, roomDetails
else
logger.log user_id: id, project_id: project_id, "unauthorized attempt to join room"
callback("unknown room")
leaveAllRooms: (client, callback = (error) ->) ->
client.get "id", (error, id) ->
logger.log user_id: id, "leaving all rooms"
SocketManager.getRoomIdsClientHasJoined client, (error, room_ids) ->
return callback("something went wrong") if error?
jobs = []
for room_id in room_ids
do (room_id) ->
jobs.push (callback) ->
RoomController.leaveRoom client, room_id, callback
async.series jobs, (error)-> callback(error)
leaveRoom: (client, room_id, callback = (error) ->) ->
client.get "id", (error, id) ->
logger.log user_id: id, room_id: room_id, "leaving room"
RoomController._getClientAttributes client, (error, attributes) ->
return callback("something went wrong") if error?
SocketManager.removeClientFromRoom client, room_id, (error) ->
return callback("something went wrong") if error?
leftRoomUpdate =
room:
id: room_id
user: attributes
SocketManager.emitToRoom room_id, "userLeft", leftRoomUpdate
logger.log user_id: id, room_id: room_id, "left room"
callback()
_addClientToRoom: (client, room_id, callback = (error) ->) ->
RoomController._getClientAttributes client, (error, attributes) ->
return callback(error) if error?
update =
room:
id: room_id
user: attributes
SocketManager.emitToRoom room_id, "userJoined", update
SocketManager.addClientToRoom client, room_id, callback
_getClientsInRoom: (room_id, callback = (error, clients) ->) ->
SocketManager.getClientsInRoom room_id, (error, clients) ->
return callback(error) if error?
formattedClients = []
jobs = []
for client in clients
do (client) ->
jobs.push (callback) ->
RoomController._getClientAttributes client, (error, attributes) ->
return callback(error) if error?
formattedClients.push attributes
callback()
async.series jobs, (error) ->
return callback(error) if error?
callback null, formattedClients
_getClientAttributes: (client, callback = (error, attributes) ->) ->
SocketManager.getClientAttributes client, ["id", "first_name", "last_name", "email", "gravatar_url"], (error, attributes) ->
return callback(error) if error?
[id, first_name, last_name, email, gravatar_url] = attributes
clientAttributes =
id : id
first_name : first_name
last_name : last_name
email : email
gravatar_url : gravatar_url
callback null, clientAttributes

View file

@ -1,25 +0,0 @@
settings = require 'settings-sharelatex'
rclientPub = require("redis").createClient(settings.redis.web.port, settings.redis.web.host)
rclientPub.auth(settings.redis.web.password)
rclientSub = require("redis").createClient(settings.redis.web.port, settings.redis.web.host)
rclientSub.auth(settings.redis.web.password)
module.exports = RealTimeEventManager =
rclientPub:rclientPub
rclientSub:rclientSub
emitToRoom: (room_id, message, payload...) ->
RealTimeEventManager.rclientPub.publish "chat-events", JSON.stringify
room_id: room_id
message: message
payload: payload
listenForChatEvents: () ->
@rclientSub.subscribe "chat-events"
@rclientSub.on "message", @_processEditorEvent.bind(@)
_processEditorEvent: (channel, message) ->
io = require('../../server').io
message = JSON.parse(message)
io.sockets.in(message.room_id).emit(message.message, message.payload...)

View file

@ -1,40 +0,0 @@
async = require "async"
RealTimeEventManager = require("./RealTimeEventManager")
module.exports = SocketManager =
addClientToRoom: (client, room_id, callback = (error) ->) ->
client.join(room_id)
callback()
removeClientFromRoom: (client, room_id, callback = (error) ->) ->
client.leave(room_id)
callback()
getClientAttributes: (client, attributes, callback = (error, values) ->) ->
jobs = []
for attribute in attributes
do (attribute) ->
jobs.push (cb) -> client.get attribute, cb
async.series jobs, callback
emitToRoom: RealTimeEventManager.emitToRoom
isClientInRoom: (targetClient, room_id, callback = (error, inRoom) ->) ->
io = require("../../server").io
for client in io.sockets.clients(room_id)
if client.id == targetClient.id
return callback null, true
callback null, false
getClientsInRoom: (room_id, callback = (error, clients) ->) ->
io = require("../../server").io
callback null, io.sockets.clients(room_id)
getRoomIdsClientHasJoined: (client, callback = (error, room_ids) ->) ->
io = require("../../server").io
room_ids = []
for room_id, value of io.sockets.manager.roomClients[client.id]
if room_id[0] == "/"
room_ids.push room_id.slice(1)
callback null, room_ids

View file

@ -2,6 +2,7 @@ crypto = require "crypto"
module.exports = UserFormatter =
formatUserForClientSide: (user) ->
return null if !user?
if user._id?
user.id = user._id.toString()
delete user._id

View file

@ -16,14 +16,16 @@ module.exports = WebApiManager =
options.method = method
request options, (error, response, body) ->
return callback(error) if error?
try
result = JSON.parse(body)
catch e
return callback(e)
return callback null, result
getUserDetailsFromAuthToken: (auth_token, callback = (error, details) ->) ->
@apiRequest "/user/personal_info?auth_token=#{auth_token}", "get", callback
if 200 <= response.statusCode < 300
try
result = JSON.parse(body)
catch e
return callback(e)
return callback null, result
else
error = new Error("web api returned non-success code: #{response.statusCode}")
error.statusCode = response.statusCode
return callback error
getUserDetails: (user_id, callback = (error, details) ->) ->
@apiRequest "/user/#{user_id}/personal_info", "get", {
@ -31,7 +33,11 @@ module.exports = WebApiManager =
user: Settings.apis.web.user
pass: Settings.apis.web.pass
sendImmediately: true
}, callback
getProjectCollaborators: (project_id, auth_token, callback = (error, collaborators) ->) ->
@apiRequest "/project/#{project_id}/collaborators?auth_token=#{auth_token}", "get", callback
}, (error, data) ->
if error?
if error.statusCode == 404
return callback null, null
else
return callback error
else
return callback null, data

View file

@ -1,31 +1,12 @@
AuthenticationController = require("./Features/Authentication/AuthenticationController")
MessageController = require("./Features/Messages/MessageController")
RoomController = require("./Features/Rooms/RoomController")
MessageHttpController = require('./Features/Messages/MessageHttpController')
module.exports = Router =
route: (app, io) ->
route: (app) ->
app.get "/room/:project_id/messages", MessageHttpController.getMessages
app.post "/room/:project_id/messages", MessageHttpController.sendMessage
app.get "/status", (req, res, next) ->
res.send("chat is alive")
io.sockets.on "connection", (client) ->
client.on "disconnect", () ->
RoomController.leaveAllRooms(client)
client.on "auth", (data, callback = (error) ->) ->
AuthenticationController.authClient(client, data, callback)
client.on "joinRoom", (data, callback = (error) ->) ->
RoomController.joinRoom(client, data, callback)
client.on "sendMessage", (data, callback = (error) ->) ->
MessageController.sendMessage(client, data, callback)
client.on "getMessages", (data, callback = (error) ->) ->
MessageController.getMessages(client, data, callback)

View file

@ -6,16 +6,13 @@ Path = require("path")
express = require("express")
app = express()
server = require("http").createServer(app)
io = require("socket.io").listen(server)
io.set("resource", "/chat/socket.io")
io.set("log level", 1)
Router = require "./router"
metrics.mongodb.monitor(Path.resolve(__dirname + "/../../node_modules/mongojs/node_modules/mongodb"), logger)
app.use express.bodyParser()
app.use metrics.http.monitor(logger)
Router.route(app, io)
Router.route(app)
if (app.get 'env') == 'development'
console.log "Development Enviroment"
@ -39,13 +36,9 @@ app.use (req, res, next) ->
app.use(express.static(__dirname + "/../../public/build"))
module.exports = {
server: server
io: io
app: app
}
require("./Features/Sockets/RealTimeEventManager").listenForChatEvents()

View file

@ -8,15 +8,14 @@
},
"dependencies": {
"async": "0.2.9",
"coffee-script": "~1.7.1",
"express": "3.3.1",
"request": "2.21.0",
"socket.io": "0.9.14",
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0",
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.0.0",
"mongojs": "0.18.2",
"redis": "~0.10.1",
"coffee-script": "~1.7.1"
"request": "^2.79.0",
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0"
},
"devDependencies": {
"bunyan": "^1.0.0",
@ -32,12 +31,13 @@
"grunt-contrib-requirejs": "~0.4.1",
"grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-watch": "~0.5.3",
"grunt-requirejs": "~0.4.0",
"grunt-mocha-test": "~0.8.0",
"grunt-execute": "^0.2.2",
"grunt-forever": "^0.4.7",
"grunt-mocha-test": "~0.8.0",
"grunt-nodemon": "~0.1.2",
"grunt-notify": "~0.2.16",
"grunt-plato": "~0.2.1",
"grunt-requirejs": "~0.4.0",
"sandboxed-module": "",
"sinon": "",
"timekeeper": ""

View file

@ -0,0 +1,73 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
async = require "async"
crypto = require "crypto"
MockWebApi = require "./helpers/MockWebApi"
ChatClient = require "./helpers/ChatClient"
describe "Getting messages", ->
before ->
@user_id1 = ObjectId().toString()
@user_id2 = ObjectId().toString()
@content1 = "foo bar"
@content2 = "hello world"
MockWebApi.addUser @user_id1, @user1 = {
id: @user_id1
first_name: "Jane"
last_name: "Smith"
email: "jane@example.com"
}
MockWebApi.addUser @user_id2, @user2 = {
id: @user_id2
first_name: "John"
last_name: "Doe"
email: "john@example.com"
}
describe "normally", ->
before (done) ->
@project_id = ObjectId().toString()
async.series [
(cb) => ChatClient.sendMessage @project_id, @user_id1, @content1, cb
(cb) => ChatClient.sendMessage @project_id, @user_id2, @content2, cb
], done
it "should contain the messages and populated users when getting the messages", (done) ->
ChatClient.getMessages @project_id, (error, response, messages) =>
expect(messages.length).to.equal 2
messages.reverse()
expect(messages[0].content).to.equal @content1
expect(messages[0].user).to.deep.equal {
id: @user_id1
first_name: "Jane"
last_name: "Smith"
email: "jane@example.com"
gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("jane@example.com").digest("hex")}"
}
expect(messages[1].content).to.equal @content2
expect(messages[1].user).to.deep.equal {
id: @user_id2
first_name: "John"
last_name: "Doe"
email: "john@example.com"
gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("john@example.com").digest("hex")}"
}
done()
describe "when a user doesn't exit", ->
before (done) ->
@project_id = ObjectId().toString()
@user_id3 = ObjectId().toString()
async.series [
(cb) => ChatClient.sendMessage @project_id, @user_id3, @content1, cb
(cb) => ChatClient.sendMessage @project_id, @user_id2, @content2, cb
], done
it "should just return null for the user", (done) ->
ChatClient.getMessages @project_id, (error, response, messages) =>
expect(messages.length).to.equal 2
messages.reverse()
expect(messages[0].content).to.equal @content1
expect(messages[0].user).to.equal null
done()

View file

@ -0,0 +1,23 @@
{ObjectId} = require "../../../app/js/mongojs"
expect = require("chai").expect
MockWebApi = require "./helpers/MockWebApi"
ChatClient = require "./helpers/ChatClient"
describe "Sending a message", ->
before (done) ->
@project_id = ObjectId().toString()
@user_id = ObjectId().toString()
@content = "foo bar"
ChatClient.sendMessage @project_id, @user_id, @content, (error, response, body) ->
expect(error).to.be.null
expect(response.statusCode).to.equal 201
done()
it "should then list the message in project messages", (done) ->
ChatClient.getMessages @project_id, (error, response, messages) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 200
expect(messages.length).to.equal 1
expect(messages[0].content).to.equal @content
done()

View file

@ -0,0 +1,16 @@
request = require("request").defaults({baseUrl: "http://localhost:3010"})
module.exports =
sendMessage: (project_id, user_id, content, callback) ->
request.post {
url: "/room/#{project_id}/messages"
json:
user_id: user_id
content: content
}, callback
getMessages: (project_id, callback) ->
request.get {
url: "/room/#{project_id}/messages",
json: true
}, callback

View file

@ -0,0 +1,27 @@
express = require("express")
app = express()
module.exports = MockWebApi =
users: {}
addUser: (user_id, user) ->
@users[user_id] = user
getUser: (user_id, callback = (error, user) ->) ->
return callback null, @users[user_id]
run: () ->
app.get "/user/:user_id/personal_info", (req, res, next) =>
@getUser req.params.user_id, (error, user) ->
if error?
res.send 500
else if user?
res.send JSON.stringify user
else
res.send 404
app.listen 3000, (error) ->
throw error if error?
MockWebApi.run()

View file

@ -1,71 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Authentication/AuthenticationController.js"
SandboxedModule = require('sandboxed-module')
describe "AuthenticationController", ->
beforeEach ->
@AuthenticationController = SandboxedModule.require modulePath, requires:
"../WebApi/WebApiManager": @WebApiManager = {}
"../Users/UserFormatter": @UserFormatter = {}
"logger-sharelatex": @logger = { log: sinon.stub() }
@callback = sinon.stub()
describe "authClient", ->
beforeEach ->
@auth_token = "super-secret-auth-token"
@client =
params: {}
set: (key, value, callback) ->
@params[key] = value
callback()
@user =
id: "user-id-123"
email: "doug@sharelatex.com"
first_name: "Douglas"
last_name: "Adams"
@WebApiManager.getUserDetailsFromAuthToken = sinon.stub().callsArgWith(1, null, @user)
@UserFormatter.formatUserForClientSide = sinon.stub().returns({
id: @user.id
first_name: @user.first_name
last_name: @user.last_name
email: @user.email
gravatar_url: "//gravatar/url"
})
@AuthenticationController.authClient(@client, auth_token: @auth_token, @callback)
it "should get the user's data from the web api", ->
@WebApiManager.getUserDetailsFromAuthToken
.calledWith(@auth_token)
.should.equal true
it "should set the user's data and auth_token on the client object", ->
@client.params.should.deep.equal {
id: @user.id
first_name: @user.first_name
last_name: @user.last_name
email: @user.email
gravatar_url: "//gravatar/url"
auth_token: @auth_token
}
it "should call the callback with the user details (including the gravatar URL, but not the auth_token)", ->
@callback
.calledWith(null, {
id: @user.id
email: @user.email
first_name: @user.first_name
last_name: @user.last_name
gravatar_url: "//gravatar/url"
}).should.equal true
it "should log the request", ->
@logger.log
.calledWith(auth_token: @auth_token, "authenticating user")
.should.equal true
@logger.log
.calledWith(user: @user, auth_token: @auth_token, "authenticated user")
.should.equal true

View file

@ -1,55 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Authorization/AuthorizationManager.js"
SandboxedModule = require('sandboxed-module')
events = require "events"
describe "AuthorizationManager", ->
beforeEach ->
@SocketManager = {}
@AuthorizationManager = SandboxedModule.require modulePath, requires:
"../WebApi/WebApiManager": @WebApiManager = {}
"../Sockets/SocketManager": @SocketManager
@callback = sinon.stub()
@user_id = "user-id-123"
@project_id = "project-id-456"
@auth_token = "auth-token-789"
@client =
params: {}
get: (key, callback = (error, value) ->) ->
callback null, @params[key]
describe "canClientJoinProjectRoom", ->
beforeEach ->
@client.params.auth_token = @auth_token
@client.params.id = @user_id
describe "when the client is a collaborator", ->
beforeEach ->
@collaborators = [
id: @user_id
]
@WebApiManager.getProjectCollaborators = sinon.stub().callsArgWith(2, null, @collaborators)
@AuthorizationManager.canClientJoinProjectRoom(@client, @project_id, @callback)
it "should get the list of collaborators from the web api", ->
@WebApiManager.getProjectCollaborators
.calledWith(@project_id, @auth_token)
.should.equal true
it "should return true", ->
@callback.calledWith(null, true).should.equal true
describe "when the client is not a collaborator", ->
beforeEach ->
@collaborators = [
id: "not the user id"
]
@WebApiManager.getProjectCollaborators = sinon.stub().callsArgWith(2, null, @collaborators)
@AuthorizationManager.canClientJoinProjectRoom(@client, @project_id, @callback)
it "should return false", ->
@callback.calledWith(null, false).should.equal true

View file

@ -1,187 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Messages/MessageController.js"
SandboxedModule = require('sandboxed-module')
events = require "events"
tk = require "timekeeper"
ObjectId = require("mongojs").ObjectId
describe "MessageController", ->
beforeEach ->
tk.freeze(new Date())
@MessageController = SandboxedModule.require modulePath, requires:
"./MessageManager": @MessageManager = {}
"./MessageFormatter": @MessageFormatter = {}
"../Sockets/SocketManager": @SocketManager = {}
"../Authorization/AuthorizationManager": @AuthorizationManager = {}
"logger-sharelatex": @logger = { log: sinon.stub() }
"metrics-sharelatex": @metrics = {inc: sinon.stub()}
@callback = sinon.stub()
@client =
params:
id: @user_id = "user-id-123"
first_name: @first_name = "Douglas"
last_name: @last_name = "Adams"
email: @email = "doug@sharelatex.com"
gravatar_url: @gravatar_url = "//gravatar/url"
get: (key, callback = (error, value) ->) -> callback null, @params[key]
afterEach ->
tk.reset()
describe "sendMessage", ->
beforeEach ->
@MessageManager.createMessage = sinon.stub().callsArg(1)
@SocketManager.emitToRoom = sinon.stub()
@singlePopulatedMessage = {data:"here"}
@formattedMessage = {formatted:true}
@MessageFormatter.formatMessageForClientSide = sinon.stub().returns(@formattedMessage)
@MessageManager.populateMessagesWithUsers = sinon.stub().callsArgWith(1, null, [@singlePopulatedMessage])
@SocketManager.getClientAttributes = (client, attributes, callback) ->
values = (client.params[key] for key in attributes)
callback null, values
describe "when the client is authorized to send a message to the room", ->
beforeEach ->
@AuthorizationManager.canClientSendMessageToRoom = sinon.stub().callsArgWith(2, null, true)
@MessageController.sendMessage(@client, {
message:
content: @content = "Hello world"
room:
id: @room_id = "room-id-123"
}, @callback)
it "should check that the client can send a message to the room", ->
@AuthorizationManager.canClientSendMessageToRoom
.calledWith(@client, @room_id)
.should.equal true
it "should insert the message into the database", ->
@MessageManager.createMessage
.calledWith({
content: @content
user_id: @user_id
room_id: @room_id
timestamp: Date.now()
})
.should.equal true
it "should format the message for the client", ->
@MessageFormatter.formatMessageForClientSide.calledWith(@singlePopulatedMessage).should.equal true
it "should send the formatted message out to the other clients in the room", ->
@SocketManager.emitToRoom.calledWith(@room_id, "messageReceived", message:@formattedMessage).should.equal true
it "should record the message as a metric", ->
@metrics.inc
.calledWith("editor.instant-message")
.should.equal true
it "should call the callback", ->
@callback.called.should.equal true
describe "when the client is not authorized", ->
beforeEach ->
@AuthorizationManager.canClientSendMessageToRoom = sinon.stub().callsArgWith(2, null, false)
@MessageController.sendMessage(@client, {
message:
content: @content = "Hello world"
room:
id: @room_id = "room-id-123"
}, @callback)
it "should not insert the message into the database", ->
@MessageManager.createMessage.called.should.equal false
it "should not send the message out to the other clients in the room", ->
@SocketManager.emitToRoom.called.should.equal false
it "should call the callback with an error that doesn't give anything away", ->
@callback.calledWith("unknown room").should.equal true
describe "getMessage", ->
beforeEach ->
@room_id = "room-id-123"
@timestamp = Date.now()
@limit = 42
describe "when the client is authorized", ->
beforeEach ->
@messages = "messages without users stub"
@messagesWithUsers = "messages with users stub"
@formattedMessages = "formatted messages stub"
@MessageManager.getMessages = sinon.stub().callsArgWith(2, null, @messages)
@MessageManager.populateMessagesWithUsers = sinon.stub().callsArgWith(1, null, @messagesWithUsers)
@AuthorizationManager.canClientReadMessagesInRoom = sinon.stub().callsArgWith(2, null, true)
@MessageFormatter.formatMessagesForClientSide = sinon.stub().returns @formattedMessages
describe "with a timestamp and limit", ->
beforeEach ->
@MessageController.getMessages(@client, {
room:
id: @room_id,
before: @timestamp,
limit: @limit
}, @callback)
it "should get the requested messages", ->
@MessageManager.getMessages
.calledWith({
timestamp: $lt: @timestamp
room_id: @room_id
}, {
limit: @limit
order_by: "timestamp"
sort_order: -1
})
.should.equal true
it "should populate the messages with the users", ->
@MessageManager.populateMessagesWithUsers
.calledWith(@messages)
.should.equal true
it "should return the formatted messages", ->
@MessageFormatter.formatMessagesForClientSide
.calledWith(@messagesWithUsers)
.should.equal true
it "should call the callback with the formatted messages", ->
@callback
.calledWith(null, @formattedMessages)
.should.equal true
describe "without a timestamp or limit", ->
beforeEach ->
@MessageController.getMessages(@client, {
room:
id: @room_id,
}, @callback)
it "should get a default number of messages from the beginning", ->
@MessageManager.getMessages
.calledWith({
room_id: @room_id
}, {
limit: @MessageController.DEFAULT_MESSAGE_LIMIT
order_by: "timestamp"
sort_order: -1
})
.should.equal true
describe "when the client is not authorized", ->
beforeEach ->
@AuthorizationManager.canClientReadMessagesInRoom = sinon.stub().callsArgWith(2, null, false)
@MessageController.getMessages(@client, {
room:
id: @room_id,
before: @timestamp,
limit: @limit
}, @callback)
it "should call the callback with an error", ->
@callback.calledWith("unknown room").should.equal true

View file

@ -1,240 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Rooms/RoomController.js"
SandboxedModule = require('sandboxed-module')
events = require "events"
ObjectId = require("mongojs").ObjectId
class MockClient
params: {}
get: (key, callback = (error, value) ->) ->
callback null, @params[key]
describe "RoomController", ->
beforeEach ->
@SocketManager =
getClientAttributes: sinon.stub()
@RoomController = SandboxedModule.require modulePath, requires:
"../Authorization/AuthorizationManager": @AuthorizationManager = {}
"../Sockets/SocketManager": @SocketManager
"../Rooms/RoomManager": @RoomManager = {}
"logger-sharelatex": @logger = { log: sinon.stub() }
@project_id = ObjectId().toString()
@room_id = ObjectId().toString()
@room =
_id: ObjectId(@room_id)
project_id: ObjectId(@project_id)
@callback = sinon.stub()
@client =
params: {}
get: (key, callback = (error, value) ->) -> callback null, @params[key]
describe "joinRoom", ->
describe "when the client is authorized", ->
beforeEach ->
@AuthorizationManager.canClientJoinProjectRoom = sinon.stub().callsArgWith(2, null, true)
@RoomManager.findOrCreateRoom = sinon.stub().callsArgWith(1, null, @room)
@RoomController._addClientToRoom = sinon.stub().callsArg(2)
@RoomController._getClientsInRoom = sinon.stub().callsArgWith(1, null, @clients = ["client1", "client2"])
@RoomController.joinRoom @client, { room: project_id: @project_id }, @callback
it "should check that the client can join the room", ->
@AuthorizationManager.canClientJoinProjectRoom
.calledWith(@client, @project_id)
.should.equal true
it "should ensure that the room exists", ->
@RoomManager.findOrCreateRoom
.calledWith({ project_id: @project_id })
.should.equal true
it "should put the client into the room", ->
@RoomController._addClientToRoom
.calledWith(@client, @room_id)
.should.equal true
it "should get the clients already in the room", ->
@RoomController._getClientsInRoom
.calledWith(@room_id)
.should.equal true
it "should call the callback with the room id", ->
@callback.calledWith(null, {
room:
id: @room_id
connectedUsers: @clients
}).should.equal true
describe "when the client is not authorized", ->
beforeEach ->
@AuthorizationManager.canClientJoinProjectRoom = sinon.stub().callsArgWith(2, null, false)
@RoomController._addClientToRoom = sinon.stub().callsArg(2)
@RoomController.joinRoom @client, { room: project_id: @project_id }, @callback
it "should not put the client into the room", ->
@RoomController._addClientToRoom.called.should.equal false
it "should call the callback with an error that gives nothing away", ->
@callback.calledWith("unknown room").should.equal true
describe "leaveAllRooms", ->
beforeEach ->
@client = new MockClient()
@client.params =
id: "client-1-id"
first_name: "Douglas"
last_name: "Adams"
email: "doug@sharelatex.com"
gravatar_url: "//gravatar/url/1"
@room_ids = ["room-id-1", "room-id-2"]
@SocketManager.getRoomIdsClientHasJoined = sinon.stub().callsArgWith(1, null, @room_ids)
@RoomController.leaveRoom = sinon.stub().callsArg(2)
@RoomController.leaveAllRooms @client, @callback
it "should get the rooms the client has joined", ->
@SocketManager.getRoomIdsClientHasJoined
.calledWith(@client)
.should.equal true
it "should leave each room", ->
for room_id in @room_ids
@RoomController.leaveRoom
.calledWith(@client, room_id)
.should.equal true
it "should call the callback", ->
@callback.called.should.equal true
describe "leaveRoom", ->
beforeEach ->
@client = new MockClient()
@client.params =
id: "client-1-id"
first_name: "Douglas"
last_name: "Adams"
email: "doug@sharelatex.com"
gravatar_url: "//gravatar/url/1"
@RoomController._getClientAttributes = sinon.stub().callsArgWith(1, null, @client.params)
@SocketManager.removeClientFromRoom = sinon.stub().callsArg(2)
@SocketManager.emitToRoom = sinon.stub()
@RoomController.leaveRoom @client, @room_id, @callback
it "should leave the room", ->
@SocketManager.removeClientFromRoom
.calledWith(@client, @room_id)
.should.equal true
it "should tell the other clients in the room that we have left", ->
@SocketManager.emitToRoom
.calledWith(@room_id, "userLeft", {
room:
id: @room_id
user:
id : @client.params["id"]
first_name : @client.params["first_name"]
last_name : @client.params["last_name"]
email : @client.params["email"]
gravatar_url : @client.params["gravatar_url"]
})
.should.equal true
it "should call the callback", ->
@callback.called.should.equal true
describe "_getClientsInRoom", ->
beforeEach ->
@client1 = new MockClient()
@client1.params =
id: "client-1-id"
first_name: "Douglas"
last_name: "Adams"
email: "doug@sharelatex.com"
gravatar_url: "//gravatar/url/1"
@client2 = new MockClient()
@client2.params =
id: "client-2-id"
first_name: "James"
last_name: "Allen"
email: "james@sharelatex.com"
gravatar_url: "//gravatar/url/2"
@clients = [ @client1, @client2 ]
callCount = 0
@RoomController._getClientAttributes = (ignore, cb)=>
if callCount == 0
callCount++
cb(null, @client1.params)
else
cb null, @client2.params
@SocketManager.getClientsInRoom = sinon.stub().callsArgWith(1, null, @clients)
@RoomController._getClientsInRoom(@room_id, @callback)
it "should get the socket.io clients in the room", ->
@SocketManager.getClientsInRoom
.calledWith(@room_id)
.should.equal true
it "should return a formatted array of clients", ->
@callback
.calledWith(null, [{
id : @client1.params["id"]
first_name : @client1.params["first_name"]
last_name : @client1.params["last_name"]
email : @client1.params["email"]
gravatar_url : @client1.params["gravatar_url"]
}, {
id : @client2.params["id"]
first_name : @client2.params["first_name"]
last_name : @client2.params["last_name"]
email : @client2.params["email"]
gravatar_url : @client2.params["gravatar_url"]
}])
.should.equal true
describe "_addClientToRoom", ->
beforeEach ->
@client = new MockClient()
@client.params =
id: "client-1-id"
first_name: "Douglas"
last_name: "Adams"
email: "doug@sharelatex.com"
gravatar_url: "//gravatar/url/1"
@RoomController._getClientAttributes = sinon.stub().callsArgWith(1, null, @client.params)
@SocketManager.addClientToRoom = sinon.stub().callsArg(2)
@SocketManager.emitToRoom = sinon.stub()
@RoomController._addClientToRoom(@client, @room_id, @callback)
it "should add the client to the room", ->
@SocketManager.addClientToRoom
.calledWith(@client, @room_id)
.should.equal true
it "should tell the room that the client has been added", ->
@SocketManager.emitToRoom
.calledWith(@room_id, "userJoined", {
room:
id: @room_id
user:
id : @client.params["id"]
first_name : @client.params["first_name"]
last_name : @client.params["last_name"]
email : @client.params["email"]
gravatar_url : @client.params["gravatar_url"]
})
.should.equal true
it "should call the callback", ->
@callback.called.should.equal true

View file

@ -1,75 +0,0 @@
SandboxedModule = require('sandboxed-module')
sinon = require('sinon')
require('chai').should()
modulePath = "../../../../app/js/Features/Sockets/RealTimeEventManager.js"
describe "RealTimeEventManager", ->
beforeEach ->
@settings =
redis:
web:
host: "host here"
port: "port here"
password: "password here"
@RealTimeEventManager = SandboxedModule.require modulePath, requires:
"redis":
createClient: () ->
auth:->
"../../server" : io: @io = {}
"settings-sharelatex":@settings
@RealTimeEventManager.rclientPub = publish: sinon.stub()
@RealTimeEventManager.rclientSub =
subscribe: sinon.stub()
on: sinon.stub()
@room_id = "room-id-here"
@message = "message-to-chat-here"
@payload = ["argument one", 42]
describe "emitToRoom", ->
beforeEach ->
@RealTimeEventManager.emitToRoom(@room_id, @message, @payload...)
it "should publish the message to redis", ->
@RealTimeEventManager.rclientPub.publish
.calledWith("chat-events", JSON.stringify(
room_id: @room_id,
message: @message
payload: @payload
))
.should.equal true
describe "listenForChatEvents", ->
beforeEach ->
@RealTimeEventManager._processEditorEvent = sinon.stub()
@RealTimeEventManager.listenForChatEvents()
it "should subscribe to the chat-events channel", ->
@RealTimeEventManager.rclientSub.subscribe
.calledWith("chat-events")
.should.equal true
it "should process the events with _processEditorEvent", ->
@RealTimeEventManager.rclientSub.on
.calledWith("message", sinon.match.func)
.should.equal true
describe "_processEditorEvent", ->
describe "with a designated room", ->
beforeEach ->
@io.sockets =
in: sinon.stub().returns(emit: @emit = sinon.stub())
data = JSON.stringify
room_id: @room_id
message: @message
payload: @payload
@RealTimeEventManager._processEditorEvent("chat-events", data)
it "should send the message to all clients in the room", ->
@io.sockets.in
.calledWith(@room_id)
.should.equal true
@emit.calledWith(@message, @payload...).should.equal true