Merge pull request #15515 from overleaf/em-promisify-user-controller

Promisify UserController

GitOrigin-RevId: d56ddb32abff2c33f45efa58285c7bf02b578cd2
This commit is contained in:
Eric Mc Sween 2023-11-02 08:47:16 -04:00 committed by Copybot
parent 2bbead57ec
commit 75c7a58100
4 changed files with 376 additions and 408 deletions

View file

@ -16,7 +16,7 @@ const HttpErrorHandler = require('../Errors/HttpErrorHandler')
const OError = require('@overleaf/o-error')
const EmailHandler = require('../Email/EmailHandler')
const UrlHelper = require('../Helpers/UrlHelper')
const { promisify } = require('util')
const { promisify, callbackify } = require('util')
const { expressify } = require('@overleaf/promise-utils')
const {
acceptsJson,
@ -46,14 +46,14 @@ function _sendSecurityAlertPasswordChanged(user) {
actionDescribed: `your password has been changed on your account ${user.email}`,
action: 'password changed',
}
EmailHandler.sendEmail('securityAlert', emailOptions, error => {
if (error) {
EmailHandler.promises
.sendEmail('securityAlert', emailOptions)
.catch(error => {
// log error when sending security alert email but do not pass back
logger.error(
{ error, userId: user._id },
'could not send security alert email when password changed'
)
}
})
}
@ -210,43 +210,31 @@ async function ensureAffiliationMiddleware(req, res, next) {
return next()
}
const UserController = {
clearSessions: expressify(clearSessions),
tryDeleteUser(req, res, next) {
async function tryDeleteUser(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
const { password } = req.body
req.logger.addFields({ userId })
if (password == null || password === '') {
logger.err(
{ userId },
'no password supplied for attempt to delete account'
)
logger.err({ userId }, 'no password supplied for attempt to delete account')
return res.sendStatus(403)
}
AuthenticationManager.authenticate(
const user = await AuthenticationManager.promises.authenticate(
{ _id: userId },
password,
(err, user) => {
if (err != null) {
OError.tag(
err,
'error authenticating during attempt to delete account',
{
userId,
}
password
)
return next(err)
}
if (!user) {
logger.err({ userId }, 'auth failed during attempt to delete account')
return res.sendStatus(403)
}
UserDeleter.deleteUser(
userId,
{ deleterUser: user, ipAddress: req.ip },
err => {
if (err) {
try {
await UserDeleter.promises.deleteUser(userId, {
deleterUser: user,
ipAddress: req.ip,
})
} catch (err) {
const errorData = {
message: 'error while deleting user account',
info: { userId },
@ -265,83 +253,65 @@ const UserController = {
errorData.info.public
)
} else {
return next(OError.tag(err, errorData.message, errorData.info))
throw err
}
}
const sessionId = req.sessionID
if (typeof req.logout === 'function') {
req.logout()
}
req.session.destroy(err => {
if (err != null) {
OError.tag(err, 'error destroying session')
return next(err)
}
UserSessionsManager.untrackSession(user, sessionId, () => {})
const destroySession = promisify(req.session.destroy.bind(req.session))
await destroySession()
UserSessionsManager.promises.untrackSession(user, sessionId).catch(err => {
logger.warn({ err, userId: user._id }, 'failed to untrack session')
})
res.sendStatus(200)
})
}
)
}
)
},
subscribe(req, res, next) {
async function subscribe(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
UserGetter.getUser(
userId,
{ _id: 1, email: 1, first_name: 1, last_name: 1 },
(err, user) => {
if (err != null) {
return next(err)
}
NewsletterManager.subscribe(user, err => {
if (err != null) {
OError.tag(err, 'error subscribing to newsletter')
return next(err)
}
return res.json({
req.logger.addFields({ userId })
const user = await UserGetter.promises.getUser(userId, {
_id: 1,
email: 1,
first_name: 1,
last_name: 1,
})
await NewsletterManager.promises.subscribe(user)
res.json({
message: req.i18n.translate('thanks_settings_updated'),
})
})
}
)
},
unsubscribe(req, res, next) {
async function unsubscribe(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
UserGetter.getUser(
userId,
{ _id: 1, email: 1, first_name: 1, last_name: 1 },
(err, user) => {
if (err != null) {
return next(err)
}
NewsletterManager.unsubscribe(user, err => {
if (err != null) {
OError.tag(err, 'error unsubscribing to newsletter')
return next(err)
}
Modules.hooks.fire('newsletterUnsubscribed', user, err => {
if (err) {
OError.tag(err, 'error firing "newsletterUnsubscribed" hook')
return next(err)
}
return res.json({
req.logger.addFields({ userId })
const user = await UserGetter.promises.getUser(userId, {
_id: 1,
email: 1,
first_name: 1,
last_name: 1,
})
await NewsletterManager.promises.unsubscribe(user)
await Modules.promises.hooks.fire('newsletterUnsubscribed', user)
res.json({
message: req.i18n.translate('thanks_settings_updated'),
})
})
})
}
)
},
updateUserSettings(req, res, next) {
async function updateUserSettings(req, res, next) {
const userId = SessionManager.getLoggedInUserId(req.session)
User.findById(userId, (err, user) => {
if (err != null || user == null) {
logger.err({ err, userId }, 'problem updaing user settings')
return res.sendStatus(500)
req.logger.addFields({ userId })
const user = await User.findById(userId).exec()
if (user == null) {
throw new OError('problem updating user settings', { userId })
}
if (req.body.first_name != null) {
@ -389,15 +359,9 @@ const UserController = {
if (req.body.lineHeight != null) {
user.ace.lineHeight = req.body.lineHeight
}
await user.save()
user.save(err => {
if (err != null) {
return next(err)
}
const newEmail =
req.body.email != null
? req.body.email.trim().toLowerCase()
: undefined
const newEmail = req.body.email?.trim().toLowerCase()
if (
newEmail == null ||
newEmail === user.email ||
@ -418,12 +382,12 @@ const UserController = {
initiatorId: userId,
ipAddress: req.ip,
}
UserUpdater.changeEmailAddress(userId, newEmail, auditLog, err => {
if (err) {
try {
await UserUpdater.promises.changeEmailAddress(userId, newEmail, auditLog)
} catch (err) {
if (err instanceof Errors.EmailExistsError) {
const translation = req.i18n.translate(
'email_already_registered'
)
const translation = req.i18n.translate('email_already_registered')
return HttpErrorHandler.conflict(req, res, translation)
} else {
return HttpErrorHandler.legacyInternal(
@ -437,99 +401,85 @@ const UserController = {
)
}
}
User.findById(userId, (err, user) => {
if (err != null) {
logger.err(
{ err, userId },
'error getting user for email update'
)
return res.sendStatus(500)
}
const user = await User.findById(userId).exec()
SessionManager.setInSessionUser(req.session, {
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
})
UserHandler.populateTeamInvites(user, err => {
// need to refresh this in the background
if (err != null) {
logger.err({ err }, 'error populateTeamInvites')
}
res.sendStatus(200)
})
})
})
}
})
})
},
doLogout(req, cb) {
try {
await UserHandler.promises.populateTeamInvites(user)
} catch (err) {
logger.error({ err }, 'error populateTeamInvites')
}
res.sendStatus(200)
}
}
async function doLogout(req) {
metrics.inc('user.logout')
const user = SessionManager.getSessionUser(req.session)
logger.debug({ user }, 'logging out')
const sessionId = req.sessionID
// passport logout
if (typeof req.logout === 'function') {
req.logout()
} // passport logout
req.session.destroy(err => {
if (err) {
OError.tag(err, 'error destroying session')
return cb(err)
}
if (user != null) {
UserSessionsManager.untrackSession(user, sessionId, () => {})
}
cb()
})
},
logout(req, res, next) {
const destroySession = promisify(req.session.destroy.bind(req.session))
await destroySession()
if (user != null) {
UserSessionsManager.promises.untrackSession(user, sessionId).catch(err => {
logger.warn({ err, userId: user._id }, 'failed to untrack session')
})
}
}
async function logout(req, res, next) {
const requestedRedirect = req.body.redirect
? UrlHelper.getSafeRedirectPath(req.body.redirect)
: undefined
const redirectUrl = requestedRedirect || '/login'
UserController.doLogout(req, err => {
if (err != null) {
return next(err)
}
await doLogout(req)
if (acceptsJson(req)) {
res.status(200).json({ redir: redirectUrl })
} else {
res.redirect(redirectUrl)
}
})
},
}
expireDeletedUser(req, res, next) {
async function expireDeletedUser(req, res, next) {
const userId = req.params.userId
UserDeleter.expireDeletedUser(userId, error => {
if (error) {
return next(error)
await UserDeleter.promises.expireDeletedUser(userId)
res.sendStatus(204)
}
async function expireDeletedUsersAfterDuration(req, res, next) {
await UserDeleter.promises.expireDeletedUsersAfterDuration()
res.sendStatus(204)
})
},
expireDeletedUsersAfterDuration(req, res, next) {
UserDeleter.expireDeletedUsersAfterDuration(error => {
if (error) {
return next(error)
}
res.sendStatus(204)
})
},
module.exports = {
clearSessions: expressify(clearSessions),
changePassword: expressify(changePassword),
}
UserController.promises = {
doLogout: promisify(UserController.doLogout),
tryDeleteUser: expressify(tryDeleteUser),
subscribe: expressify(subscribe),
unsubscribe: expressify(unsubscribe),
updateUserSettings: expressify(updateUserSettings),
doLogout: callbackify(doLogout),
logout: expressify(logout),
expireDeletedUser: expressify(expireDeletedUser),
expireDeletedUsersAfterDuration: expressify(expireDeletedUsersAfterDuration),
promises: {
doLogout,
ensureAffiliation,
ensureAffiliationMiddleware,
},
}
module.exports = UserController

View file

@ -1,3 +1,4 @@
const { promisifyAll } = require('@overleaf/promise-utils')
const TeamInvitesHandler = require('../Subscription/TeamInvitesHandler')
const UserHandler = {
@ -13,3 +14,4 @@ const UserHandler = {
},
}
module.exports = UserHandler
module.exports.promises = promisifyAll(UserHandler)

View file

@ -243,6 +243,7 @@ const UserSessionsManager = {
UserSessionsManager.promises = {
getAllUserSessions: promisify(UserSessionsManager.getAllUserSessions),
revokeAllUserSessions: promisify(UserSessionsManager.revokeAllUserSessions),
untrackSession: promisify(UserSessionsManager.untrackSession),
}
module.exports = UserSessionsManager

View file

@ -12,7 +12,7 @@ describe('UserController', function () {
this.user = {
_id: this.user_id,
email: 'email@overleaf.com',
save: sinon.stub().callsArgWith(0),
save: sinon.stub().resolves(),
ace: {},
}
@ -34,29 +34,37 @@ describe('UserController', function () {
ip: '0:0:0:0',
query: {},
headers: {},
logger: {
addFields: sinon.stub(),
},
}
this.UserDeleter = { deleteUser: sinon.stub().yields() }
this.UserDeleter = { promises: { deleteUser: sinon.stub().resolves() } }
this.UserGetter = {
getUser: sinon.stub().yields(null, this.user),
promises: { getUser: sinon.stub().resolves(this.user) },
}
this.User = { findById: sinon.stub().yields(null, this.user) }
this.User = {
findById: sinon
.stub()
.returns({ exec: sinon.stub().resolves(this.user) }),
}
this.NewsLetterManager = {
subscribe: sinon.stub().yields(),
unsubscribe: sinon.stub().yields(),
}
this.AuthenticationController = {
establishUserSession: sinon.stub().callsArg(2),
promises: {
subscribe: sinon.stub().resolves(),
unsubscribe: sinon.stub().resolves(),
},
}
this.SessionManager = {
getLoggedInUserId: sinon.stub().returns(this.user._id),
getSessionUser: sinon.stub().returns(this.req.session.user),
setInSessionUser: sinon.stub(),
}
this.AuthenticationManager = {
authenticate: sinon.stub(),
validatePassword: sinon.stub(),
promises: {
authenticate: sinon.stub(),
setUserPassword: sinon.stub(),
@ -65,24 +73,29 @@ describe('UserController', function () {
.stub()
.returns({ type: 'error', key: 'some-key' }),
}
this.UserUpdater = {
changeEmailAddress: sinon.stub(),
promises: {
changeEmailAddress: sinon.stub().resolves(),
confirmEmail: sinon.stub().resolves(),
addAffiliationForNewUser: sinon.stub().resolves(),
},
}
this.settings = { siteUrl: 'sharelatex.example.com' }
this.UserHandler = { populateTeamInvites: sinon.stub().callsArgWith(1) }
this.UserHandler = {
promises: { populateTeamInvites: sinon.stub().resolves() },
}
this.UserSessionsManager = {
trackSession: sinon.stub(),
untrackSession: sinon.stub(),
revokeAllUserSessions: sinon.stub().callsArgWith(2, null),
promises: {
getAllUserSessions: sinon.stub().resolves(),
revokeAllUserSessions: sinon.stub().resolves(),
untrackSession: sinon.stub().resolves(),
},
}
this.HttpErrorHandler = {
badRequest: sinon.stub(),
conflict: sinon.stub(),
@ -97,7 +110,24 @@ describe('UserController', function () {
.withArgs('https://evil.com')
.returns(undefined)
this.UrlHelper.getSafeRedirectPath.returnsArg(0)
this.acceptsJson = sinon.stub().returns(false)
this.Features = {
hasFeature: sinon.stub(),
}
this.UserAuditLogHandler = {
promises: {
addEntry: sinon.stub().resolves(),
},
}
this.RequestContentTypeDetection = {
acceptsJson: sinon.stub().returns(false),
}
this.EmailHandler = {
promises: { sendEmail: sinon.stub().resolves() },
}
this.UserController = SandboxedModule.require(modulePath, {
requires: {
@ -105,37 +135,22 @@ describe('UserController', function () {
'./UserGetter': this.UserGetter,
'./UserDeleter': this.UserDeleter,
'./UserUpdater': this.UserUpdater,
'../../models/User': {
User: this.User,
},
'../../models/User': { User: this.User },
'../Newsletter/NewsletterManager': this.NewsLetterManager,
'../Authentication/AuthenticationController':
this.AuthenticationController,
'../Authentication/SessionManager': this.SessionManager,
'../Authentication/AuthenticationManager': this.AuthenticationManager,
'../../infrastructure/Features': (this.Features = {
hasFeature: sinon.stub(),
}),
'./UserAuditLogHandler': (this.UserAuditLogHandler = {
promises: {
addEntry: sinon.stub().resolves(),
},
}),
'../../infrastructure/Features': this.Features,
'./UserAuditLogHandler': this.UserAuditLogHandler,
'./UserHandler': this.UserHandler,
'./UserSessionsManager': this.UserSessionsManager,
'../Errors/HttpErrorHandler': this.HttpErrorHandler,
'@overleaf/settings': this.settings,
'@overleaf/metrics': {
inc() {},
},
'@overleaf/o-error': OError,
'../Email/EmailHandler': (this.EmailHandler = {
sendEmail: sinon.stub(),
promises: { sendEmail: sinon.stub().resolves() },
}),
'../../infrastructure/RequestContentTypeDetection': {
acceptsJson: this.acceptsJson,
},
'../Email/EmailHandler': this.EmailHandler,
'../../infrastructure/RequestContentTypeDetection':
this.RequestContentTypeDetection,
},
})
@ -154,13 +169,11 @@ describe('UserController', function () {
beforeEach(function () {
this.req.body.password = 'wat'
this.req.logout = sinon.stub()
this.req.session.destroy = sinon.stub().callsArgWith(0, null)
this.req.session.destroy = sinon.stub().yields()
this.SessionManager.getLoggedInUserId = sinon
.stub()
.returns(this.user._id)
this.AuthenticationManager.authenticate = sinon
.stub()
.callsArgWith(2, null, this.user)
this.AuthenticationManager.promises.authenticate.resolves(this.user)
})
it('should send 200', function (done) {
@ -173,10 +186,12 @@ describe('UserController', function () {
it('should try to authenticate user', function (done) {
this.res.sendStatus = code => {
this.AuthenticationManager.authenticate.callCount.should.equal(1)
this.AuthenticationManager.authenticate
.calledWith({ _id: this.user._id }, this.req.body.password)
.should.equal(true)
this.AuthenticationManager.promises.authenticate.should.have.been
.calledOnce
this.AuthenticationManager.promises.authenticate.should.have.been.calledWith(
{ _id: this.user._id },
this.req.body.password
)
done()
}
this.UserController.tryDeleteUser(this.req, this.res, this.next)
@ -184,8 +199,10 @@ describe('UserController', function () {
it('should delete the user', function (done) {
this.res.sendStatus = code => {
this.UserDeleter.deleteUser.callCount.should.equal(1)
this.UserDeleter.deleteUser.calledWith(this.user._id).should.equal(true)
this.UserDeleter.promises.deleteUser.should.have.been.calledOnce
this.UserDeleter.promises.deleteUser.should.have.been.calledWith(
this.user._id
)
done()
}
this.UserController.tryDeleteUser(this.req, this.res, this.next)
@ -207,9 +224,9 @@ describe('UserController', function () {
describe('when authenticate produces an error', function () {
beforeEach(function () {
this.AuthenticationManager.authenticate = sinon
.stub()
.callsArgWith(2, new Error('woops'))
this.AuthenticationManager.promises.authenticate.rejects(
new Error('woops')
)
})
it('should call next with an error', function (done) {
@ -224,9 +241,7 @@ describe('UserController', function () {
describe('when authenticate does not produce a user', function () {
beforeEach(function () {
this.AuthenticationManager.authenticate = sinon
.stub()
.callsArgWith(2, null, null)
this.AuthenticationManager.promises.authenticate.resolves(null)
})
it('should return 403', function (done) {
@ -240,7 +255,7 @@ describe('UserController', function () {
describe('when deleteUser produces an error', function () {
beforeEach(function () {
this.UserDeleter.deleteUser = sinon.stub().yields(new Error('woops'))
this.UserDeleter.promises.deleteUser.rejects(new Error('woops'))
})
it('should call next with an error', function (done) {
@ -255,9 +270,9 @@ describe('UserController', function () {
describe('when deleteUser produces a known error', function () {
beforeEach(function () {
this.UserDeleter.deleteUser = sinon
.stub()
.yields(new Errors.SubscriptionAdminDeletionError())
this.UserDeleter.promises.deleteUser.rejects(
new Errors.SubscriptionAdminDeletionError()
)
})
it('should return a HTTP Unprocessable Entity error', function (done) {
@ -298,9 +313,9 @@ describe('UserController', function () {
it('should send the user to subscribe', function (done) {
this.res.json = data => {
expect(data.message).to.equal('thanks_settings_updated')
this.NewsLetterManager.subscribe
.calledWith(this.user)
.should.equal(true)
this.NewsLetterManager.promises.subscribe.should.have.been.calledWith(
this.user
)
done()
}
this.UserController.subscribe(this.req, this.res)
@ -311,12 +326,12 @@ describe('UserController', function () {
it('should send the user to unsubscribe', function (done) {
this.res.json = data => {
expect(data.message).to.equal('thanks_settings_updated')
this.NewsLetterManager.unsubscribe
.calledWith(this.user)
.should.equal(true)
this.NewsLetterManager.promises.unsubscribe.should.have.been.calledWith(
this.user
)
done()
}
this.UserController.unsubscribe(this.req, this.res)
this.UserController.unsubscribe(this.req, this.res, this.next)
})
})
@ -333,7 +348,7 @@ describe('UserController', function () {
this.user.save.called.should.equal(true)
done()
}
this.UserController.updateUserSettings(this.req, this.res)
this.UserController.updateUserSettings(this.req, this.res, this.next)
})
it('should set the first name', function (done) {
@ -401,12 +416,13 @@ describe('UserController', function () {
it('should call the user updater with the new email and user _id', function (done) {
this.req.body.email = this.newEmail.toUpperCase()
this.UserUpdater.changeEmailAddress.callsArgWith(3)
this.res.sendStatus = code => {
code.should.equal(200)
this.UserUpdater.changeEmailAddress
.calledWith(this.user_id, this.newEmail, this.auditLog)
.should.equal(true)
this.UserUpdater.promises.changeEmailAddress.should.have.been.calledWith(
this.user_id,
this.newEmail,
this.auditLog
)
done()
}
this.UserController.updateUserSettings(this.req, this.res)
@ -414,14 +430,15 @@ describe('UserController', function () {
it('should update the email on the session', function (done) {
this.req.body.email = this.newEmail.toUpperCase()
this.UserUpdater.changeEmailAddress.callsArgWith(3)
let callcount = 0
this.User.findById = (id, cb) => {
this.User.findById = id => ({
exec: async () => {
if (++callcount === 2) {
this.user.email = this.newEmail
}
cb(null, this.user)
}
return this.user
},
})
this.res.sendStatus = code => {
code.should.equal(200)
this.SessionManager.setInSessionUser
@ -438,12 +455,11 @@ describe('UserController', function () {
it('should call populateTeamInvites', function (done) {
this.req.body.email = this.newEmail.toUpperCase()
this.UserUpdater.changeEmailAddress.callsArgWith(3)
this.res.sendStatus = code => {
code.should.equal(200)
this.UserHandler.populateTeamInvites
.calledWith(this.user)
.should.equal(true)
this.UserHandler.promises.populateTeamInvites.should.have.been.calledWith(
this.user
)
done()
}
this.UserController.updateUserSettings(this.req, this.res)
@ -452,7 +468,7 @@ describe('UserController', function () {
describe('when changeEmailAddress yields an error', function () {
it('should pass on an error and not send a success status', function (done) {
this.req.body.email = this.newEmail.toUpperCase()
this.UserUpdater.changeEmailAddress.callsArgWith(3, new OError())
this.UserUpdater.promises.changeEmailAddress.rejects(new OError())
this.HttpErrorHandler.legacyInternal = sinon.spy(
(req, res, message, error) => {
expect(req).to.exist
@ -473,8 +489,7 @@ describe('UserController', function () {
done()
})
this.req.body.email = this.newEmail.toUpperCase()
this.UserUpdater.changeEmailAddress.callsArgWith(
3,
this.UserUpdater.promises.changeEmailAddress.rejects(
new Errors.EmailExistsError()
)
this.UserController.updateUserSettings(this.req, this.res)
@ -483,7 +498,6 @@ describe('UserController', function () {
describe('when using an external auth source', function () {
beforeEach(function () {
this.UserUpdater.changeEmailAddress.callsArgWith(2)
this.newEmail = 'someone23@example.com'
this.req.externalAuthenticationSystemUsed = sinon.stub().returns(true)
})
@ -492,7 +506,7 @@ describe('UserController', function () {
this.req.body.email = this.newEmail
this.res.sendStatus = code => {
code.should.equal(200)
this.UserUpdater.changeEmailAddress
this.UserUpdater.promises.changeEmailAddress
.calledWith(this.user_id, this.newEmail)
.should.equal(false)
done()
@ -504,7 +518,7 @@ describe('UserController', function () {
describe('logout', function () {
beforeEach(function () {
this.acceptsJson.returns(false)
this.RequestContentTypeDetection.acceptsJson.returns(false)
})
it('should destroy the session', function (done) {
@ -522,10 +536,12 @@ describe('UserController', function () {
this.req.session.destroy = sinon.stub().callsArgWith(0)
this.res.redirect = url => {
url.should.equal('/login')
this.UserSessionsManager.untrackSession.callCount.should.equal(1)
this.UserSessionsManager.untrackSession
.calledWith(sinon.match(this.req.user), this.req.sessionID)
.should.equal(true)
this.UserSessionsManager.promises.untrackSession.should.have.been
.calledOnce
this.UserSessionsManager.promises.untrackSession.should.have.been.calledWith(
sinon.match(this.req.user),
this.req.sessionID
)
done()
}
@ -562,7 +578,7 @@ describe('UserController', function () {
})
it('should send json with redir property for json request', function (done) {
this.acceptsJson.returns(true)
this.RequestContentTypeDetection.acceptsJson.returns(true)
this.req.session.destroy = sinon.stub().callsArgWith(0)
this.res.status = code => {
code.should.equal(200)
@ -580,9 +596,8 @@ describe('UserController', function () {
describe('success', function () {
it('should call revokeAllUserSessions', function (done) {
this.res.sendStatus.callsFake(() => {
this.UserSessionsManager.promises.revokeAllUserSessions.callCount.should.equal(
1
)
this.UserSessionsManager.promises.revokeAllUserSessions.should.have
.been.calledOnce
done()
})
this.UserController.clearSessions(this.req, this.res)
@ -717,7 +732,7 @@ describe('UserController', function () {
actionDescribed: `your password has been changed on your account ${this.user.email}`,
action: 'password changed',
}
const emailCall = this.EmailHandler.sendEmail.lastCall
const emailCall = this.EmailHandler.promises.sendEmail.lastCall
expect(emailCall.args[0]).to.equal('securityAlert')
expect(emailCall.args[1]).to.deep.equal(expectedArg)
done()
@ -826,7 +841,7 @@ describe('UserController', function () {
newPassword1: 'newpass',
newPassword2: 'newpass',
}
this.EmailHandler.sendEmail.yields(anError)
this.EmailHandler.promises.sendEmail.rejects(anError)
})
it('should not return error but should log it', function (done) {
this.res.json.callsFake(result => {