Add in support for threads for comments

This commit is contained in:
James Allen 2016-12-16 16:52:50 +00:00
parent 2a1e82ce46
commit cb88e1e41b
17 changed files with 296 additions and 475 deletions

View file

@ -12,14 +12,6 @@ module.exports = (grunt) ->
src: "app.js"
coffee:
client:
expand: true,
flatten: false,
cwd: 'public/coffee',
src: ['**/*.coffee'],
dest: 'public/build/',
ext: '.js'
server:
expand: true,
flatten: false,
@ -56,47 +48,7 @@ module.exports = (grunt) ->
files: ['app/**/*.coffee', 'test/unit/**/*.coffee']
tasks: ['compile:server', 'compile:unit_tests', 'mochaTest']
client_coffee:
files: ['public/**/*.coffee']
tasks: ['compile']
less:
files: ['public/less/*.less']
tasks: ['compile']
jade:
files: ['public/jade/*.jade']
tasks: ['compile']
less:
production:
files:
"public/build/css/chat.css": "public/less/chat.less"
jade:
compile:
files:
"public/build/html/templates.html": ["public/jade/templates.jade"]
requirejs:
compile:
options:
mainConfigFile: 'public/app.build.js',
uglify:
my_target:
files:
'public/build/chat.js': ['public/build/chat.js']
copy:
main:
expand: true
cwd: 'public/js'
src: '**'
dest: 'public/build/'
clean: ["public/build", "app/js", "test/unit/js"]
clean: ["app/js", "test/unit/js"]
nodemon:
dev:
@ -127,11 +79,6 @@ module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-contrib-coffee'
grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-contrib-copy'
grunt.loadNpmTasks 'grunt-contrib-less'
grunt.loadNpmTasks 'grunt-contrib-jade'
grunt.loadNpmTasks 'grunt-contrib-requirejs'
grunt.loadNpmTasks 'grunt-contrib-uglify'
grunt.loadNpmTasks 'grunt-nodemon'
grunt.loadNpmTasks 'grunt-contrib-clean'
grunt.loadNpmTasks 'grunt-concurrent'
@ -142,10 +89,9 @@ module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-forever'
grunt.registerTask 'compile', ['clean', 'copy', 'coffee', 'less', 'jade', 'requirejs']
grunt.registerTask 'compile', ['clean', 'coffee']
grunt.registerTask 'install', ['compile']
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

@ -14,3 +14,19 @@ module.exports = MessageFormatter =
formatMessagesForClientSide: (messages) ->
(@formatMessageForClientSide(message) for message in messages)
groupMessagesByThreads: (rooms, messages) ->
room_id_to_thread_id = {}
for room in rooms
room_id_to_thread_id[room._id.toString()] = room.thread_id.toString()
threads = {}
for message in messages
thread_id = room_id_to_thread_id[message.room_id.toString()]
threads[thread_id] ?= []
threads[thread_id].push MessageFormatter.formatMessageForClientSide(message)
for thread_id, messages of threads
messages.sort (a,b) -> a.timestamp - b.timestamp
return threads

View file

@ -2,61 +2,70 @@ logger = require "logger-sharelatex"
metrics = require "metrics-sharelatex"
MessageManager = require "./MessageManager"
MessageFormatter = require "./MessageFormatter"
RoomManager = require "../Rooms/RoomManager"
ThreadManager = require "../Threads/ThreadManager"
{ObjectId} = require "../../mongojs"
module.exports = MessageHttpController =
DEFAULT_MESSAGE_LIMIT: 50
getGlobalMessages: (req, res, next) ->
MessageHttpController._getMessages(ThreadManager.GLOBAL_THREAD, req, res, next)
sendMessage: (req, res, next) ->
sendGlobalMessage: (req, res, next) ->
MessageHttpController._sendMessage(ThreadManager.GLOBAL_THREAD, req, res, next)
sendThreadMessage: (req, res, next) ->
MessageHttpController._sendMessage(req.params.thread_id, req, res, next)
getAllThreads: (req, res, next) ->
{project_id} = req.params
logger.log {project_id}, "getting all threads"
ThreadManager.findAllThreadRooms project_id, (error, rooms) ->
return next(error) if error?
room_ids = rooms.map (r) -> r._id
MessageManager.findAllMessagesInRooms room_ids, (error, messages) ->
return next(error) if error?
MessageManager.populateMessagesWithUsers messages, (error, messages) ->
return next(error) if error?
threads = MessageFormatter.groupMessagesByThreads rooms, messages
res.json threads
_sendMessage: (client_thread_id, req, res, next) ->
{user_id, content} = req?.body
{project_id} = req.params
logger.log user_id: user_id, content: content, "new message recived"
RoomManager.findOrCreateRoom project_id: project_id, (error, room) ->
if !ObjectId.isValid(user_id)
return res.send(400, "Invalid user_id")
logger.log {client_thread_id, project_id, user_id, content}, "new message received"
ThreadManager.findOrCreateThread project_id, client_thread_id, (error, thread) ->
return next(error) if error?
newMessageOpts =
content: content
room_id: room._id
user_id: user_id
timestamp: Date.now()
MessageManager.createMessage newMessageOpts, (error, message) ->
if err?
logger.err err:error, user_id:user_id, "something went wrong with create message"
return next(err)
MessageManager.createMessage thread._id, user_id, content, Date.now(), (error, message) ->
return next(error) if error?
MessageManager.populateMessagesWithUsers [message], (error, messages) ->
if error?
logger.err err:error, user_id:user_id, "something went wrong populateMessagesWithUsers"
return next("something went wrong")
return next(error) if error?
message = MessageFormatter.formatMessageForClientSide(messages[0])
message.room =
id: project_id
res.send(201, message)
getMessages: (req, res, next) ->
_getMessages: (client_thread_id, req, res, next) ->
{project_id} = req.params
query = {}
if req.query?.before?
query.timestamp = $lt: parseInt(req.query.before, 10)
before = parseInt(req.query.before, 10)
else
before = null
if req.query?.limit?
limit = parseInt(req.query.limit, 10)
else
limit = MessageHttpController.DEFAULT_MESSAGE_LIMIT
options =
order_by: "timestamp"
sort_order: -1
limit: limit
logger.log options:options, "get message request recived"
RoomManager.findOrCreateRoom project_id: project_id, (error, room) ->
logger.log {limit, before, project_id, client_thread_id}, "get message request received"
ThreadManager.findOrCreateThread project_id, client_thread_id, (error, thread) ->
return next(error) if error?
query.room_id = room._id
MessageManager.getMessages query, options, (error, messages) ->
if error?
logger.err err:error, "something went getMessages"
return next("something went wrong")
thread_object_id = thread._id
logger.log {limit, before, project_id, client_thread_id, thread_object_id}, "found or created thread"
MessageManager.getMessages thread_object_id, limit, before, (error, messages) ->
return next(error) if error?
MessageManager.populateMessagesWithUsers messages, (error, messages) ->
if error?
logger.err err:error, "something went populateMessagesWithUsers"
return next("something went wrong")
return next(error) if error?
messages = MessageFormatter.formatMessagesForClientSide messages
logger.log project_id: project_id, "got messages"
res.send 200, messages
logger.log {project_id, messages}, "got messages"
res.send 200, messages

View file

@ -5,21 +5,28 @@ WebApiManager = require "../WebApi/WebApiManager"
async = require "async"
module.exports = MessageManager =
createMessage: (message, callback = (error, message) ->) ->
message = @_ensureIdsAreObjectIds(message)
db.messages.save message, callback
createMessage: (room_id, user_id, content, timestamp, callback = (error, message) ->) ->
newMessageOpts =
content: content
room_id: room_id
user_id: user_id
timestamp: timestamp
newMessageOpts = @_ensureIdsAreObjectIds(newMessageOpts)
db.messages.save newMessageOpts, callback
getMessages: (query, options, callback = (error, messages) ->) ->
getMessages: (room_id, limit, before, callback = (error, messages) ->) ->
query =
room_id: room_id
if before?
query.timestamp = { $lt: before }
query = @_ensureIdsAreObjectIds(query)
cursor = db.messages.find(query)
if options.order_by?
options.sort_order ||= 1
sortQuery = {}
sortQuery[options.order_by] = options.sort_order
cursor = cursor.sort(sortQuery)
if options.limit?
cursor = cursor.limit(options.limit)
cursor = db.messages.find(query).sort({ timestamp: -1 }).limit(limit)
cursor.toArray callback
findAllMessagesInRooms: (room_ids, callback = (error, messages) ->) ->
db.messages.find {
room_id: { $in: room_ids }
}, callback
populateMessagesWithUsers: (messages, callback = (error, messages) ->) ->
jobs = new Array()

View file

@ -1,18 +0,0 @@
mongojs = require("../../mongojs")
db = mongojs.db
ObjectId = mongojs.ObjectId
module.exports = RoomManager =
findOrCreateRoom: (query, callback = (error, room) ->) ->
if query.project_id? and query.project_id not instanceof ObjectId
query.project_id = ObjectId(query.project_id)
db.rooms.findOne query, (error, room) ->
return callback(error) if error?
if room?
callback null, room
else
db.rooms.save query, (error, room) ->
return callback(error) if error?
callback null, room

View file

@ -0,0 +1,34 @@
mongojs = require("../../mongojs")
db = mongojs.db
ObjectId = mongojs.ObjectId
module.exports = ThreadManager =
GLOBAL_THREAD: "GLOBAL"
findOrCreateThread: (project_id, thread_id, callback = (error, thread) ->) ->
query =
project_id: ObjectId(project_id.toString())
if thread_id? and thread_id != ThreadManager.GLOBAL_THREAD
query.thread_id = ObjectId(thread_id.toString())
# Threads used to be called rooms, and still are in the DB
db.rooms.findOne query, (error, thread) ->
return callback(error) if error?
if thread?
callback null, thread
else
db.rooms.save query, (error, thread) ->
return callback(error) if error?
callback null, thread
findAllThreadRooms: (project_id, callback = (error, rooms) ->) ->
db.rooms.find {
project_id: ObjectId(project_id.toString())
thread_id: { $exists: true }
}, {
thread_id: 1
}, callback

View file

@ -1,6 +1,6 @@
Settings = require("settings-sharelatex")
mongojs = require "mongojs"
db = mongojs.connect(Settings.mongo.url, ["rooms", "messages"])
db = mongojs(Settings.mongo.url, ["rooms", "messages"])
module.exports =
db: db
ObjectId: mongojs.ObjectId

View file

@ -1,10 +1,38 @@
MessageHttpController = require('./Features/Messages/MessageHttpController')
{ObjectId} = require "./mongojs"
module.exports = Router =
route: (app) ->
app.get "/room/:project_id/messages", MessageHttpController.getMessages
app.post "/room/:project_id/messages", MessageHttpController.sendMessage
app.param 'project_id', (req, res, next, project_id) ->
if ObjectId.isValid(project_id)
next()
else
res.send 400, "Invalid project_id"
app.param 'thread_id', (req, res, next, thread_id) ->
if ObjectId.isValid(thread_id)
next()
else
res.send 400, "Invalid thread_id"
# These are for backwards compatibility
app.get "/room/:project_id/messages", MessageHttpController.getGlobalMessages
app.post "/room/:project_id/messages", MessageHttpController.sendGlobalMessage
app.get "/project/:project_id/messages", MessageHttpController.getGlobalMessages
app.post "/project/:project_id/messages", MessageHttpController.sendGlobalMessage
app.post "/project/:project_id/thread/:thread_id/messages", MessageHttpController.sendThreadMessage
app.get "/project/:project_id/threads", MessageHttpController.getAllThreads
# app.get "/project/:project_id/thread", MessageHttpController.getAllThreadMessages
#
# app.post "/project/:project_id/thread/:thread_id/messages/:message_id/edit", MessageHttpController.editMessage
# app.del "/project/:project_id/thread/:thread_id/messages/:message_id", MessageHttpController.deleteMessage
#
# app.post "/project/:project_id/thread/:thread_id/resolve", MessageHttpController.resolveThread
# app.post "/project/:project_id/thread/:thread_id/reopen", MessageHttpController.reopenThread
# app.del "/project/:project_id/thread/:thread_id", MessageHttpController.deleteThread
#
app.get "/status", (req, res, next) ->
res.send("chat is alive")

View file

@ -11,8 +11,8 @@
"coffee-script": "~1.7.1",
"express": "3.3.1",
"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",
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.6.0",
"mongojs": "^2.4.0",
"redis": "~0.10.1",
"request": "^2.79.0",
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0"
@ -25,11 +25,6 @@
"grunt-concurrent": "~0.4.2",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.7.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jade": "~0.8.0",
"grunt-contrib-less": "~0.8.2",
"grunt-contrib-requirejs": "~0.4.1",
"grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-watch": "~0.5.3",
"grunt-execute": "^0.2.2",
"grunt-forever": "^0.4.7",
@ -37,7 +32,6 @@
"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

@ -25,16 +25,16 @@ describe "Getting messages", ->
email: "john@example.com"
}
describe "normally", ->
describe "globally", ->
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
(cb) => ChatClient.sendGlobalMessage @project_id, @user_id1, @content1, cb
(cb) => ChatClient.sendGlobalMessage @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) =>
ChatClient.getGlobalMessages @project_id, (error, response, messages) =>
expect(messages.length).to.equal 2
messages.reverse()
expect(messages[0].content).to.equal @content1
@ -55,17 +55,72 @@ describe "Getting messages", ->
}
done()
describe "from all the threads", ->
before (done) ->
@project_id = ObjectId().toString()
@thread_id1 = ObjectId().toString()
@thread_id2 = ObjectId().toString()
async.series [
(cb) => ChatClient.sendMessage @project_id, @thread_id1, @user_id1, "one", cb
(cb) => ChatClient.sendMessage @project_id, @thread_id2, @user_id2, "two", cb
(cb) => ChatClient.sendMessage @project_id, @thread_id1, @user_id1, "three", cb
(cb) => ChatClient.sendMessage @project_id, @thread_id2, @user_id2, "four", cb
], done
it "should contain a dictionary of threads with messages with populated users", (done) ->
ChatClient.getThreads @project_id, (error, response, threads) =>
expect(Object.keys(threads).length).to.equal 2
thread1 = threads[@thread_id1]
expect(thread1.length).to.equal 2
thread2 = threads[@thread_id2]
expect(thread2.length).to.equal 2
expect(thread1[0].content).to.equal "one"
expect(thread1[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(thread1[1].content).to.equal "three"
expect(thread1[1].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(thread2[0].content).to.equal "two"
expect(thread2[0].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")}"
}
expect(thread2[1].content).to.equal "four"
expect(thread2[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
(cb) => ChatClient.sendGlobalMessage @project_id, @user_id3, @content1, cb
(cb) => ChatClient.sendGlobalMessage @project_id, @user_id2, @content2, cb
], done
it "should just return null for the user", (done) ->
ChatClient.getMessages @project_id, (error, response, messages) =>
ChatClient.getGlobalMessages @project_id, (error, response, messages) =>
expect(messages.length).to.equal 2
messages.reverse()
expect(messages[0].content).to.equal @content1

View file

@ -5,19 +5,72 @@ MockWebApi = require "./helpers/MockWebApi"
ChatClient = require "./helpers/ChatClient"
describe "Sending a message", ->
before (done) ->
before ->
@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()
@thread_id = ObjectId().toString()
MockWebApi.addUser @user_id, @user = {
id: @user_id
first_name: "Jane"
last_name: "Smith"
email: "jane@example.com"
}
describe "globally", ->
before (done) ->
@content = "global message"
ChatClient.sendGlobalMessage @project_id, @user_id, @content, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
expect(body.content).to.equal @content
expect(body.user.id).to.equal @user_id
expect(body.room.id).to.equal @project_id
done()
it "should then list the message in the project messages", (done) ->
ChatClient.getGlobalMessages @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()
describe "to a thread", ->
before (done) ->
@content = "thread message"
ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 201
expect(body.content).to.equal @content
expect(body.user.id).to.equal @user_id
expect(body.room.id).to.equal @project_id
done()
it "should then list the message in the threads", (done) ->
ChatClient.getThreads @project_id, (error, response, threads) =>
expect(error).to.be.null
expect(response.statusCode).to.equal 200
expect(threads[@thread_id].length).to.equal 1
expect(threads[@thread_id][0].content).to.equal @content
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()
describe "with a malformed user_id", ->
it "should return a graceful error", (done) ->
ChatClient.sendMessage @project_id, @thread_id, "malformed-user", "content", (error, response, body) =>
expect(response.statusCode).to.equal 400
expect(body).to.equal "Invalid user_id"
done()
describe "with a malformed project_id", ->
it "should return a graceful error", (done) ->
ChatClient.sendMessage "malformed-project", @thread_id, @user_id, "content", (error, response, body) =>
expect(response.statusCode).to.equal 400
expect(body).to.equal "Invalid project_id"
done()
describe "with a malformed thread_id", ->
it "should return a graceful error", (done) ->
ChatClient.sendMessage @project_id, "malformed-thread-id", @user_id, "content", (error, response, body) =>
expect(response.statusCode).to.equal 400
expect(body).to.equal "Invalid thread_id"
done()

View file

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

View file

@ -1,13 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Messages/MessageFormatter.js"
SandboxedModule = require('sandboxed-module')
events = require "events"
ObjectId = require("mongojs").ObjectId
describe "MessageFormatter", ->
beforeEach ->
@MessageFormatter = SandboxedModule.require modulePath, requires: {}

View file

@ -1,173 +0,0 @@
should = require('chai').should()
SandboxedModule = require('sandboxed-module')
assert = require('assert')
path = require('path')
sinon = require('sinon')
modulePath = path.join __dirname, "../../../../app/js/Features/Messages/MessageHttpController"
expect = require("chai").expect
tk = require("timekeeper")
describe "MessagesHttpController", ->
beforeEach ->
@settings = {}
@date = Date.now()
tk.freeze(@date)
@MessagesHttpController = SandboxedModule.require modulePath, requires:
"settings-sharelatex":@settings
"logger-sharelatex": log:->
"./MessageManager": @MessageManager = {}
"./MessageFormatter": @MessageFormatter = {}
"../Rooms/RoomManager": @RoomManager = {}
@req =
body:{}
@res = {}
@project_id = "12321321"
@room_id = "Asdfadf adfafd"
@user_id = "09832910838239081203981"
@content = "my message here"
afterEach ->
tk.reset()
describe "sendMessage", ->
beforeEach ->
@initialMessage = {content:@content}
@MessageManager.createMessage = sinon.stub().callsArgWith(1, null, @initialMessage)
@req.params =
project_id:@project_id
@req.body =
user_id:@user_id
content:@content
@singlePopulatedMessage = {data:"here"}
@MessageManager.populateMessagesWithUsers = sinon.stub().callsArgWith(1, null, [@singlePopulatedMessage])
@RoomManager.findOrCreateRoom = sinon.stub().callsArgWith(1, null, @room = { _id : @room_id })
@formattedMessage = {formatted:true}
@MessageFormatter.formatMessageForClientSide = sinon.stub().returns(@formattedMessage)
it "should look up the room for the project", ->
@res.send = =>
@RoomManager.findOrCreateRoom
.calledWith({
project_id: @project_id
})
.should.equal true
done()
it "should create the message with the message manager", (done)->
@res.send = =>
@MessageManager.createMessage
.calledWith({
content: @content
user_id: @user_id
room_id: @room_id
timestamp: @date
})
.should.equal true
done()
@MessagesHttpController.sendMessage @req, @res
it "should return the formetted message", (done)->
@res.send = (code, data)=>
assert.deepEqual @MessageManager.populateMessagesWithUsers.args[0][0], [@initialMessage]
code.should.equal 201
data.should.equal @formattedMessage
done()
@MessagesHttpController.sendMessage @req, @res
describe "getMessages", ->
beforeEach ->
@project_id = "room-id-123"
@timestamp = Date.now()
@limit = 42
@messages = "messages without users stub"
@messagesWithUsers = "messages with users stub"
@formattedMessages = "formatted messages stub"
@RoomManager.findOrCreateRoom = sinon.stub().callsArgWith(1, null, @room = { _id : @room_id })
@MessageManager.getMessages = sinon.stub().callsArgWith(2, null, @messages)
@MessageManager.populateMessagesWithUsers = sinon.stub().callsArgWith(1, null, @messagesWithUsers)
@MessageFormatter.formatMessagesForClientSide = sinon.stub().returns @formattedMessages
describe "with a timestamp and limit", ->
beforeEach ->
@req.params =
project_id:@project_id
@req.query =
before: @timestamp,
limit: "#{@limit}"
it "should look up the room for the project", ->
@res.send = =>
@RoomManager.findOrCreateRoom
.calledWith({
project_id: @project_id
})
.should.equal true
done()
it "should get the requested messages", ->
@res.send = =>
@MessageManager.getMessages
.calledWith({
timestamp: $lt: @timestamp
room_id: @room_id
}, {
limit: @limit
order_by: "timestamp"
sort_order: -1
})
.should.equal true
@MessagesHttpController.getMessages(@req, @res)
it "should populate the messages with the users", (done)->
@res.send = =>
@MessageManager.populateMessagesWithUsers.calledWith(@messages).should.equal true
done()
@MessagesHttpController.getMessages(@req, @res)
it "should return the formatted messages", (done)->
@res.send = ()=>
@MessageFormatter.formatMessagesForClientSide.calledWith(@messagesWithUsers).should.equal true
done()
@MessagesHttpController.getMessages(@req, @res)
it "should send the formated messages back with a 200", (done)->
@res.send = (code, data)=>
code.should.equal 200
data.should.equal @formattedMessages
done()
@MessagesHttpController.getMessages(@req, @res)
describe "without a timestamp or limit", ->
beforeEach ->
@req.params =
project_id:@project_id
it "should get a default number of messages from the beginning", ->
@res.send = =>
@MessageManager.getMessages
.calledWith({
room_id: @room_id
}, {
limit: @MessagesHttpController.DEFAULT_MESSAGE_LIMIT
order_by: "timestamp"
sort_order: -1
})
.should.equal true
@MessagesHttpController.getMessages(@req, @res)

View file

@ -1,61 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Messages/MessageManager.js"
SandboxedModule = require('sandboxed-module')
events = require "events"
ObjectId = require("mongojs").ObjectId
describe "MessageManager", ->
beforeEach ->
@MessageManager = SandboxedModule.require modulePath, requires:
"../WebApi/WebApiManager": @WebApiManager = {}
"../../mongojs": {}
@callback = sinon.stub()
describe "populateMessagesWithUsers", ->
beforeEach ->
@user0 =
id: ObjectId().toString()
first_name: "Adam"
@user1 =
id: ObjectId().toString()
first_name: "Eve"
@users = {}
@users[@user0.id] = @user0
@users[@user1.id] = @user1
@messages = [{
content: "First message content"
user_id: ObjectId(@user0.id)
}, {
content: "Second message content"
user_id: ObjectId(@user0.id)
}, {
content: "Third message content"
user_id: ObjectId(@user1.id)
}]
@WebApiManager.getUserDetails = (user_id, callback = (error, user) ->) =>
callback null, @users[user_id]
sinon.spy @WebApiManager, "getUserDetails"
@MessageManager.populateMessagesWithUsers @messages, @callback
it "should insert user objects in the place of user_ids", ->
messages = @callback.args[0][1]
expect(messages).to.deep.equal [{
content: "First message content"
user: @user0
}, {
content: "Second message content"
user: @user0
}, {
content: "Third message content"
user: @user1
}]
it "should call getUserDetails once and only once for each user", ->
@WebApiManager.getUserDetails.calledWith(@user0.id).should.equal true
@WebApiManager.getUserDetails.calledWith(@user1.id).should.equal true
@WebApiManager.getUserDetails.calledTwice.should.equal true

View file

@ -1,58 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Rooms/RoomManager.js"
SandboxedModule = require('sandboxed-module')
events = require "events"
mongojs = require "mongojs"
ObjectId = mongojs.ObjectId
describe "RoomManager", ->
beforeEach ->
@RoomManager = SandboxedModule.require modulePath, requires:
"../../mongojs":
db: @db = { rooms: {} }
ObjectId: ObjectId
@callback = sinon.stub()
describe "findOrCreateRoom", ->
describe "when the room exists", ->
beforeEach ->
@project_id = ObjectId().toString()
@room =
_id: ObjectId()
project_id: ObjectId(@project_id)
@db.rooms.findOne = sinon.stub().callsArgWith(1, null, @room)
@RoomManager.findOrCreateRoom(project_id: @project_id, @callback)
it "should look up the room based on the query", ->
@db.rooms.findOne
.calledWith(project_id: ObjectId(@project_id))
.should.equal true
it "should return the room in the callback", ->
@callback
.calledWith(null, @room)
.should.equal true
describe "when the room does not exist", ->
beforeEach ->
@project_id = ObjectId().toString()
@room =
_id: ObjectId()
project_id: ObjectId(@project_id)
@db.rooms.findOne = sinon.stub().callsArgWith(1, null, null)
@db.rooms.save = sinon.stub().callsArgWith(1, null, @room)
@RoomManager.findOrCreateRoom(project_id: @project_id, @callback)
it "should create the room", ->
@db.rooms.save
.calledWith(project_id: ObjectId(@project_id))
.should.equal true
it "should return the room in the callback", ->
@callback
.calledWith(null, @room)
.should.equal true

View file

@ -1,12 +0,0 @@
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../../app/js/Features/Users/UserFormatter.js"
SandboxedModule = require('sandboxed-module')
events = require "events"
describe "UserFormatter", ->
beforeEach ->
@UserFormatter = SandboxedModule.require modulePath, requires: {}