mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-24 21:12:38 -04:00
dc706b4942
Send primary email address change notification to latest confirmed addresses GitOrigin-RevId: ba4aba38a2d8785ee24156449c612ff05cd66fc7
1059 lines
31 KiB
JavaScript
1059 lines
31 KiB
JavaScript
const SandboxedModule = require('sandboxed-module')
|
|
const path = require('path')
|
|
const sinon = require('sinon')
|
|
const modulePath = path.join(
|
|
__dirname,
|
|
'../../../../app/src/Features/User/UserUpdater'
|
|
)
|
|
const tk = require('timekeeper')
|
|
const { expect } = require('chai')
|
|
const { normalizeQuery } = require('../../../../app/src/Features/Helpers/Mongo')
|
|
|
|
describe('UserUpdater', function () {
|
|
beforeEach(function () {
|
|
tk.freeze(Date.now())
|
|
this.mongodb = {
|
|
db: {},
|
|
ObjectId(id) {
|
|
return id
|
|
},
|
|
}
|
|
this.UserGetter = {
|
|
getUserEmail: sinon.stub(),
|
|
getUserByAnyEmail: sinon.stub(),
|
|
promises: {
|
|
ensureUniqueEmailAddress: sinon.stub(),
|
|
getUser: sinon.stub(),
|
|
getUserByMainEmail: sinon.stub(),
|
|
getUserFullEmails: sinon.stub(),
|
|
},
|
|
}
|
|
this.addAffiliation = sinon.stub().yields()
|
|
this.removeAffiliation = sinon.stub().callsArgWith(2, null)
|
|
this.refreshFeatures = sinon.stub().yields()
|
|
this.NewsletterManager = {
|
|
promises: {
|
|
changeEmail: sinon.stub(),
|
|
},
|
|
}
|
|
this.RecurlyWrapper = {
|
|
promises: {
|
|
updateAccountEmailAddress: sinon.stub(),
|
|
},
|
|
}
|
|
this.AnalyticsManager = {
|
|
recordEventForUser: sinon.stub(),
|
|
}
|
|
this.UserUpdater = SandboxedModule.require(modulePath, {
|
|
requires: {
|
|
'../Helpers/Mongo': { normalizeQuery },
|
|
'../../infrastructure/mongodb': this.mongodb,
|
|
'@overleaf/metrics': {
|
|
timeAsyncMethod: sinon.stub(),
|
|
},
|
|
'./UserGetter': this.UserGetter,
|
|
'../Institutions/InstitutionsAPI': (this.InstitutionsAPI = {
|
|
addAffiliation: this.addAffiliation,
|
|
removeAffiliation: this.removeAffiliation,
|
|
promises: {
|
|
addAffiliation: sinon.stub(),
|
|
removeAffiliation: sinon.stub(),
|
|
},
|
|
}),
|
|
'../Email/EmailHandler': (this.EmailHandler = {
|
|
promises: {
|
|
sendEmail: sinon.stub(),
|
|
},
|
|
}),
|
|
'../../infrastructure/Features': (this.Features = {
|
|
hasFeature: sinon.stub().returns(false),
|
|
}),
|
|
'../Subscription/FeaturesUpdater': (this.FeaturesUpdater = {
|
|
refreshFeatures: this.refreshFeatures,
|
|
promises: {
|
|
refreshFeatures: sinon.stub().resolves(),
|
|
},
|
|
}),
|
|
'@overleaf/settings': (this.settings = {}),
|
|
request: (this.request = {}),
|
|
'../Newsletter/NewsletterManager': this.NewsletterManager,
|
|
'../Subscription/RecurlyWrapper': this.RecurlyWrapper,
|
|
'./UserAuditLogHandler': (this.UserAuditLogHandler = {
|
|
promises: {
|
|
addEntry: sinon.stub().resolves(),
|
|
},
|
|
}),
|
|
'../Analytics/AnalyticsManager': this.AnalyticsManager,
|
|
},
|
|
})
|
|
|
|
this.stubbedUserEmail = 'hello@world.com'
|
|
this.stubbedUser = {
|
|
_id: '3131231',
|
|
name: 'bob',
|
|
email: this.stubbedUserEmail,
|
|
emails: [
|
|
{
|
|
email: this.stubbedUserEmail,
|
|
},
|
|
],
|
|
}
|
|
this.newEmail = 'bob@bob.com'
|
|
this.callback = sinon.stub()
|
|
})
|
|
|
|
afterEach(function () {
|
|
return tk.reset()
|
|
})
|
|
|
|
describe('addAffiliationForNewUser', function (done) {
|
|
beforeEach(function () {
|
|
this.UserUpdater.updateUser = sinon
|
|
.stub()
|
|
.yields(null, { matchedCount: 1, modifiedCount: 1 })
|
|
})
|
|
it('should not remove affiliationUnchecked flag if v1 returns an error', function (done) {
|
|
this.addAffiliation.yields(true)
|
|
this.UserUpdater.addAffiliationForNewUser(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
(error, updated) => {
|
|
expect(error).to.exist
|
|
expect(updated).to.be.undefined
|
|
sinon.assert.notCalled(this.UserUpdater.updateUser)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
it('should remove affiliationUnchecked flag if v1 does not return an error', function (done) {
|
|
this.addAffiliation.yields()
|
|
this.UserUpdater.addAffiliationForNewUser(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
error => {
|
|
expect(error).not.to.exist
|
|
sinon.assert.calledOnce(this.UserUpdater.updateUser)
|
|
sinon.assert.calledWithMatch(
|
|
this.UserUpdater.updateUser,
|
|
{ _id: this.stubbedUser._id, 'emails.email': this.newEmail },
|
|
{ $unset: { 'emails.$.affiliationUnchecked': 1 } }
|
|
)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('changeEmailAddress', function () {
|
|
beforeEach(function () {
|
|
this.auditLog = {
|
|
initiatorId: 'abc123',
|
|
ipAddress: '0:0:0:0',
|
|
}
|
|
this.UserGetter.getUserEmail.callsArgWith(1, null, this.stubbedUser.email)
|
|
this.UserUpdater.addEmailAddress = sinon.stub().callsArgWith(4)
|
|
this.UserUpdater.setDefaultEmailAddress = sinon.stub().yields()
|
|
this.UserUpdater.removeEmailAddress = sinon.stub().callsArgWith(2)
|
|
})
|
|
|
|
it('change email', function (done) {
|
|
this.UserUpdater.changeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.UserUpdater.addEmailAddress
|
|
.calledWith(this.stubbedUser._id, this.newEmail, {}, this.auditLog)
|
|
.should.equal(true)
|
|
this.UserUpdater.setDefaultEmailAddress
|
|
.calledWith(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
true,
|
|
this.auditLog,
|
|
true
|
|
)
|
|
.should.equal(true)
|
|
this.UserUpdater.removeEmailAddress
|
|
.calledWith(this.stubbedUser._id, this.stubbedUser.email)
|
|
.should.equal(true)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('validates email', function (done) {
|
|
this.UserUpdater.changeEmailAddress(
|
|
this.stubbedUser._id,
|
|
'foo',
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle error', function (done) {
|
|
this.UserUpdater.removeEmailAddress.callsArgWith(2, new Error('nope'))
|
|
this.UserUpdater.changeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('addEmailAddress', function () {
|
|
beforeEach(function () {
|
|
this.UserGetter.promises.ensureUniqueEmailAddress = sinon
|
|
.stub()
|
|
.resolves()
|
|
this.UserUpdater.promises.updateUser = sinon.stub().resolves()
|
|
})
|
|
|
|
it('add email', function (done) {
|
|
this.UserUpdater.addEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
{},
|
|
{ initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
|
|
err => {
|
|
this.UserGetter.promises.ensureUniqueEmailAddress.called.should.equal(
|
|
true
|
|
)
|
|
expect(err).to.not.exist
|
|
const reversedHostname = this.newEmail
|
|
.split('@')[1]
|
|
.split('')
|
|
.reverse()
|
|
.join('')
|
|
this.UserUpdater.promises.updateUser
|
|
.calledWith(this.stubbedUser._id, {
|
|
$push: {
|
|
emails: {
|
|
email: this.newEmail,
|
|
createdAt: sinon.match.date,
|
|
reversedHostname,
|
|
},
|
|
},
|
|
})
|
|
.should.equal(true)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('add affiliation', function (done) {
|
|
const affiliationOptions = {
|
|
university: { id: 1 },
|
|
role: 'Prof',
|
|
department: 'Math',
|
|
}
|
|
this.UserUpdater.addEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
affiliationOptions,
|
|
{ initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.InstitutionsAPI.promises.addAffiliation.calledOnce.should.equal(
|
|
true
|
|
)
|
|
const { args } = this.InstitutionsAPI.promises.addAffiliation.lastCall
|
|
args[0].should.equal(this.stubbedUser._id)
|
|
args[1].should.equal(this.newEmail)
|
|
args[2].should.equal(affiliationOptions)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle affiliation error', function (done) {
|
|
this.InstitutionsAPI.promises.addAffiliation.rejects(new Error('nope'))
|
|
this.UserUpdater.addEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
{},
|
|
{ initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
|
|
err => {
|
|
expect(err).to.exist
|
|
this.UserUpdater.promises.updateUser.called.should.equal(false)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('validates email', function (done) {
|
|
this.UserUpdater.addEmailAddress(
|
|
this.stubbedUser._id,
|
|
'bar',
|
|
{},
|
|
{ initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('updates the audit log', function (done) {
|
|
this.ip = '127:0:0:0'
|
|
this.UserUpdater.addEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
{},
|
|
{ initiatorId: this.stubbedUser._id, ipAddress: this.ip },
|
|
error => {
|
|
expect(error).to.not.exist
|
|
this.InstitutionsAPI.promises.addAffiliation.calledOnce.should.equal(
|
|
true
|
|
)
|
|
const { args } = this.UserAuditLogHandler.promises.addEntry.lastCall
|
|
expect(args[0]).to.equal(this.stubbedUser._id)
|
|
expect(args[1]).to.equal('add-email')
|
|
expect(args[2]).to.equal(this.stubbedUser._id)
|
|
expect(args[3]).to.equal(this.ip)
|
|
expect(args[4]).to.deep.equal({
|
|
newSecondaryEmail: this.newEmail,
|
|
})
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
describe('errors', function () {
|
|
describe('via UserAuditLogHandler', function () {
|
|
const anError = new Error('oops')
|
|
beforeEach(function () {
|
|
this.UserAuditLogHandler.promises.addEntry.throws(anError)
|
|
})
|
|
it('should not add email and should return error', function (done) {
|
|
this.UserUpdater.addEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
{},
|
|
{ initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
|
|
error => {
|
|
expect(error).to.exist
|
|
expect(error).to.equal(anError)
|
|
expect(this.UserUpdater.promises.updateUser).to.not.have.been
|
|
.called
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('removeEmailAddress', function () {
|
|
beforeEach(function () {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.returns({ matchedCount: 1 })
|
|
})
|
|
|
|
it('remove email', function (done) {
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.UserUpdater.promises.updateUser
|
|
.calledWith(
|
|
{ _id: this.stubbedUser._id, email: { $ne: this.newEmail } },
|
|
{ $pull: { emails: { email: this.newEmail } } }
|
|
)
|
|
.should.equal(true)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('remove affiliation', function (done) {
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.InstitutionsAPI.promises.removeAffiliation.calledOnce.should.equal(
|
|
true
|
|
)
|
|
const { args } =
|
|
this.InstitutionsAPI.promises.removeAffiliation.lastCall
|
|
args[0].should.equal(this.stubbedUser._id)
|
|
args[1].should.equal(this.newEmail)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('refresh features', function (done) {
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
sinon.assert.calledWith(
|
|
this.FeaturesUpdater.promises.refreshFeatures,
|
|
this.stubbedUser._id
|
|
)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle error from updateUser', function (done) {
|
|
const anError = new Error('nope')
|
|
this.UserUpdater.promises.updateUser.rejects(anError)
|
|
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
expect(err).to.deep.equal(anError)
|
|
expect(err._oErrorTags[0].message).to.equal(
|
|
'problem removing users email'
|
|
)
|
|
expect(
|
|
this.FeaturesUpdater.promises.refreshFeatures.callCount
|
|
).to.equal(0)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle missed update', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.returns({ matchedCount: 0 })
|
|
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
expect(err.message).to.equal('Cannot remove email')
|
|
expect(
|
|
this.FeaturesUpdater.promises.refreshFeatures.callCount
|
|
).to.equal(0)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle affiliation error', function (done) {
|
|
const anError = new Error('nope')
|
|
this.InstitutionsAPI.promises.removeAffiliation.rejects(anError)
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
expect(err).to.deep.equal(anError)
|
|
this.UserUpdater.promises.updateUser.called.should.equal(false)
|
|
expect(
|
|
this.FeaturesUpdater.promises.refreshFeatures.callCount
|
|
).to.equal(0)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('returns error when removing primary email', function (done) {
|
|
this.UserGetter.promises.getUserByMainEmail = sinon
|
|
.stub()
|
|
.returns({ _id: '123abc' })
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
expect(err.message).to.deep.equal('cannot remove primary email')
|
|
expect(this.UserUpdater.promises.updateUser.callCount).to.equal(0)
|
|
expect(
|
|
this.FeaturesUpdater.promises.refreshFeatures.callCount
|
|
).to.equal(0)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('validates email', function (done) {
|
|
this.UserUpdater.removeEmailAddress(this.stubbedUser._id, 'baz', err => {
|
|
expect(err).to.exist
|
|
expect(err.message).to.equal('invalid email')
|
|
done()
|
|
})
|
|
})
|
|
|
|
it('skip email validation when skipParseEmail included', function (done) {
|
|
const skipParseEmail = true
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
'baz',
|
|
skipParseEmail,
|
|
err => {
|
|
expect(err).to.not.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('returns an error when skipParseEmail included but email is not a string', function (done) {
|
|
const skipParseEmail = true
|
|
this.UserUpdater.removeEmailAddress(
|
|
this.stubbedUser._id,
|
|
1,
|
|
skipParseEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
expect(err.message).to.equal('email must be a string')
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('setDefaultEmailAddress', function () {
|
|
function setStubbedUserEmails(test, emails) {
|
|
test.stubbedUser.emails = emails
|
|
test.UserGetter.promises.getUserFullEmails.resolves(
|
|
test.stubbedUser.emails
|
|
)
|
|
}
|
|
|
|
beforeEach(function () {
|
|
this.auditLog = {
|
|
initiatorId: this.stubbedUser,
|
|
ipAddress: '0:0:0:0',
|
|
}
|
|
setStubbedUserEmails(this, [
|
|
{
|
|
email: this.newEmail,
|
|
confirmedAt: new Date(),
|
|
},
|
|
])
|
|
this.UserGetter.promises.getUser.resolves(this.stubbedUser)
|
|
this.NewsletterManager.promises.changeEmail.callsArgWith(2, null)
|
|
this.RecurlyWrapper.promises.updateAccountEmailAddress.resolves()
|
|
})
|
|
|
|
it('set default', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 1 })
|
|
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.UserUpdater.promises.updateUser
|
|
.calledWith(
|
|
{ _id: this.stubbedUser._id, 'emails.email': this.newEmail },
|
|
{
|
|
$set: {
|
|
email: this.newEmail,
|
|
lastPrimaryEmailCheck: sinon.match.date,
|
|
},
|
|
}
|
|
)
|
|
.should.equal(true)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('set changed the email in newsletter', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 1 })
|
|
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.NewsletterManager.promises.changeEmail
|
|
.calledWith(this.stubbedUser, this.newEmail)
|
|
.should.equal(true)
|
|
this.RecurlyWrapper.promises.updateAccountEmailAddress
|
|
.calledWith(this.stubbedUser._id, this.newEmail)
|
|
.should.equal(true)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle error', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon.stub().rejects(Error('nope'))
|
|
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle missed update', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 0 })
|
|
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('validates email', function (done) {
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
'.edu',
|
|
false,
|
|
this.auditLog,
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('updates audit log', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 1 })
|
|
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
error => {
|
|
expect(error).to.not.exist
|
|
expect(
|
|
this.UserAuditLogHandler.promises.addEntry
|
|
).to.have.been.calledWith(
|
|
this.stubbedUser._id,
|
|
'change-primary-email',
|
|
this.auditLog.initiatorId,
|
|
this.auditLog.ipAddress,
|
|
{
|
|
newPrimaryEmail: this.newEmail,
|
|
oldPrimaryEmail: this.stubbedUser.email,
|
|
}
|
|
)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('blocks email update if audit log returns an error', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon.stub()
|
|
this.UserAuditLogHandler.promises.addEntry.rejects(new Error('oops'))
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
error => {
|
|
expect(error).to.exist
|
|
expect(this.UserUpdater.promises.updateUser).to.not.have.been.called
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
describe('when email not confirmed', function () {
|
|
beforeEach(function () {
|
|
setStubbedUserEmails(this, [
|
|
{
|
|
email: this.newEmail,
|
|
confirmedAt: null,
|
|
},
|
|
])
|
|
this.UserUpdater.promises.updateUser = sinon.stub()
|
|
})
|
|
|
|
it('should callback with error', function () {
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
error => {
|
|
expect(error).to.exist
|
|
expect(error.name).to.equal('UnconfirmedEmailError')
|
|
this.UserUpdater.promises.updateUser.callCount.should.equal(0)
|
|
this.NewsletterManager.promises.changeEmail.callCount.should.equal(
|
|
0
|
|
)
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('securityAlertPrimaryEmailChangedExtraRecipients', function () {
|
|
it('should be empty for unaffiliated user with single email', function () {
|
|
const recipients =
|
|
this.UserUpdater.securityAlertPrimaryEmailChangedExtraRecipients(
|
|
this.stubbedUser.emails,
|
|
this.stubbedUser.email,
|
|
this.newEmail
|
|
)
|
|
expect(recipients).to.have.same.members([])
|
|
})
|
|
|
|
it('should be most recently (re-)confirmed emails grouped by institution and by domain for unaffiliated emails as recipients', function () {
|
|
setStubbedUserEmails(this, [
|
|
{
|
|
email: '1@a1.uni',
|
|
confirmedAt: new Date(2020, 0, 1),
|
|
reConfirmedAt: new Date(2021, 2, 11),
|
|
lastConfirmedAt: new Date(2021, 2, 11),
|
|
default: false,
|
|
affiliation: {
|
|
institution: {
|
|
id: 123,
|
|
name: 'A1 University',
|
|
},
|
|
cachedConfirmedAt: '2020-01-01T18:25:01.639Z',
|
|
cachedReconfirmedAt: '2021-03-11T18:25:01.639Z',
|
|
},
|
|
},
|
|
{
|
|
email: '2@a1.uni',
|
|
confirmedAt: new Date(2019, 0, 1),
|
|
reConfirmedAt: new Date(2022, 2, 11),
|
|
lastConfirmedAt: new Date(2022, 2, 11),
|
|
default: false,
|
|
affiliation: {
|
|
institution: {
|
|
id: 123,
|
|
name: 'A1 University',
|
|
},
|
|
cachedConfirmedAt: '2019-01-01T18:25:01.639Z',
|
|
cachedReconfirmedAt: '2022-03-11T18:25:01.639Z',
|
|
},
|
|
},
|
|
{
|
|
email: '2020@foo.bar',
|
|
confirmedAt: new Date(2020, 6, 1),
|
|
lastConfirmedAt: new Date(2020, 6, 1),
|
|
},
|
|
{
|
|
email: '2021@foo.bar',
|
|
confirmedAt: new Date(2021, 6, 1),
|
|
lastConfirmedAt: new Date(2021, 6, 1),
|
|
},
|
|
{
|
|
email: this.stubbedUser.email,
|
|
confirmedAt: new Date(2021, 6, 1),
|
|
lastConfirmedAt: new Date(2021, 6, 1),
|
|
},
|
|
])
|
|
|
|
const recipients =
|
|
this.UserUpdater.securityAlertPrimaryEmailChangedExtraRecipients(
|
|
this.stubbedUser.emails,
|
|
this.stubbedUser.email,
|
|
this.newEmail
|
|
)
|
|
expect(recipients).to.have.same.members(['2@a1.uni', '2021@foo.bar'])
|
|
})
|
|
|
|
it('should be most recently (re-)confirmed emails grouped by institution and by domain for unaffiliated emails as recipients (multiple institutions and unaffiliated email domains)', function () {
|
|
setStubbedUserEmails(this, [
|
|
{
|
|
email: '1@a1.uni',
|
|
confirmedAt: new Date(2020, 0, 1),
|
|
reConfirmedAt: new Date(2021, 2, 11),
|
|
lastConfirmedAt: new Date(2021, 2, 11),
|
|
default: false,
|
|
affiliation: {
|
|
institution: {
|
|
id: 123,
|
|
name: 'A1 University',
|
|
},
|
|
cachedConfirmedAt: '2020-01-01T18:25:01.639Z',
|
|
cachedReconfirmedAt: '2021-03-11T18:25:01.639Z',
|
|
},
|
|
},
|
|
{
|
|
email: '1@b2.uni',
|
|
confirmedAt: new Date(2019, 0, 1),
|
|
reConfirmedAt: new Date(2022, 2, 11),
|
|
lastConfirmedAt: new Date(2022, 2, 11),
|
|
default: false,
|
|
affiliation: {
|
|
institution: {
|
|
id: 234,
|
|
name: 'B2 University',
|
|
},
|
|
cachedConfirmedAt: '2019-01-01T18:25:01.639Z',
|
|
cachedReconfirmedAt: '2022-03-11T18:25:01.639Z',
|
|
},
|
|
},
|
|
{
|
|
email: '2020@foo.bar',
|
|
confirmedAt: new Date(2020, 6, 1),
|
|
lastConfirmedAt: new Date(2020, 6, 1),
|
|
},
|
|
{
|
|
email: '2021@bar.foo',
|
|
confirmedAt: new Date(2021, 6, 1),
|
|
lastConfirmedAt: new Date(2021, 6, 1),
|
|
},
|
|
{
|
|
email: this.stubbedUser.email,
|
|
confirmedAt: new Date(2021, 6, 1),
|
|
lastConfirmedAt: new Date(2021, 6, 1),
|
|
},
|
|
])
|
|
|
|
const recipients =
|
|
this.UserUpdater.securityAlertPrimaryEmailChangedExtraRecipients(
|
|
this.stubbedUser.emails,
|
|
this.stubbedUser.email,
|
|
this.newEmail
|
|
)
|
|
expect(recipients).to.have.same.members([
|
|
'1@a1.uni',
|
|
'1@b2.uni',
|
|
'2020@foo.bar',
|
|
'2021@bar.foo',
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('when email does not belong to user', function () {
|
|
beforeEach(function () {
|
|
setStubbedUserEmails(this, [])
|
|
this.UserGetter.promises.getUser.resolves(this.stubbedUser)
|
|
this.UserUpdater.promises.updateUser = sinon.stub()
|
|
})
|
|
|
|
it('should callback with error', function () {
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
error => {
|
|
expect(error).to.exist
|
|
expect(error.name).to.equal('Error')
|
|
this.UserUpdater.promises.updateUser.callCount.should.equal(0)
|
|
this.NewsletterManager.promises.changeEmail.callCount.should.equal(
|
|
0
|
|
)
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('security alert', function () {
|
|
it('should be sent to old and new email when sendSecurityAlert=true', function (done) {
|
|
// this.UserGetter.promises.getUser.resolves(this.stubbedUser)
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 1 })
|
|
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
true,
|
|
error => {
|
|
expect(error).to.not.exist
|
|
this.EmailHandler.promises.sendEmail.callCount.should.equal(2)
|
|
const toOldEmailAlert =
|
|
this.EmailHandler.promises.sendEmail.firstCall
|
|
expect(toOldEmailAlert.args[0]).to.equal('securityAlert')
|
|
const toNewEmailAlert =
|
|
this.EmailHandler.promises.sendEmail.lastCall
|
|
expect(toOldEmailAlert.args[1].to).to.equal(this.stubbedUser.email)
|
|
expect(toNewEmailAlert.args[0]).to.equal('securityAlert')
|
|
expect(toNewEmailAlert.args[1].to).to.equal(this.newEmail)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
describe('errors', function () {
|
|
const anError = new Error('oops')
|
|
describe('EmailHandler', function () {
|
|
beforeEach(function () {
|
|
this.EmailHandler.promises.sendEmail.rejects(anError)
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 1 })
|
|
})
|
|
it('should log but not pass back the error', function (done) {
|
|
this.UserUpdater.setDefaultEmailAddress(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
false,
|
|
this.auditLog,
|
|
true,
|
|
error => {
|
|
expect(error).to.not.exist
|
|
const loggerCall = this.logger.error.firstCall
|
|
expect(loggerCall.args[0]).to.deep.equal({
|
|
error: anError,
|
|
userId: this.stubbedUser._id,
|
|
})
|
|
expect(loggerCall.args[1]).to.contain(
|
|
'could not send security alert email when primary email changed'
|
|
)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('confirmEmail', function () {
|
|
beforeEach(function () {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 1 })
|
|
})
|
|
|
|
it('should update the email record', function (done) {
|
|
this.UserUpdater.confirmEmail(
|
|
this.stubbedUser._id,
|
|
this.stubbedUserEmail,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.UserUpdater.promises.updateUser
|
|
.calledWith(
|
|
{
|
|
_id: this.stubbedUser._id,
|
|
'emails.email': this.stubbedUserEmail,
|
|
},
|
|
{
|
|
$set: {
|
|
'emails.$.reconfirmedAt': new Date(),
|
|
},
|
|
$min: {
|
|
'emails.$.confirmedAt': new Date(),
|
|
},
|
|
}
|
|
)
|
|
.should.equal(true)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('add affiliation', function (done) {
|
|
this.UserUpdater.confirmEmail(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
this.InstitutionsAPI.promises.addAffiliation.calledOnce.should.equal(
|
|
true
|
|
)
|
|
sinon.assert.calledWith(
|
|
this.InstitutionsAPI.promises.addAffiliation,
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
{ confirmedAt: new Date() }
|
|
)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle error', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.throws(new Error('nope'))
|
|
|
|
this.UserUpdater.confirmEmail(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('handle missed update', function (done) {
|
|
this.UserUpdater.promises.updateUser = sinon
|
|
.stub()
|
|
.resolves({ matchedCount: 0 })
|
|
|
|
this.UserUpdater.confirmEmail(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('validates email', function (done) {
|
|
this.UserUpdater.confirmEmail(this.stubbedUser._id, '@', err => {
|
|
expect(err).to.exist
|
|
done()
|
|
})
|
|
})
|
|
|
|
it('handle affiliation error', function (done) {
|
|
this.InstitutionsAPI.promises.addAffiliation.throws(Error('nope'))
|
|
this.UserUpdater.confirmEmail(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).to.exist
|
|
this.UserUpdater.promises.updateUser.called.should.equal(false)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
|
|
it('refresh features', function (done) {
|
|
this.UserUpdater.confirmEmail(
|
|
this.stubbedUser._id,
|
|
this.newEmail,
|
|
err => {
|
|
expect(err).not.to.exist
|
|
sinon.assert.calledWith(
|
|
this.FeaturesUpdater.promises.refreshFeatures,
|
|
this.stubbedUser._id
|
|
)
|
|
done()
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|