From 9fba98cd4554c91864db4985114b39da79f28406 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Fri, 22 Jul 2016 11:38:00 +0100 Subject: [PATCH] Accept invite, and start testing the invite handler. --- .../CollaboratorsInviteController.coffee | 2 +- .../CollaboratorsInviteHandler.coffee | 70 +++++++++++++++++-- .../CollaboratorsInviteHandlerTests.coffee | 69 ++++++++++++++++++ 3 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee index 4b48ce01c9..1cc36d6e14 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee @@ -60,6 +60,6 @@ module.exports = CollaboratorsInviteController = logger.log {projectId, inviteId}, "accepting invite" CollaboratorsInviteHandler.acceptInvite projectId, inviteId, token, currentUser, (err) -> if err? - logger.err {projectId, inviteId}, "error getting invite by token" + logger.err {projectId, inviteId}, "error accepting invite by token" return next(err) res.sendStatus(201) diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee index b8416dc93e..9d23605ef8 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee @@ -1,9 +1,7 @@ -UserCreator = require('../User/UserCreator') Project = require("../../models/Project").Project ProjectInvite = require("../../models/ProjectInvite").ProjectInvite mimelib = require("mimelib") logger = require('logger-sharelatex') -UserGetter = require "../User/UserGetter" ContactManager = require "../Contacts/ContactManager" CollaboratorsEmailHandler = require "./CollaboratorsEmailHandler" Async = require "async" @@ -11,7 +9,7 @@ PrivilegeLevels = require "../Authorization/PrivilegeLevels" Errors = require "../Errors/Errors" Crypto = require 'crypto' -module.experts = CollaboratorsInviteHandler = +module.exports = CollaboratorsInviteHandler = inviteToProject: (projectId, sendingUserId, email, privileges, callback=(err,invite)->) -> logger.log {projectId, sendingUserId, email, privileges}, "adding invite" @@ -27,7 +25,7 @@ module.experts = CollaboratorsInviteHandler = projectId: projectId privileges: privileges } - ProjectInvite.save (err) -> + invite.save (err, invite) -> if err? logger.err {err, projectId, sendingUserId, email}, "error saving token" return callback(err) @@ -48,10 +46,72 @@ module.experts = CollaboratorsInviteHandler = if err? logger.err {err, projectId, inviteId}, "error fetching invite" return callback(err) + if !invite + err = new Errors.NotFoundError("no invite found for token") + logger.err {err, projectId, token: tokenString}, "no invite found" + return callback(err) now = new Date() + # TODO: re-assess whether we should return null or a notfounderror if invite.expiresAt < now logger.log {projectId, inviteId, expiresAt: invite.expiresAt}, "invite expired" return callback(null, null) callback(null, invite) - acceptInvite: (projectId, inviteId, callback=(err)->) -> + acceptInvite: (projectId, inviteId, tokenString, user, callback=(err)->) -> + Project.findOne {_id: projectId}, (err, project) -> + if err? + logger.err {err, projectId}, "error finding project" + return callback(err) + if !project + err = new Errors.NotFoundError("no project found for invite") + logger.log {err, projectId, inviteId}, "no project found" + return callback(err) + # TODO: check if we need to cast the ids to ObjectId + ProjectInvite.findOne {_id: inviteId, projectId: projectId, token: token}, (err, invite) -> + if err? + logger.err {err, projectId, inviteId}, "error finding invite" + return callback(err) + if !invite + err = new Errors.NotFoundError("no matching invite found") + logger.log {err, projectId, inviteId}, "no matching invite found" + return callback(err) + + now = new Date() + if invite.expiresAt < now + err = new Errors.NotFoundError("invite expired") + logger.log {err, projectId, inviteId, expiresAt: invite.expiresAt}, "invite expired" + return callback(err) + + # do the thing + existing_users = (project.collaberator_refs or []) + existing_users = existing_users.concat(project.readOnly_refs or []) + existing_users = existing_users.map (u) -> u.toString() + if existing_users.indexOf(user._id.toString()) > -1 + return callback null # User already in Project + + privilegeLevel = invite.privileges + + if privilegeLevel == PrivilegeLevels.READ_AND_WRITE + level = {"collaberator_refs": user._id} + logger.log {privileges: privilegeLevel, user_id: user._id, projectId}, "adding user" + else if privilegeLevel == PrivilegeLevels.READ_ONLY + level = {"readOnly_refs": user._id} + logger.log {privileges: privilegeLevel, user_id: user._id, projectId}, "adding user" + else + return callback(new Error("unknown privilegeLevel: #{privilegeLevel}")) + + ContactManager.addContact invite.sendingUserId, user._id + + Project.update { _id: project._id }, { $addToSet: level }, (error) -> + return callback(error) if error? + # Flush to TPDS in background to add files to collaborator's Dropbox + ProjectEntityHandler = require("../Project/ProjectEntityHandler") + ProjectEntityHandler.flushProjectToThirdPartyDataStore project_id, (error) -> + if error? + logger.error {err: error, project_id, user_id}, "error flushing to TPDS after adding collaborator" + # Remove invite + ProjectInvite.remove {_id: inviteId}, (err) -> + if err? + logger.err {err, projectId, inviteId}, "error removing invite" + return callback(err) + callback() diff --git a/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee b/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee new file mode 100644 index 0000000000..77c768cf47 --- /dev/null +++ b/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee @@ -0,0 +1,69 @@ +sinon = require('sinon') +chai = require('chai') +should = chai.should() +expect = chai.expect +modulePath = "../../../../app/js/Features/Collaborators/CollaboratorsInviteHandler.js" +SandboxedModule = require('sandboxed-module') +events = require "events" +ObjectId = require("mongojs").ObjectId + +describe "CollaboratorsInviteHandler", -> + beforeEach -> + @ProjectInvite = class ProjectInvite + constructor: (options={}) -> + this._id = ObjectId() + for k,v of options + this[k] = v + this + save: sinon.stub() + findOne: sinon.stub() + @Project = {} + @CollaboratorsInviteHandler = SandboxedModule.require modulePath, requires: + 'settings-sharelatex': @settings = {} + 'logger-sharelatex': @logger = {err: sinon.stub(), error: sinon.stub(), log: sinon.stub()} + './CollaboratorsEmailHandler': @CollaboratorsEmailHandler = {} + '../Contacts/ContactManager': @ContactManager = {} + '../../models/Project': {Project: @Project} + '../../models/ProjectInvite': {ProjectInvite: @ProjectInvite} + + @projectId = ObjectId() + @sendingUserId = ObjectId() + @email = "user@example.com" + @userId = ObjectId() + @privileges = "readAndWrite" + + describe 'inviteToProject', -> + + beforeEach -> + @ProjectInvite::save = sinon.spy (cb) -> cb(null, this) + @CollaboratorsEmailHandler.notifyUserOfProjectInvite = sinon.stub() + @call = (callback) => + @CollaboratorsInviteHandler.inviteToProject @projectId, @sendingUserId, @email, @privileges, callback + + describe 'when all goes well', -> + + beforeEach -> + + it 'should not produce an error', (done) -> + @call (err, invite) => + expect(err).to.not.be.instanceof Error + done() + + it 'should produce the invite object', (done) -> + @call (err, invite) => + expect(invite).to.not.equal null + expect(invite).to.not.equal undefined + expect(invite).to.be.instanceof Object + expect(invite).to.have.all.keys ['_id', 'email', 'token', 'sendingUserId', 'projectId', 'privileges'] + done() + + it 'should have called ProjectInvite.save', (done) -> + @call (err, invite) => + @ProjectInvite::save.callCount.should.equal 1 + done() + + it 'should have called CollaboratorsEmailHandler.notifyUserOfProjectInvite', (done) -> + @call (err, invite) => + @CollaboratorsEmailHandler.notifyUserOfProjectInvite.callCount.should.equal 1 + @CollaboratorsEmailHandler.notifyUserOfProjectInvite.calledWith(@projectId, @email).should.equal true + done()