SandboxedModule = require('sandboxed-module') should = require('chai').should() sinon = require 'sinon' expect = require("chai").expect querystring = require 'querystring' modulePath = "../../../../app/js/Features/Subscription/TeamInvitesHandler" ObjectId = require("mongojs").ObjectId Errors = require("../../../../app/js/Features/Errors/Errors") describe "TeamInvitesHandler", -> beforeEach -> @manager = { id: "666666", first_name: "Daenerys" last_name: "Targaryen" email: "daenerys@example.com" } @token = "aaaaaaaaaaaaaaaaaaaaaa" @teamInvite = { email: "jorah@example.com", token: @token, } @subscription = { id: "55153a8014829a865bbf700d", admin_id: @manager.id, groupPlan: true, member_ids: [], teamInvites: [ @teamInvite ], save: sinon.stub().yields(null), } @SubscriptionLocator = { getUsersSubscription: sinon.stub(), getSubscription: sinon.stub().yields(null, @subscription) } @UserGetter = { getUser: sinon.stub().yields(), getUserByAnyEmail: sinon.stub().yields() } @SubscriptionUpdater = { addUserToGroup: sinon.stub().yields() } @LimitationsManager = { teamHasReachedMemberLimit: sinon.stub().returns(false) } @Subscription = { findOne: sinon.stub().yields() update: sinon.stub().yields() } @EmailHandler = { sendEmail: sinon.stub().yields(null) } @newToken = "bbbbbbbbb" @crypto = { randomBytes: => toString: sinon.stub().returns(@newToken) } @UserGetter.getUser.withArgs(@manager.id).yields(null, @manager) @UserGetter.getUserByAnyEmail.withArgs(@manager.email).yields(null, @manager) @SubscriptionLocator.getUsersSubscription.yields(null, @subscription) @Subscription.findOne.yields(null, @subscription) @TeamInvitesHandler = SandboxedModule.require modulePath, requires: "logger-sharelatex": { log: -> } "crypto": @crypto "settings-sharelatex": { siteUrl: "http://example.com" } "../../models/TeamInvite": { TeamInvite: @TeamInvite = {} } "../../models/Subscription": { Subscription: @Subscription } "../User/UserGetter": @UserGetter "./SubscriptionLocator": @SubscriptionLocator "./SubscriptionUpdater": @SubscriptionUpdater "./LimitationsManager": @LimitationsManager "../Email/EmailHandler": @EmailHandler "../Errors/Errors": Errors describe "getInvite", -> it "returns the invite if there's one", (done) -> @TeamInvitesHandler.getInvite @token, (err, invite, subscription) => expect(err).to.eq(null) expect(invite).to.deep.eq(@teamInvite) expect(subscription).to.deep.eq(@subscription) done() it "returns teamNotFound if there's none", (done) -> @Subscription.findOne = sinon.stub().yields(null, null) @TeamInvitesHandler.getInvite @token, (err, invite, subscription) -> expect(err).to.be.instanceof(Errors.NotFoundError) done() describe "createInvite", -> it "adds the team invite to the subscription", (done) -> @TeamInvitesHandler.createInvite @manager.id, "John.Snow@example.com", (err, invite) => expect(err).to.eq(null) expect(invite.token).to.eq(@newToken) expect(invite.email).to.eq("john.snow@example.com") expect(invite.inviterName).to.eq("Daenerys Targaryen (daenerys@example.com)") expect(@subscription.teamInvites).to.deep.include(invite) done() it "sends an email", (done) -> @TeamInvitesHandler.createInvite @manager.id, "John.Snow@example.com", (err, invite) => @EmailHandler.sendEmail.calledWith("verifyEmailToJoinTeam", sinon.match({ to: "john.snow@example.com", inviterName: "Daenerys Targaryen (daenerys@example.com)", acceptInviteUrl: "http://example.com/subscription/invites/#{@newToken}/" }) ).should.equal true done() it "refreshes the existing invite if the email has already been invited", (done) -> originalInvite = Object.assign({}, @teamInvite) @TeamInvitesHandler.createInvite @manager.id, originalInvite.email, (err, invite) => expect(err).to.eq(null) expect(invite).to.exist expect(@subscription.teamInvites.length).to.eq 1 expect(@subscription.teamInvites).to.deep.include invite expect(invite.email).to.eq originalInvite.email @subscription.save.calledOnce.should.eq true done() it "removes any legacy invite from the subscription", (done) -> @TeamInvitesHandler.createInvite @manager.id, "John.Snow@example.com", (err, invite) => @Subscription.update.calledWith( { _id: new ObjectId("55153a8014829a865bbf700d") }, { '$pull': { invited_emails: "john.snow@example.com" } } ).should.eq true done() describe "createDomainInvite", -> beforeEach -> @licence = subscription_id: @subscription.id name: "Team Daenerys" @user = email: "John.Snow@example.com" it "adds the team invite to the subscription", (done) -> @TeamInvitesHandler.createDomainInvite @user, @licence, (err, invite) => expect(err).to.eq(null) expect(invite.token).to.eq(@newToken) expect(invite.email).to.eq("john.snow@example.com") expect(invite.inviterName).to.eq("Team Daenerys") expect(@subscription.teamInvites).to.deep.include(invite) done() it "sends an email", (done) -> @TeamInvitesHandler.createDomainInvite @user, @licence, (err, invite) => @EmailHandler.sendEmail.calledWith("verifyEmailToJoinTeam", sinon.match({ to: "john.snow@example.com" inviterName: "Team Daenerys" acceptInviteUrl: "http://example.com/subscription/invites/#{@newToken}/" }) ).should.equal true done() describe "acceptInvite", -> beforeEach -> @user = { id: "123456789", first_name: "Tyrion", last_name: "Lannister", email: "tyrion@example.com" } @UserGetter.getUserByAnyEmail.withArgs(@user.email).yields(null, @user) @subscription.teamInvites.push({ email: "john.snow@example.com", token: "dddddddd", inviterName: "Daenerys Targaryen (daenerys@example.com)" }) it "adds the user to the team", (done) -> @TeamInvitesHandler.acceptInvite "dddddddd", @user.id, => @SubscriptionUpdater.addUserToGroup.calledWith(@manager.id, @user.id).should.eq true done() it "removes the invite from the subscription", (done) -> @TeamInvitesHandler.acceptInvite "dddddddd", @user.id, => @Subscription.update.calledWith( { _id: new ObjectId("55153a8014829a865bbf700d") }, { '$pull': { teamInvites: { email: 'john.snow@example.com' } } } ).should.eq true done() describe "revokeInvite", -> it "removes the team invite from the subscription", (done) -> @TeamInvitesHandler.revokeInvite @manager.id, "jorah@example.com", => @Subscription.update.calledWith( { _id: new ObjectId("55153a8014829a865bbf700d") }, { '$pull': { teamInvites: { email: "jorah@example.com" } } } ).should.eq true @Subscription.update.calledWith( { _id: new ObjectId("55153a8014829a865bbf700d") }, { '$pull': { invited_emails: "jorah@example.com" } } ).should.eq true done() describe "createTeamInvitesForLegacyInvitedEmail", (done) -> beforeEach -> @subscription.invited_emails = ["eddard@example.com", "robert@example.com"] @TeamInvitesHandler.createInvite = sinon.stub().yields(null) @SubscriptionLocator.getGroupsWithEmailInvite = sinon.stub().yields(null, [@subscription]) it "sends an invitation email to addresses in the legacy invited_emails field", (done) -> @TeamInvitesHandler.createTeamInvitesForLegacyInvitedEmail "eddard@example.com", (err, invite) => expect(err).not.to.exist @TeamInvitesHandler.createInvite.calledWith( @subscription.admin_id, "eddard@example.com" ).should.eq true @TeamInvitesHandler.createInvite.callCount.should.eq 1 done() describe "validation", -> it "doesn't create an invite if the team limit has been reached", (done) -> @LimitationsManager.teamHasReachedMemberLimit = sinon.stub().returns(true) @TeamInvitesHandler.createInvite @manager.id, "John.Snow@example.com", (err, invite) => expect(err).to.deep.equal(limitReached: true) done() it "doesn't create an invite if the subscription is not in a group plan", (done) -> @subscription.groupPlan = false @TeamInvitesHandler.createInvite @manager.id, "John.Snow@example.com", (err, invite) => expect(err).to.deep.equal(wrongPlan: true) done() it "doesn't create an invite if the user is already part of the team", (done) -> member = { id: "1a2b", _id: "1a2b", email: "tyrion@example.com" } @subscription.member_ids = [member.id] @UserGetter.getUserByAnyEmail.withArgs(member.email).yields(null, member) @TeamInvitesHandler.createInvite @manager.id, "tyrion@example.com", (err, invite) => expect(err).to.deep.equal(alreadyInTeam: true) expect(invite).not.to.exist done()