From cb88e1e41b69fdac2f139db81eed66f2289b28b8 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 16 Dec 2016 16:52:50 +0000 Subject: [PATCH] Add in support for threads for comments --- services/chat/Gruntfile.coffee | 58 +----- .../Features/Messages/MessageFormatter.coffee | 16 ++ .../Messages/MessageHttpController.coffee | 81 ++++---- .../Features/Messages/MessageManager.coffee | 31 ++-- .../coffee/Features/Rooms/RoomManager.coffee | 18 -- .../Features/Threads/ThreadManager.coffee | 34 ++++ services/chat/app/coffee/mongojs.coffee | 2 +- services/chat/app/coffee/router.coffee | 34 +++- services/chat/package.json | 10 +- .../coffee/GettingMessagesTests.coffee | 69 ++++++- .../coffee/SendingAMessageTests.coffee | 79 ++++++-- .../coffee/helpers/ChatClient.coffee | 22 ++- .../Messages/MessageFormatterTests.coffee | 13 -- .../MessageHttpControllerTests.coffee | 173 ------------------ .../Messages/MessageManagerTests.coffee | 61 ------ .../unit/coffee/Rooms/RoomManagerTests.coffee | 58 ------ .../coffee/Users/UserFormatterTests.coffee | 12 -- 17 files changed, 296 insertions(+), 475 deletions(-) delete mode 100644 services/chat/app/coffee/Features/Rooms/RoomManager.coffee create mode 100644 services/chat/app/coffee/Features/Threads/ThreadManager.coffee delete mode 100644 services/chat/test/unit/coffee/Messages/MessageFormatterTests.coffee delete mode 100644 services/chat/test/unit/coffee/Messages/MessageHttpControllerTests.coffee delete mode 100644 services/chat/test/unit/coffee/Messages/MessageManagerTests.coffee delete mode 100644 services/chat/test/unit/coffee/Rooms/RoomManagerTests.coffee delete mode 100644 services/chat/test/unit/coffee/Users/UserFormatterTests.coffee diff --git a/services/chat/Gruntfile.coffee b/services/chat/Gruntfile.coffee index 3496569094..b4d7b91f34 100644 --- a/services/chat/Gruntfile.coffee +++ b/services/chat/Gruntfile.coffee @@ -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'] diff --git a/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee b/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee index 3d77cda1dd..ddfa34f49d 100644 --- a/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee +++ b/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee @@ -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 \ No newline at end of file diff --git a/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee b/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee index c3f28fe86d..2ac046cb79 100644 --- a/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee +++ b/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee @@ -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 \ No newline at end of file + logger.log {project_id, messages}, "got messages" + res.send 200, messages diff --git a/services/chat/app/coffee/Features/Messages/MessageManager.coffee b/services/chat/app/coffee/Features/Messages/MessageManager.coffee index a66f465163..7c2f8541fa 100644 --- a/services/chat/app/coffee/Features/Messages/MessageManager.coffee +++ b/services/chat/app/coffee/Features/Messages/MessageManager.coffee @@ -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() diff --git a/services/chat/app/coffee/Features/Rooms/RoomManager.coffee b/services/chat/app/coffee/Features/Rooms/RoomManager.coffee deleted file mode 100644 index c9820467e8..0000000000 --- a/services/chat/app/coffee/Features/Rooms/RoomManager.coffee +++ /dev/null @@ -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 - diff --git a/services/chat/app/coffee/Features/Threads/ThreadManager.coffee b/services/chat/app/coffee/Features/Threads/ThreadManager.coffee new file mode 100644 index 0000000000..9a3cc87eda --- /dev/null +++ b/services/chat/app/coffee/Features/Threads/ThreadManager.coffee @@ -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 + + + diff --git a/services/chat/app/coffee/mongojs.coffee b/services/chat/app/coffee/mongojs.coffee index 134fcd05ae..44b54c15f4 100644 --- a/services/chat/app/coffee/mongojs.coffee +++ b/services/chat/app/coffee/mongojs.coffee @@ -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 diff --git a/services/chat/app/coffee/router.coffee b/services/chat/app/coffee/router.coffee index 7e19ec62c5..c8c71fc567 100644 --- a/services/chat/app/coffee/router.coffee +++ b/services/chat/app/coffee/router.coffee @@ -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") diff --git a/services/chat/package.json b/services/chat/package.json index 2f3b2625a3..d30b905039 100644 --- a/services/chat/package.json +++ b/services/chat/package.json @@ -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": "" diff --git a/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee b/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee index b5cf30ac0d..4379cd46db 100644 --- a/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee +++ b/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee @@ -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 diff --git a/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee b/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee index e7c7e8f08c..f0c233029a 100644 --- a/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee +++ b/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee @@ -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() \ No newline at end of file diff --git a/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee b/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee index 8be9412fa7..36365726f9 100644 --- a/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee +++ b/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee @@ -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 \ No newline at end of file diff --git a/services/chat/test/unit/coffee/Messages/MessageFormatterTests.coffee b/services/chat/test/unit/coffee/Messages/MessageFormatterTests.coffee deleted file mode 100644 index 96060f31e4..0000000000 --- a/services/chat/test/unit/coffee/Messages/MessageFormatterTests.coffee +++ /dev/null @@ -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: {} - diff --git a/services/chat/test/unit/coffee/Messages/MessageHttpControllerTests.coffee b/services/chat/test/unit/coffee/Messages/MessageHttpControllerTests.coffee deleted file mode 100644 index 1c84531a2e..0000000000 --- a/services/chat/test/unit/coffee/Messages/MessageHttpControllerTests.coffee +++ /dev/null @@ -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) diff --git a/services/chat/test/unit/coffee/Messages/MessageManagerTests.coffee b/services/chat/test/unit/coffee/Messages/MessageManagerTests.coffee deleted file mode 100644 index 86e53b2377..0000000000 --- a/services/chat/test/unit/coffee/Messages/MessageManagerTests.coffee +++ /dev/null @@ -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 - - diff --git a/services/chat/test/unit/coffee/Rooms/RoomManagerTests.coffee b/services/chat/test/unit/coffee/Rooms/RoomManagerTests.coffee deleted file mode 100644 index 2f786b4cad..0000000000 --- a/services/chat/test/unit/coffee/Rooms/RoomManagerTests.coffee +++ /dev/null @@ -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 - diff --git a/services/chat/test/unit/coffee/Users/UserFormatterTests.coffee b/services/chat/test/unit/coffee/Users/UserFormatterTests.coffee deleted file mode 100644 index c6e331c9a8..0000000000 --- a/services/chat/test/unit/coffee/Users/UserFormatterTests.coffee +++ /dev/null @@ -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: {} -