From 872904d73e9717c18ca31f9f2f8eabd99de1ca3c Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Wed, 4 Oct 2023 08:20:23 -0400 Subject: [PATCH] Merge pull request #14945 from overleaf/em-promisify-collaborators-invite-handler Promisify CollaboratorsInviteHandler GitOrigin-RevId: 070f7938eb1c306905b3b70bef212a09b57cdf20 --- .../CollaboratorsEmailHandler.js | 45 +- .../CollaboratorsInviteHandler.js | 455 +++---- .../Notifications/NotificationsBuilder.js | 3 + .../CollaboratorsInviteHandlerTests.js | 1202 +++++++---------- 4 files changed, 656 insertions(+), 1049 deletions(-) diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsEmailHandler.js b/services/web/app/src/Features/Collaborators/CollaboratorsEmailHandler.js index 6831f46c58..5e9cdcb243 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsEmailHandler.js +++ b/services/web/app/src/Features/Collaborators/CollaboratorsEmailHandler.js @@ -1,13 +1,9 @@ -/* eslint-disable - n/handle-callback-err, - max-len, -*/ -let CollaboratorsEmailHandler +const { callbackify } = require('util') const { Project } = require('../../models/Project') const EmailHandler = require('../Email/EmailHandler') const Settings = require('@overleaf/settings') -module.exports = CollaboratorsEmailHandler = { +const CollaboratorsEmailHandler = { _buildInviteUrl(project, invite) { return ( `${Settings.siteUrl}/project/${project._id}/invite/token/${invite.token}?` + @@ -18,22 +14,29 @@ module.exports = CollaboratorsEmailHandler = { ) }, - notifyUserOfProjectInvite(projectId, email, invite, sendingUser, callback) { - Project.findOne({ _id: projectId }) + async notifyUserOfProjectInvite(projectId, email, invite, sendingUser) { + const project = await Project.findOne({ _id: projectId }) .select('name owner_ref') .populate('owner_ref') - .exec(function (err, project) { - const emailOptions = { - to: email, - replyTo: project.owner_ref.email, - project: { - name: project.name, - }, - inviteUrl: CollaboratorsEmailHandler._buildInviteUrl(project, invite), - owner: project.owner_ref, - sendingUser_id: sendingUser._id, - } - EmailHandler.sendEmail('projectInvite', emailOptions, callback) - }) + .exec() + const emailOptions = { + to: email, + replyTo: project.owner_ref.email, + project: { + name: project.name, + }, + inviteUrl: CollaboratorsEmailHandler._buildInviteUrl(project, invite), + owner: project.owner_ref, + sendingUser_id: sendingUser._id, + } + await EmailHandler.promises.sendEmail('projectInvite', emailOptions) }, } + +module.exports = { + promises: CollaboratorsEmailHandler, + notifyUserOfProjectInvite: callbackify( + CollaboratorsEmailHandler.notifyUserOfProjectInvite + ), + _buildInviteUrl: CollaboratorsEmailHandler._buildInviteUrl, +} diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js index 389f29c5f9..9418ff1545 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js +++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js @@ -1,17 +1,5 @@ -/* eslint-disable - n/handle-callback-err, - max-len, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ +const { callbackify, promisify } = require('util') const { ProjectInvite } = require('../../models/ProjectInvite') -const OError = require('@overleaf/o-error') const logger = require('@overleaf/logger') const CollaboratorsEmailHandler = require('./CollaboratorsEmailHandler') const CollaboratorsHandler = require('./CollaboratorsHandler') @@ -20,335 +8,196 @@ const ProjectGetter = require('../Project/ProjectGetter') const Errors = require('../Errors/Errors') const Crypto = require('crypto') const NotificationsBuilder = require('../Notifications/NotificationsBuilder') -const { promisifyAll } = require('../../util/promises') + +const randomBytes = promisify(Crypto.randomBytes) const CollaboratorsInviteHandler = { - getAllInvites(projectId, callback) { - if (callback == null) { - callback = function () {} - } + async getAllInvites(projectId) { logger.debug({ projectId }, 'fetching invites for project') - ProjectInvite.find({ projectId }, function (err, invites) { - if (err != null) { - OError.tag(err, 'error getting invites from mongo', { - projectId, - }) - return callback(err) - } - logger.debug( - { projectId, count: invites.length }, - 'found invites for project' - ) - callback(null, invites) - }) - }, - - getInviteCount(projectId, callback) { - if (callback == null) { - callback = function () {} - } - logger.debug({ projectId }, 'counting invites for project') - ProjectInvite.countDocuments({ projectId }, function (err, count) { - if (err != null) { - OError.tag(err, 'error getting invites from mongo', { - projectId, - }) - return callback(err) - } - callback(null, count) - }) - }, - - _trySendInviteNotification(projectId, sendingUser, invite, callback) { - if (callback == null) { - callback = function () {} - } - const { email } = invite - UserGetter.getUserByAnyEmail( - email, - { _id: 1 }, - function (err, existingUser) { - if (err != null) { - OError.tag(err, 'error checking if user exists', { - projectId, - email, - }) - return callback(err) - } - if (existingUser == null) { - logger.debug( - { projectId, email }, - 'no existing user found, returning' - ) - return callback(null) - } - ProjectGetter.getProject( - projectId, - { _id: 1, name: 1 }, - function (err, project) { - if (err != null) { - OError.tag(err, 'error getting project', { - projectId, - email, - }) - return callback(err) - } - if (project == null) { - logger.debug( - { projectId }, - 'no project found while sending notification, returning' - ) - return callback(null) - } - NotificationsBuilder.projectInvite( - invite, - project, - sendingUser, - existingUser - ).create(callback) - } - ) - } + const invites = await ProjectInvite.find({ projectId }).exec() + logger.debug( + { projectId, count: invites.length }, + 'found invites for project' ) + return invites }, - _tryCancelInviteNotification(inviteId, callback) { - if (callback == null) { - callback = function () {} - } - NotificationsBuilder.projectInvite( - { _id: inviteId }, - null, - null, - null - ).read(callback) + async getInviteCount(projectId) { + logger.debug({ projectId }, 'counting invites for project') + const count = await ProjectInvite.countDocuments({ projectId }).exec() + return count }, - _sendMessages(projectId, sendingUser, invite, callback) { - if (callback == null) { - callback = function () {} + async _trySendInviteNotification(projectId, sendingUser, invite) { + const { email } = invite + const existingUser = await UserGetter.promises.getUserByAnyEmail(email, { + _id: 1, + }) + if (existingUser == null) { + logger.debug({ projectId, email }, 'no existing user found, returning') + return null } + const project = await ProjectGetter.promises.getProject(projectId, { + _id: 1, + name: 1, + }) + if (project == null) { + logger.debug( + { projectId }, + 'no project found while sending notification, returning' + ) + return null + } + await NotificationsBuilder.promises + .projectInvite(invite, project, sendingUser, existingUser) + .create() + }, + + async _tryCancelInviteNotification(inviteId) { + return await NotificationsBuilder.promises + .projectInvite({ _id: inviteId }, null, null, null) + .read() + }, + + async _sendMessages(projectId, sendingUser, invite) { logger.debug( { projectId, inviteId: invite._id }, 'sending notification and email for invite' ) - CollaboratorsEmailHandler.notifyUserOfProjectInvite( + await CollaboratorsEmailHandler.promises.notifyUserOfProjectInvite( projectId, invite.email, invite, + sendingUser + ) + await CollaboratorsInviteHandler._trySendInviteNotification( + projectId, sendingUser, - function (err) { - if (err != null) { - return callback(err) - } - CollaboratorsInviteHandler._trySendInviteNotification( - projectId, - sendingUser, - invite, - function (err) { - if (err != null) { - return callback(err) - } - callback() - } - ) - } + invite ) }, - inviteToProject(projectId, sendingUser, email, privileges, callback) { - if (callback == null) { - callback = function () {} - } + async inviteToProject(projectId, sendingUser, email, privileges) { logger.debug( { projectId, sendingUserId: sendingUser._id, email, privileges }, 'adding invite' ) - Crypto.randomBytes(24, function (err, buffer) { - if (err != null) { - OError.tag(err, 'error generating random token', { - projectId, - sendingUserId: sendingUser._id, - email, - }) - return callback(err) - } - const token = buffer.toString('hex') - const invite = new ProjectInvite({ - email, - token, - sendingUserId: sendingUser._id, - projectId, - privileges, - }) - invite.save(function (err, invite) { - if (err != null) { - OError.tag(err, 'error saving token', { - projectId, - sendingUserId: sendingUser._id, - email, - }) - return callback(err) - } - // Send email and notification in background - CollaboratorsInviteHandler._sendMessages( - projectId, - sendingUser, - invite, - function (err) { - if (err != null) { - return logger.err( - { err, projectId, email }, - 'error sending messages for invite' - ) - } - } - ) - callback(null, invite) - }) + const buffer = await randomBytes(24) + const token = buffer.toString('hex') + let invite = new ProjectInvite({ + email, + token, + sendingUserId: sendingUser._id, + projectId, + privileges, }) + invite = await invite.save() + + // Send email and notification in background + CollaboratorsInviteHandler._sendMessages( + projectId, + sendingUser, + invite + ).catch(err => { + logger.err({ err, projectId, email }, 'error sending messages for invite') + }) + + return invite }, - revokeInvite(projectId, inviteId, callback) { - if (callback == null) { - callback = function () {} - } + async revokeInvite(projectId, inviteId) { logger.debug({ projectId, inviteId }, 'removing invite') - ProjectInvite.deleteOne({ projectId, _id: inviteId }, function (err) { - if (err != null) { - OError.tag(err, 'error removing invite', { - projectId, - inviteId, - }) - return callback(err) - } - CollaboratorsInviteHandler._tryCancelInviteNotification( - inviteId, - function () {} - ) - callback(null) - }) - }, - - resendInvite(projectId, sendingUser, inviteId, callback) { - if (callback == null) { - callback = function () {} - } - logger.debug({ projectId, inviteId }, 'resending invite email') - ProjectInvite.findOne({ _id: inviteId, projectId }, function (err, invite) { - if (err != null) { - OError.tag(err, 'error finding invite', { - projectId, - inviteId, - }) - return callback(err) - } - if (invite == null) { + await ProjectInvite.deleteOne({ projectId, _id: inviteId }).exec() + CollaboratorsInviteHandler._tryCancelInviteNotification(inviteId).catch( + err => { logger.err( { err, projectId, inviteId }, - 'no invite found, nothing to resend' + 'failed to cancel invite notification' ) - return callback(null) - } - CollaboratorsInviteHandler._sendMessages( - projectId, - sendingUser, - invite, - function (err) { - if (err != null) { - OError.tag(err, 'error resending invite messages', { - projectId, - inviteId, - }) - return callback(err) - } - callback(null) - } - ) - }) - }, - - getInviteByToken(projectId, tokenString, callback) { - if (callback == null) { - callback = function () {} - } - logger.debug({ projectId }, 'fetching invite by token') - ProjectInvite.findOne( - { projectId, token: tokenString }, - function (err, invite) { - if (err != null) { - OError.tag(err, 'error fetching invite', { - projectId, - }) - return callback(err) - } - if (invite == null) { - logger.err({ err, projectId }, 'no invite found') - return callback(null, null) - } - callback(null, invite) } ) }, - acceptInvite(projectId, tokenString, user, callback) { - if (callback == null) { - callback = function () {} - } - logger.debug({ projectId, userId: user._id }, 'accepting invite') - CollaboratorsInviteHandler.getInviteByToken( + async resendInvite(projectId, sendingUser, inviteId) { + logger.debug({ projectId, inviteId }, 'resending invite email') + const invite = await ProjectInvite.findOne({ + _id: inviteId, projectId, - tokenString, - function (err, invite) { - if (err != null) { - OError.tag(err, 'error finding invite', { - projectId, - tokenString, - }) - return callback(err) - } - if (!invite) { - err = new Errors.NotFoundError('no matching invite found') - logger.debug({ err, projectId }, 'no matching invite found') - return callback(err) - } - const inviteId = invite._id - CollaboratorsHandler.addUserIdToProject( - projectId, - invite.sendingUserId, - user._id, - invite.privileges, - function (err) { - if (err != null) { - OError.tag(err, 'error adding user to project', { - projectId, - inviteId, - userId: user._id, - }) - return callback(err) - } - // Remove invite - logger.debug({ projectId, inviteId }, 'removing invite') - ProjectInvite.deleteOne({ _id: inviteId }, function (err) { - if (err != null) { - OError.tag(err, 'error removing invite', { - projectId, - inviteId, - }) - return callback(err) - } - CollaboratorsInviteHandler._tryCancelInviteNotification( - inviteId, - function () {} - ) - callback() - }) - } + }).exec() + + if (invite == null) { + logger.warn({ projectId, inviteId }, 'no invite found, nothing to resend') + return + } + + await CollaboratorsInviteHandler._sendMessages( + projectId, + sendingUser, + invite + ) + }, + + async getInviteByToken(projectId, tokenString) { + logger.debug({ projectId }, 'fetching invite by token') + const invite = await ProjectInvite.findOne({ + projectId, + token: tokenString, + }).exec() + + if (invite == null) { + logger.err({ projectId }, 'no invite found') + return null + } + + return invite + }, + + async acceptInvite(projectId, tokenString, user) { + logger.debug({ projectId, userId: user._id }, 'accepting invite') + const invite = await CollaboratorsInviteHandler.getInviteByToken( + projectId, + tokenString + ) + + if (!invite) { + throw new Errors.NotFoundError('no matching invite found') + } + const inviteId = invite._id + CollaboratorsHandler.promises.addUserIdToProject( + projectId, + invite.sendingUserId, + user._id, + invite.privileges + ) + + // Remove invite + logger.debug({ projectId, inviteId }, 'removing invite') + await ProjectInvite.deleteOne({ _id: inviteId }).exec() + CollaboratorsInviteHandler._tryCancelInviteNotification(inviteId).catch( + err => { + logger.error( + { err, projectId, inviteId }, + 'failed to cancel invite notification' ) } ) }, } -module.exports = CollaboratorsInviteHandler -module.exports.promises = promisifyAll(CollaboratorsInviteHandler) +module.exports = { + promises: CollaboratorsInviteHandler, + getAllInvites: callbackify(CollaboratorsInviteHandler.getAllInvites), + getInviteCount: callbackify(CollaboratorsInviteHandler.getInviteCount), + inviteToProject: callbackify(CollaboratorsInviteHandler.inviteToProject), + revokeInvite: callbackify(CollaboratorsInviteHandler.revokeInvite), + resendInvite: callbackify(CollaboratorsInviteHandler.resendInvite), + getInviteByToken: callbackify(CollaboratorsInviteHandler.getInviteByToken), + acceptInvite: callbackify(CollaboratorsInviteHandler.acceptInvite), + _trySendInviteNotification: callbackify( + CollaboratorsInviteHandler._trySendInviteNotification + ), + _tryCancelInviteNotification: callbackify( + CollaboratorsInviteHandler._tryCancelInviteNotification + ), + _sendMessages: callbackify(CollaboratorsInviteHandler._sendMessages), +} diff --git a/services/web/app/src/Features/Notifications/NotificationsBuilder.js b/services/web/app/src/Features/Notifications/NotificationsBuilder.js index 0887667501..09a3bfb23d 100644 --- a/services/web/app/src/Features/Notifications/NotificationsBuilder.js +++ b/services/web/app/src/Features/Notifications/NotificationsBuilder.js @@ -283,6 +283,9 @@ NotificationsBuilder.promises = { groupInvitation: function (userId, groupId, managedUsersEnabled) { return promisifyAll(groupInvitation(userId, groupId, managedUsersEnabled)) }, + projectInvite(invite, project, sendingUser, user) { + return promisifyAll(projectInvite(invite, project, sendingUser, user)) + }, } module.exports = NotificationsBuilder diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.js b/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.js index d6cc296370..86051dafff 100644 --- a/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.js +++ b/services/web/test/unit/src/Collaborators/CollaboratorsInviteHandlerTests.js @@ -1,69 +1,56 @@ -/* eslint-disable - chai-friendly/no-unused-expressions, - n/handle-callback-err, - max-len, - no-return-assign, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS206: Consider reworking classes to avoid initClass - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ const sinon = require('sinon') const { expect } = require('chai') -const modulePath = - '../../../../app/src/Features/Collaborators/CollaboratorsInviteHandler.js' const SandboxedModule = require('sandboxed-module') -const events = require('events') const { ObjectId } = require('mongodb') const Crypto = require('crypto') +const Errors = require('../../../../app/src/Features/Errors/Errors') + +const MODULE_PATH = + '../../../../app/src/Features/Collaborators/CollaboratorsInviteHandler.js' describe('CollaboratorsInviteHandler', function () { beforeEach(function () { - let ProjectInvite - this.ProjectInvite = ProjectInvite = (function () { - ProjectInvite = class ProjectInvite { - static initClass() { - this.prototype.save = sinon.stub() - this.findOne = sinon.stub() - this.find = sinon.stub() - this.deleteOne = sinon.stub() - this.countDocuments = sinon.stub() + this.ProjectInvite = class ProjectInvite { + constructor(options) { + if (options == null) { + options = {} } - - constructor(options) { - if (options == null) { - options = {} - } - this._id = ObjectId() - for (const k in options) { - const v = options[k] - this[k] = v - } - this + this._id = ObjectId() + for (const k in options) { + const v = options[k] + this[k] = v } } - ProjectInvite.initClass() - return ProjectInvite - })() - this.Crypto = Crypto - this.CollaboratorsInviteHandler = SandboxedModule.require(modulePath, { + } + this.ProjectInvite.prototype.save = sinon.stub() + this.ProjectInvite.findOne = sinon.stub() + this.ProjectInvite.find = sinon.stub() + this.ProjectInvite.deleteOne = sinon.stub() + this.ProjectInvite.countDocuments = sinon.stub() + + this.Crypto = { + randomBytes: sinon.stub().callsFake(Crypto.randomBytes), + } + this.settings = {} + this.CollaboratorsEmailHandler = { promises: {} } + this.CollaboratorsHandler = { + promises: { + addUserIdToProject: sinon.stub(), + }, + } + this.UserGetter = { promises: { getUser: sinon.stub() } } + this.ProjectGetter = { promises: {} } + this.NotificationsBuilder = { promises: {} } + + this.CollaboratorsInviteHandler = SandboxedModule.require(MODULE_PATH, { requires: { - '@overleaf/settings': (this.settings = {}), + '@overleaf/settings': this.settings, '../../models/ProjectInvite': { ProjectInvite: this.ProjectInvite }, - './CollaboratorsEmailHandler': (this.CollaboratorsEmailHandler = {}), - './CollaboratorsHandler': (this.CollaboratorsHandler = { - addUserIdToProject: sinon.stub(), - }), - '../User/UserGetter': (this.UserGetter = { getUser: sinon.stub() }), - '../Project/ProjectGetter': (this.ProjectGetter = {}), - '../Notifications/NotificationsBuilder': (this.NotificationsBuilder = - {}), + './CollaboratorsEmailHandler': this.CollaboratorsEmailHandler, + './CollaboratorsHandler': this.CollaboratorsHandler, + '../User/UserGetter': this.UserGetter, + '../Project/ProjectGetter': this.ProjectGetter, + '../Notifications/NotificationsBuilder': this.NotificationsBuilder, crypto: this.Crypto, }, }) @@ -83,7 +70,7 @@ describe('CollaboratorsInviteHandler', function () { this.inviteId = ObjectId() this.token = 'hnhteaosuhtaeosuahs' this.privileges = 'readAndWrite' - return (this.fakeInvite = { + this.fakeInvite = { _id: this.inviteId, email: this.email, token: this.token, @@ -91,48 +78,35 @@ describe('CollaboratorsInviteHandler', function () { projectId: this.projectId, privileges: this.privileges, createdAt: new Date(), - }) + } }) describe('getInviteCount', function () { beforeEach(function () { - this.ProjectInvite.countDocuments.callsArgWith(1, null, 2) - return (this.call = callback => { - return this.CollaboratorsInviteHandler.getInviteCount( - this.projectId, - callback + this.ProjectInvite.countDocuments.returns({ + exec: sinon.stub().resolves(2), + }) + this.call = async () => { + return await this.CollaboratorsInviteHandler.promises.getInviteCount( + this.projectId ) - }) + } }) - it('should not produce an error', function (done) { - return this.call((err, invites) => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) - }) - - it('should produce the count of documents', function (done) { - return this.call((err, count) => { - expect(count).to.equal(2) - return done() - }) + it('should produce the count of documents', async function () { + const count = await this.call() + expect(count).to.equal(2) }) describe('when model.countDocuments produces an error', function () { beforeEach(function () { - return this.ProjectInvite.countDocuments.callsArgWith( - 1, - new Error('woops') - ) + this.ProjectInvite.countDocuments.returns({ + exec: sinon.stub().rejects(new Error('woops')), + }) }) - it('should produce an error', function (done) { - return this.call((err, count) => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) }) }) @@ -143,477 +117,357 @@ describe('CollaboratorsInviteHandler', function () { { _id: ObjectId(), one: 1 }, { _id: ObjectId(), two: 2 }, ] - this.ProjectInvite.find.callsArgWith(1, null, this.fakeInvites) - return (this.call = callback => { - return this.CollaboratorsInviteHandler.getAllInvites( - this.projectId, - callback - ) + this.ProjectInvite.find.returns({ + exec: sinon.stub().resolves(this.fakeInvites), }) + this.call = async () => { + return await this.CollaboratorsInviteHandler.promises.getAllInvites( + this.projectId + ) + } }) describe('when all goes well', function () { beforeEach(function () {}) - it('should not produce an error', function (done) { - return this.call((err, invites) => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should produce a list of invite objects', async function () { + const invites = await this.call() + expect(invites).to.not.be.oneOf([null, undefined]) + expect(invites).to.deep.equal(this.fakeInvites) }) - it('should produce a list of invite objects', function (done) { - return this.call((err, invites) => { - expect(invites).to.not.be.oneOf([null, undefined]) - expect(invites).to.deep.equal(this.fakeInvites) - return done() - }) - }) - - it('should have called ProjectInvite.find', function (done) { - return this.call((err, invites) => { - this.ProjectInvite.find.callCount.should.equal(1) - this.ProjectInvite.find - .calledWith({ projectId: this.projectId }) - .should.equal(true) - return done() - }) + it('should have called ProjectInvite.find', async function () { + await this.call() + this.ProjectInvite.find.callCount.should.equal(1) + this.ProjectInvite.find + .calledWith({ projectId: this.projectId }) + .should.equal(true) }) }) describe('when ProjectInvite.find produces an error', function () { beforeEach(function () { - return this.ProjectInvite.find.callsArgWith(1, new Error('woops')) + this.ProjectInvite.find.returns({ + exec: sinon.stub().rejects(new Error('woops')), + }) }) - it('should produce an error', function (done) { - return this.call((err, invites) => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) }) }) describe('inviteToProject', function () { beforeEach(function () { - this.ProjectInvite.prototype.save = sinon.spy(function (cb) { - return cb(null, this) + this.ProjectInvite.prototype.save.callsFake(async function () { + return this }) - this.randomBytesSpy = sinon.spy(this.Crypto, 'randomBytes') - this.CollaboratorsInviteHandler._sendMessages = sinon + this.CollaboratorsInviteHandler.promises._sendMessages = sinon .stub() - .callsArgWith(3, null) - return (this.call = callback => { - return this.CollaboratorsInviteHandler.inviteToProject( + .resolves() + this.call = async () => { + return await this.CollaboratorsInviteHandler.promises.inviteToProject( this.projectId, this.sendingUser, this.email, - this.privileges, - callback + this.privileges ) - }) - }) - - afterEach(function () { - return this.randomBytesSpy.restore() + } }) describe('when all goes well', function () { beforeEach(function () {}) - it('should not produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should produce the invite object', async function () { + const invite = await this.call() + 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', + ]) }) - it('should produce the invite object', function (done) { - return this.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', - ]) - return done() - }) + it('should have generated a random token', async function () { + await this.call() + this.Crypto.randomBytes.callCount.should.equal(1) }) - it('should have generated a random token', function (done) { - return this.call((err, invite) => { - this.randomBytesSpy.callCount.should.equal(1) - return done() - }) + it('should have called ProjectInvite.save', async function () { + await this.call() + this.ProjectInvite.prototype.save.callCount.should.equal(1) }) - it('should have called ProjectInvite.save', function (done) { - return this.call((err, invite) => { - this.ProjectInvite.prototype.save.callCount.should.equal(1) - return done() - }) - }) - - it('should have called _sendMessages', function (done) { - return this.call((err, invite) => { - this.CollaboratorsInviteHandler._sendMessages.callCount.should.equal( - 1 - ) - this.CollaboratorsInviteHandler._sendMessages - .calledWith(this.projectId, this.sendingUser) - .should.equal(true) - return done() - }) + it('should have called _sendMessages', async function () { + await this.call() + this.CollaboratorsInviteHandler.promises._sendMessages.callCount.should.equal( + 1 + ) + this.CollaboratorsInviteHandler.promises._sendMessages + .calledWith(this.projectId, this.sendingUser) + .should.equal(true) }) }) describe('when saving model produces an error', function () { beforeEach(function () { - return (this.ProjectInvite.prototype.save = sinon.spy(function (cb) { - return cb(new Error('woops'), this) - })) + this.ProjectInvite.prototype.save.rejects(new Error('woops')) }) - it('should produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) }) }) describe('_sendMessages', function () { beforeEach(function () { - this.CollaboratorsEmailHandler.notifyUserOfProjectInvite = sinon + this.CollaboratorsEmailHandler.promises.notifyUserOfProjectInvite = sinon .stub() - .callsArgWith(4, null) - this.CollaboratorsInviteHandler._trySendInviteNotification = sinon - .stub() - .callsArgWith(3, null) - return (this.call = callback => { - return this.CollaboratorsInviteHandler._sendMessages( + .resolves() + this.CollaboratorsInviteHandler.promises._trySendInviteNotification = + sinon.stub().resolves() + this.call = async () => { + await this.CollaboratorsInviteHandler.promises._sendMessages( this.projectId, this.sendingUser, - this.fakeInvite, - callback + this.fakeInvite ) - }) + } }) describe('when all goes well', function () { - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should call CollaboratorsEmailHandler.notifyUserOfProjectInvite', async function () { + await this.call() + this.CollaboratorsEmailHandler.promises.notifyUserOfProjectInvite.callCount.should.equal( + 1 + ) + this.CollaboratorsEmailHandler.promises.notifyUserOfProjectInvite + .calledWith(this.projectId, this.fakeInvite.email, this.fakeInvite) + .should.equal(true) }) - it('should call CollaboratorsEmailHandler.notifyUserOfProjectInvite', function (done) { - return this.call(err => { - this.CollaboratorsEmailHandler.notifyUserOfProjectInvite.callCount.should.equal( - 1 - ) - this.CollaboratorsEmailHandler.notifyUserOfProjectInvite - .calledWith(this.projectId, this.fakeInvite.email, this.fakeInvite) - .should.equal(true) - return done() - }) - }) - - it('should call _trySendInviteNotification', function (done) { - return this.call(err => { - this.CollaboratorsInviteHandler._trySendInviteNotification.callCount.should.equal( - 1 - ) - this.CollaboratorsInviteHandler._trySendInviteNotification - .calledWith(this.projectId, this.sendingUser, this.fakeInvite) - .should.equal(true) - return done() - }) + it('should call _trySendInviteNotification', async function () { + await this.call() + this.CollaboratorsInviteHandler.promises._trySendInviteNotification.callCount.should.equal( + 1 + ) + this.CollaboratorsInviteHandler.promises._trySendInviteNotification + .calledWith(this.projectId, this.sendingUser, this.fakeInvite) + .should.equal(true) }) }) describe('when CollaboratorsEmailHandler.notifyUserOfProjectInvite produces an error', function () { beforeEach(function () { - return (this.CollaboratorsEmailHandler.notifyUserOfProjectInvite = sinon - .stub() - .callsArgWith(4, new Error('woops'))) + this.CollaboratorsEmailHandler.promises.notifyUserOfProjectInvite = + sinon.stub().rejects(new Error('woops')) }) - it('should produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) - it('should not call _trySendInviteNotification', function (done) { - return this.call(err => { - this.CollaboratorsInviteHandler._trySendInviteNotification.callCount.should.equal( - 0 - ) - return done() - }) + it('should not call _trySendInviteNotification', async function () { + await expect(this.call()).to.be.rejected + this.CollaboratorsInviteHandler.promises._trySendInviteNotification.callCount.should.equal( + 0 + ) }) }) describe('when _trySendInviteNotification produces an error', function () { beforeEach(function () { - return (this.CollaboratorsInviteHandler._trySendInviteNotification = - sinon.stub().callsArgWith(3, new Error('woops'))) + this.CollaboratorsInviteHandler.promises._trySendInviteNotification = + sinon.stub().rejects(new Error('woops')) }) - it('should produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) }) }) describe('revokeInvite', function () { beforeEach(function () { - this.ProjectInvite.deleteOne.callsArgWith(1, null) - this.CollaboratorsInviteHandler._tryCancelInviteNotification = sinon - .stub() - .callsArgWith(1, null) - return (this.call = callback => { - return this.CollaboratorsInviteHandler.revokeInvite( - this.projectId, - this.inviteId, - callback - ) + this.ProjectInvite.deleteOne.returns({ + exec: sinon.stub().resolves(), }) + this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification = + sinon.stub().resolves() + this.call = async () => { + await this.CollaboratorsInviteHandler.promises.revokeInvite( + this.projectId, + this.inviteId + ) + } }) describe('when all goes well', function () { beforeEach(function () {}) - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should call ProjectInvite.deleteOne', async function () { + await this.call() + this.ProjectInvite.deleteOne.callCount.should.equal(1) + this.ProjectInvite.deleteOne + .calledWith({ projectId: this.projectId, _id: this.inviteId }) + .should.equal(true) }) - it('should call ProjectInvite.deleteOne', function (done) { - return this.call(err => { - this.ProjectInvite.deleteOne.callCount.should.equal(1) - this.ProjectInvite.deleteOne - .calledWith({ projectId: this.projectId, _id: this.inviteId }) - .should.equal(true) - return done() - }) - }) - - it('should call _tryCancelInviteNotification', function (done) { - return this.call(err => { - this.CollaboratorsInviteHandler._tryCancelInviteNotification.callCount.should.equal( - 1 - ) - this.CollaboratorsInviteHandler._tryCancelInviteNotification - .calledWith(this.inviteId) - .should.equal(true) - return done() - }) + it('should call _tryCancelInviteNotification', async function () { + await this.call() + this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification.callCount.should.equal( + 1 + ) + this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification + .calledWith(this.inviteId) + .should.equal(true) }) }) describe('when remove produces an error', function () { beforeEach(function () { - return this.ProjectInvite.deleteOne.callsArgWith(1, new Error('woops')) + this.ProjectInvite.deleteOne.returns({ + exec: sinon.stub().rejects(new Error('woops')), + }) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) }) }) describe('resendInvite', function () { beforeEach(function () { - this.ProjectInvite.findOne.callsArgWith(1, null, this.fakeInvite) - this.CollaboratorsInviteHandler._sendMessages = sinon + this.ProjectInvite.findOne.returns({ + exec: sinon.stub().resolves(this.fakeInvite), + }) + this.CollaboratorsInviteHandler.promises._sendMessages = sinon .stub() - .callsArgWith(3, null) - return (this.call = callback => { - return this.CollaboratorsInviteHandler.resendInvite( + .resolves() + this.call = async () => { + await this.CollaboratorsInviteHandler.promises.resendInvite( this.projectId, this.sendingUser, - this.inviteId, - callback + this.inviteId ) - }) + } }) describe('when all goes well', function () { beforeEach(function () {}) - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should call ProjectInvite.findOne', async function () { + await this.call() + this.ProjectInvite.findOne.callCount.should.equal(1) + this.ProjectInvite.findOne + .calledWith({ _id: this.inviteId, projectId: this.projectId }) + .should.equal(true) }) - it('should call ProjectInvite.findOne', function (done) { - return this.call((err, invite) => { - this.ProjectInvite.findOne.callCount.should.equal(1) - this.ProjectInvite.findOne - .calledWith({ _id: this.inviteId, projectId: this.projectId }) - .should.equal(true) - return done() - }) - }) - - it('should have called _sendMessages', function (done) { - return this.call((err, invite) => { - this.CollaboratorsInviteHandler._sendMessages.callCount.should.equal( - 1 - ) - this.CollaboratorsInviteHandler._sendMessages - .calledWith(this.projectId, this.sendingUser, this.fakeInvite) - .should.equal(true) - return done() - }) + it('should have called _sendMessages', async function () { + await this.call() + this.CollaboratorsInviteHandler.promises._sendMessages.callCount.should.equal( + 1 + ) + this.CollaboratorsInviteHandler.promises._sendMessages + .calledWith(this.projectId, this.sendingUser, this.fakeInvite) + .should.equal(true) }) }) describe('when findOne produces an error', function () { beforeEach(function () { - return this.ProjectInvite.findOne.callsArgWith(1, new Error('woops')) - }) - - it('should produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.be.instanceof(Error) - return done() + this.ProjectInvite.findOne.returns({ + exec: sinon.stub().rejects(new Error('woops')), }) }) - it('should not have called _sendMessages', function (done) { - return this.call((err, invite) => { - this.CollaboratorsInviteHandler._sendMessages.callCount.should.equal( - 0 - ) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) + }) + + it('should not have called _sendMessages', async function () { + await expect(this.call()).to.be.rejected + this.CollaboratorsInviteHandler.promises._sendMessages.callCount.should.equal( + 0 + ) }) }) describe('when findOne does not find an invite', function () { beforeEach(function () { - return this.ProjectInvite.findOne.callsArgWith(1, null, null) - }) - - it('should not produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() + this.ProjectInvite.findOne.returns({ + exec: sinon.stub().resolves(null), }) }) - it('should not have called _sendMessages', function (done) { - return this.call((err, invite) => { - this.CollaboratorsInviteHandler._sendMessages.callCount.should.equal( - 0 - ) - return done() - }) + it('should not have called _sendMessages', async function () { + await this.call() + this.CollaboratorsInviteHandler.promises._sendMessages.callCount.should.equal( + 0 + ) }) }) }) describe('getInviteByToken', function () { beforeEach(function () { - this.ProjectInvite.findOne.callsArgWith(1, null, this.fakeInvite) - return (this.call = callback => { - return this.CollaboratorsInviteHandler.getInviteByToken( - this.projectId, - this.token, - callback - ) + this.ProjectInvite.findOne.returns({ + exec: sinon.stub().resolves(this.fakeInvite), }) + this.call = async () => { + return await this.CollaboratorsInviteHandler.promises.getInviteByToken( + this.projectId, + this.token + ) + } }) describe('when all goes well', function () { - beforeEach(function () {}) - - it('should not produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should produce the invite object', async function () { + const invite = await this.call() + expect(invite).to.deep.equal(this.fakeInvite) }) - it('should produce the invite object', function (done) { - return this.call((err, invite) => { - expect(invite).to.deep.equal(this.fakeInvite) - return done() - }) - }) - - it('should call ProjectInvite.findOne', function (done) { - return this.call((err, invite) => { - this.ProjectInvite.findOne.callCount.should.equal(1) - this.ProjectInvite.findOne - .calledWith({ projectId: this.projectId, token: this.token }) - .should.equal(true) - return done() - }) + it('should call ProjectInvite.findOne', async function () { + await this.call() + this.ProjectInvite.findOne.callCount.should.equal(1) + this.ProjectInvite.findOne + .calledWith({ projectId: this.projectId, token: this.token }) + .should.equal(true) }) }) describe('when findOne produces an error', function () { beforeEach(function () { - return this.ProjectInvite.findOne.callsArgWith(1, new Error('woops')) + this.ProjectInvite.findOne.returns({ + exec: sinon.stub().rejects(new Error('woops')), + }) }) - it('should produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) }) describe('when findOne does not find an invite', function () { beforeEach(function () { - return this.ProjectInvite.findOne.callsArgWith(1, null, null) - }) - - it('should not produce an error', function (done) { - return this.call((err, invite) => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() + this.ProjectInvite.findOne.returns({ + exec: sinon.stub().resolves(null), }) }) - it('should not produce an invite object', function (done) { - return this.call((err, invite) => { - expect(invite).to.not.be.instanceof(Error) - expect(invite).to.be.oneOf([null, undefined]) - return done() - }) + it('should not produce an invite object', async function () { + const invite = await this.call() + expect(invite).to.be.oneOf([null, undefined]) }) }) }) @@ -625,272 +479,221 @@ describe('CollaboratorsInviteHandler', function () { collaberator_refs: [], readOnly_refs: [], } - this.CollaboratorsHandler.addUserIdToProject.callsArgWith(4, null) + this.CollaboratorsHandler.promises.addUserIdToProject.resolves() this._getInviteByToken = sinon.stub( - this.CollaboratorsInviteHandler, + this.CollaboratorsInviteHandler.promises, 'getInviteByToken' ) - this._getInviteByToken.callsArgWith(2, null, this.fakeInvite) - this.CollaboratorsInviteHandler._tryCancelInviteNotification = sinon - .stub() - .callsArgWith(1, null) - this.ProjectInvite.deleteOne.callsArgWith(1, null) - return (this.call = callback => { - return this.CollaboratorsInviteHandler.acceptInvite( + this._getInviteByToken.resolves(this.fakeInvite) + this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification = + sinon.stub().resolves() + this.ProjectInvite.deleteOne.returns({ exec: sinon.stub().resolves() }) + this.call = async () => { + await this.CollaboratorsInviteHandler.promises.acceptInvite( this.projectId, this.token, - this.user, - callback + this.user ) - }) + } }) afterEach(function () { - return this._getInviteByToken.restore() + this._getInviteByToken.restore() }) describe('when all goes well', function () { - beforeEach(function () {}) - - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should have called getInviteByToken', async function () { + await this.call() + this._getInviteByToken.callCount.should.equal(1) + this._getInviteByToken + .calledWith(this.projectId, this.token) + .should.equal(true) }) - it('should have called getInviteByToken', function (done) { - return this.call(err => { - this._getInviteByToken.callCount.should.equal(1) - this._getInviteByToken - .calledWith(this.projectId, this.token) - .should.equal(true) - return done() - }) + it('should have called CollaboratorsHandler.addUserIdToProject', async function () { + await this.call() + this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal( + 1 + ) + this.CollaboratorsHandler.promises.addUserIdToProject + .calledWith( + this.projectId, + this.sendingUserId, + this.userId, + this.fakeInvite.privileges + ) + .should.equal(true) }) - it('should have called CollaboratorsHandler.addUserIdToProject', function (done) { - return this.call(err => { - this.CollaboratorsHandler.addUserIdToProject.callCount.should.equal(1) - this.CollaboratorsHandler.addUserIdToProject - .calledWith( - this.projectId, - this.sendingUserId, - this.userId, - this.fakeInvite.privileges - ) - .should.equal(true) - return done() - }) - }) - - it('should have called ProjectInvite.deleteOne', function (done) { - return this.call(err => { - this.ProjectInvite.deleteOne.callCount.should.equal(1) - this.ProjectInvite.deleteOne - .calledWith({ _id: this.inviteId }) - .should.equal(true) - return done() - }) + it('should have called ProjectInvite.deleteOne', async function () { + await this.call() + this.ProjectInvite.deleteOne.callCount.should.equal(1) + this.ProjectInvite.deleteOne + .calledWith({ _id: this.inviteId }) + .should.equal(true) }) }) describe('when the invite is for readOnly access', function () { beforeEach(function () { this.fakeInvite.privileges = 'readOnly' - return this._getInviteByToken.callsArgWith(2, null, this.fakeInvite) + this._getInviteByToken.resolves(this.fakeInvite) }) - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.not.be.instanceof(Error) - expect(err).to.be.oneOf([null, undefined]) - return done() - }) - }) - - it('should have called CollaboratorsHandler.addUserIdToProject', function (done) { - return this.call(err => { - this.CollaboratorsHandler.addUserIdToProject.callCount.should.equal(1) - this.CollaboratorsHandler.addUserIdToProject - .calledWith( - this.projectId, - this.sendingUserId, - this.userId, - this.fakeInvite.privileges - ) - .should.equal(true) - return done() - }) + it('should have called CollaboratorsHandler.addUserIdToProject', async function () { + await this.call() + this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal( + 1 + ) + this.CollaboratorsHandler.promises.addUserIdToProject + .calledWith( + this.projectId, + this.sendingUserId, + this.userId, + this.fakeInvite.privileges + ) + .should.equal(true) }) }) describe('when getInviteByToken does not find an invite', function () { beforeEach(function () { - return this._getInviteByToken.callsArgWith(2, null, null) + this._getInviteByToken.resolves(null) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - expect(err.name).to.equal('NotFoundError') - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Errors.NotFoundError) }) - it('should have called getInviteByToken', function (done) { - return this.call(err => { - this._getInviteByToken.callCount.should.equal(1) - this._getInviteByToken - .calledWith(this.projectId, this.token) - .should.equal(true) - return done() - }) + it('should have called getInviteByToken', async function () { + await expect(this.call()).to.be.rejected + this._getInviteByToken.callCount.should.equal(1) + this._getInviteByToken + .calledWith(this.projectId, this.token) + .should.equal(true) }) - it('should not have called CollaboratorsHandler.addUserIdToProject', function (done) { - return this.call(err => { - this.CollaboratorsHandler.addUserIdToProject.callCount.should.equal(0) - return done() - }) + it('should not have called CollaboratorsHandler.addUserIdToProject', async function () { + await expect(this.call()).to.be.rejected + this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal( + 0 + ) }) - it('should not have called ProjectInvite.deleteOne', function (done) { - return this.call(err => { - this.ProjectInvite.deleteOne.callCount.should.equal(0) - return done() - }) + it('should not have called ProjectInvite.deleteOne', async function () { + await expect(this.call()).to.be.rejected + this.ProjectInvite.deleteOne.callCount.should.equal(0) }) }) describe('when getInviteByToken produces an error', function () { beforeEach(function () { - return this._getInviteByToken.callsArgWith(2, new Error('woops')) + this._getInviteByToken.rejects(new Error('woops')) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) - it('should have called getInviteByToken', function (done) { - return this.call(err => { - this._getInviteByToken.callCount.should.equal(1) - this._getInviteByToken - .calledWith(this.projectId, this.token) - .should.equal(true) - return done() - }) + it('should have called getInviteByToken', async function () { + await expect(this.call()).to.be.rejected + this._getInviteByToken.callCount.should.equal(1) + this._getInviteByToken + .calledWith(this.projectId, this.token) + .should.equal(true) }) - it('should not have called CollaboratorsHandler.addUserIdToProject', function (done) { - return this.call(err => { - this.CollaboratorsHandler.addUserIdToProject.callCount.should.equal(0) - return done() - }) + it('should not have called CollaboratorsHandler.addUserIdToProject', async function () { + await expect(this.call()).to.be.rejected + this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal( + 0 + ) }) - it('should not have called ProjectInvite.deleteOne', function (done) { - return this.call(err => { - this.ProjectInvite.deleteOne.callCount.should.equal(0) - return done() - }) + it('should not have called ProjectInvite.deleteOne', async function () { + await expect(this.call()).to.be.rejected + this.ProjectInvite.deleteOne.callCount.should.equal(0) }) }) describe('when addUserIdToProject produces an error', function () { beforeEach(function () { - return this.CollaboratorsHandler.addUserIdToProject.callsArgWith( + this.CollaboratorsHandler.promises.addUserIdToProject.callsArgWith( 4, new Error('woops') ) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) - it('should have called getInviteByToken', function (done) { - return this.call(err => { - this._getInviteByToken.callCount.should.equal(1) - this._getInviteByToken - .calledWith(this.projectId, this.token) - .should.equal(true) - return done() - }) + it('should have called getInviteByToken', async function () { + await expect(this.call()).to.be.rejected + this._getInviteByToken.callCount.should.equal(1) + this._getInviteByToken + .calledWith(this.projectId, this.token) + .should.equal(true) }) - it('should have called CollaboratorsHandler.addUserIdToProject', function (done) { - return this.call(err => { - this.CollaboratorsHandler.addUserIdToProject.callCount.should.equal(1) - this.CollaboratorsHandler.addUserIdToProject - .calledWith( - this.projectId, - this.sendingUserId, - this.userId, - this.fakeInvite.privileges - ) - .should.equal(true) - return done() - }) + it('should have called CollaboratorsHandler.addUserIdToProject', async function () { + await expect(this.call()).to.be.rejected + this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal( + 1 + ) + this.CollaboratorsHandler.promises.addUserIdToProject + .calledWith( + this.projectId, + this.sendingUserId, + this.userId, + this.fakeInvite.privileges + ) + .should.equal(true) }) - it('should not have called ProjectInvite.deleteOne', function (done) { - return this.call(err => { - this.ProjectInvite.deleteOne.callCount.should.equal(0) - return done() - }) + it('should not have called ProjectInvite.deleteOne', async function () { + await expect(this.call()).to.be.rejected + this.ProjectInvite.deleteOne.callCount.should.equal(0) }) }) describe('when ProjectInvite.deleteOne produces an error', function () { beforeEach(function () { - return this.ProjectInvite.deleteOne.callsArgWith(1, new Error('woops')) - }) - - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() + this.ProjectInvite.deleteOne.returns({ + exec: sinon.stub().rejects(new Error('woops')), }) }) - it('should have called getInviteByToken', function (done) { - return this.call(err => { - this._getInviteByToken.callCount.should.equal(1) - this._getInviteByToken - .calledWith(this.projectId, this.token) - .should.equal(true) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) - it('should have called CollaboratorsHandler.addUserIdToProject', function (done) { - return this.call(err => { - this.CollaboratorsHandler.addUserIdToProject.callCount.should.equal(1) - this.CollaboratorsHandler.addUserIdToProject - .calledWith( - this.projectId, - this.sendingUserId, - this.userId, - this.fakeInvite.privileges - ) - .should.equal(true) - return done() - }) + it('should have called getInviteByToken', async function () { + await expect(this.call()).to.be.rejected + this._getInviteByToken.callCount.should.equal(1) + this._getInviteByToken + .calledWith(this.projectId, this.token) + .should.equal(true) }) - it('should have called ProjectInvite.deleteOne', function (done) { - return this.call(err => { - this.ProjectInvite.deleteOne.callCount.should.equal(1) - return done() - }) + it('should have called CollaboratorsHandler.addUserIdToProject', async function () { + await expect(this.call()).to.be.rejected + this.CollaboratorsHandler.promises.addUserIdToProject.callCount.should.equal( + 1 + ) + this.CollaboratorsHandler.promises.addUserIdToProject + .calledWith( + this.projectId, + this.sendingUserId, + this.userId, + this.fakeInvite.privileges + ) + .should.equal(true) + }) + + it('should have called ProjectInvite.deleteOne', async function () { + await expect(this.call()).to.be.rejected + this.ProjectInvite.deleteOne.callCount.should.equal(1) }) }) }) @@ -899,47 +702,34 @@ describe('CollaboratorsInviteHandler', function () { beforeEach(function () { this.inviteId = ObjectId() this.currentUser = { _id: ObjectId() } - this.notification = { read: sinon.stub().callsArgWith(0, null) } - this.NotificationsBuilder.projectInvite = sinon + this.notification = { read: sinon.stub().resolves() } + this.NotificationsBuilder.promises.projectInvite = sinon .stub() .returns(this.notification) - return (this.call = callback => { - return this.CollaboratorsInviteHandler._tryCancelInviteNotification( - this.inviteId, - callback + this.call = async () => { + await this.CollaboratorsInviteHandler.promises._tryCancelInviteNotification( + this.inviteId ) - }) + } }) - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.be.oneOf([null, undefined]) - return done() - }) - }) - - it('should call notification.read', function (done) { - return this.call(err => { - this.notification.read.callCount.should.equal(1) - return done() - }) + it('should call notification.read', async function () { + await this.call() + this.notification.read.callCount.should.equal(1) }) describe('when notification.read produces an error', function () { beforeEach(function () { this.notification = { - read: sinon.stub().callsArgWith(0, new Error('woops')), + read: sinon.stub().rejects(new Error('woops')), } - return (this.NotificationsBuilder.projectInvite = sinon + this.NotificationsBuilder.promises.projectInvite = sinon .stub() - .returns(this.notification)) + .returns(this.notification) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejected }) }) }) @@ -959,183 +749,145 @@ describe('CollaboratorsInviteHandler', function () { first_name: 'jim', } this.existingUser = { _id: ObjectId() } - this.UserGetter.getUserByAnyEmail = sinon + this.UserGetter.promises.getUserByAnyEmail = sinon .stub() - .callsArgWith(2, null, this.existingUser) + .resolves(this.existingUser) this.fakeProject = { _id: this.project_id, name: 'some project', } - this.ProjectGetter.getProject = sinon + this.ProjectGetter.promises.getProject = sinon .stub() - .callsArgWith(2, null, this.fakeProject) - this.notification = { create: sinon.stub().callsArgWith(0, null) } - this.NotificationsBuilder.projectInvite = sinon + .resolves(this.fakeProject) + this.notification = { create: sinon.stub().resolves() } + this.NotificationsBuilder.promises.projectInvite = sinon .stub() .returns(this.notification) - return (this.call = callback => { - return this.CollaboratorsInviteHandler._trySendInviteNotification( + this.call = async () => { + await this.CollaboratorsInviteHandler.promises._trySendInviteNotification( this.project_id, this.sendingUser, - this.invite, - callback + this.invite ) - }) + } }) describe('when the user exists', function () { beforeEach(function () {}) - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should call getUser', async function () { + await this.call() + this.UserGetter.promises.getUserByAnyEmail.callCount.should.equal(1) + this.UserGetter.promises.getUserByAnyEmail + .calledWith(this.invite.email) + .should.equal(true) }) - it('should call getUser', function (done) { - return this.call(err => { - this.UserGetter.getUserByAnyEmail.callCount.should.equal(1) - this.UserGetter.getUserByAnyEmail - .calledWith(this.invite.email) - .should.equal(true) - return done() - }) + it('should call getProject', async function () { + await this.call() + this.ProjectGetter.promises.getProject.callCount.should.equal(1) + this.ProjectGetter.promises.getProject + .calledWith(this.project_id) + .should.equal(true) }) - it('should call getProject', function (done) { - return this.call(err => { - this.ProjectGetter.getProject.callCount.should.equal(1) - this.ProjectGetter.getProject - .calledWith(this.project_id) - .should.equal(true) - return done() - }) - }) - - it('should call NotificationsBuilder.projectInvite.create', function (done) { - return this.call(err => { - this.NotificationsBuilder.projectInvite.callCount.should.equal(1) - this.notification.create.callCount.should.equal(1) - return done() - }) + it('should call NotificationsBuilder.projectInvite.create', async function () { + await this.call() + this.NotificationsBuilder.promises.projectInvite.callCount.should.equal( + 1 + ) + this.notification.create.callCount.should.equal(1) }) describe('when getProject produces an error', function () { beforeEach(function () { - return this.ProjectGetter.getProject.callsArgWith( + this.ProjectGetter.promises.getProject.callsArgWith( 2, new Error('woops') ) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) - it('should not call NotificationsBuilder.projectInvite.create', function (done) { - return this.call(err => { - this.NotificationsBuilder.projectInvite.callCount.should.equal(0) - this.notification.create.callCount.should.equal(0) - return done() - }) + it('should not call NotificationsBuilder.projectInvite.create', async function () { + await expect(this.call()).to.be.rejected + this.NotificationsBuilder.promises.projectInvite.callCount.should.equal( + 0 + ) + this.notification.create.callCount.should.equal(0) }) }) describe('when projectInvite.create produces an error', function () { beforeEach(function () { - return this.notification.create.callsArgWith(0, new Error('woops')) + this.notification.create.callsArgWith(0, new Error('woops')) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) }) }) describe('when the user does not exist', function () { beforeEach(function () { - return (this.UserGetter.getUserByAnyEmail = sinon - .stub() - .callsArgWith(2, null, null)) + this.UserGetter.promises.getUserByAnyEmail = sinon.stub().resolves(null) }) - it('should not produce an error', function (done) { - return this.call(err => { - expect(err).to.be.oneOf([null, undefined]) - return done() - }) + it('should call getUser', async function () { + await this.call() + this.UserGetter.promises.getUserByAnyEmail.callCount.should.equal(1) + this.UserGetter.promises.getUserByAnyEmail + .calledWith(this.invite.email) + .should.equal(true) }) - it('should call getUser', function (done) { - return this.call(err => { - this.UserGetter.getUserByAnyEmail.callCount.should.equal(1) - this.UserGetter.getUserByAnyEmail - .calledWith(this.invite.email) - .should.equal(true) - return done() - }) + it('should not call getProject', async function () { + await this.call() + this.ProjectGetter.promises.getProject.callCount.should.equal(0) }) - it('should not call getProject', function (done) { - return this.call(err => { - this.ProjectGetter.getProject.callCount.should.equal(0) - return done() - }) - }) - - it('should not call NotificationsBuilder.projectInvite.create', function (done) { - return this.call(err => { - this.NotificationsBuilder.projectInvite.callCount.should.equal(0) - this.notification.create.callCount.should.equal(0) - return done() - }) + it('should not call NotificationsBuilder.projectInvite.create', async function () { + await this.call() + this.NotificationsBuilder.promises.projectInvite.callCount.should.equal( + 0 + ) + this.notification.create.callCount.should.equal(0) }) }) describe('when the getUser produces an error', function () { beforeEach(function () { - return (this.UserGetter.getUserByAnyEmail = sinon + this.UserGetter.promises.getUserByAnyEmail = sinon .stub() - .callsArgWith(2, new Error('woops'))) + .rejects(new Error('woops')) }) - it('should produce an error', function (done) { - return this.call(err => { - expect(err).to.be.instanceof(Error) - return done() - }) + it('should produce an error', async function () { + await expect(this.call()).to.be.rejectedWith(Error) }) - it('should call getUser', function (done) { - return this.call(err => { - this.UserGetter.getUserByAnyEmail.callCount.should.equal(1) - this.UserGetter.getUserByAnyEmail - .calledWith(this.invite.email) - .should.equal(true) - return done() - }) + it('should call getUser', async function () { + await expect(this.call()).to.be.rejected + this.UserGetter.promises.getUserByAnyEmail.callCount.should.equal(1) + this.UserGetter.promises.getUserByAnyEmail + .calledWith(this.invite.email) + .should.equal(true) }) - it('should not call getProject', function (done) { - return this.call(err => { - this.ProjectGetter.getProject.callCount.should.equal(0) - return done() - }) + it('should not call getProject', async function () { + await expect(this.call()).to.be.rejected + this.ProjectGetter.promises.getProject.callCount.should.equal(0) }) - it('should not call NotificationsBuilder.projectInvite.create', function (done) { - return this.call(err => { - this.NotificationsBuilder.projectInvite.callCount.should.equal(0) - this.notification.create.callCount.should.equal(0) - return done() - }) + it('should not call NotificationsBuilder.projectInvite.create', async function () { + await expect(this.call()).to.be.rejected + this.NotificationsBuilder.promises.projectInvite.callCount.should.equal( + 0 + ) + this.notification.create.callCount.should.equal(0) }) }) })