UserUpdaterTests.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. const SandboxedModule = require('sandboxed-module')
  2. const path = require('path')
  3. const sinon = require('sinon')
  4. const modulePath = path.join(
  5. __dirname,
  6. '../../../../app/src/Features/User/UserUpdater'
  7. )
  8. const tk = require('timekeeper')
  9. const { expect } = require('chai')
  10. const { normalizeQuery } = require('../../../../app/src/Features/Helpers/Mongo')
  11. describe('UserUpdater', function () {
  12. beforeEach(function () {
  13. tk.freeze(Date.now())
  14. this.mongodb = {
  15. db: {},
  16. ObjectId(id) {
  17. return id
  18. },
  19. }
  20. this.UserGetter = {
  21. getUserEmail: sinon.stub(),
  22. getUserByAnyEmail: sinon.stub(),
  23. promises: {
  24. ensureUniqueEmailAddress: sinon.stub(),
  25. getUser: sinon.stub(),
  26. getUserByMainEmail: sinon.stub(),
  27. },
  28. }
  29. this.addAffiliation = sinon.stub().yields()
  30. this.removeAffiliation = sinon.stub().callsArgWith(2, null)
  31. this.refreshFeatures = sinon.stub().yields()
  32. this.NewsletterManager = {
  33. promises: {
  34. changeEmail: sinon.stub(),
  35. },
  36. }
  37. this.RecurlyWrapper = {
  38. promises: {
  39. updateAccountEmailAddress: sinon.stub(),
  40. },
  41. }
  42. this.AnalyticsManager = {
  43. recordEventForUser: sinon.stub(),
  44. }
  45. this.UserUpdater = SandboxedModule.require(modulePath, {
  46. requires: {
  47. '../Helpers/Mongo': { normalizeQuery },
  48. '../../infrastructure/mongodb': this.mongodb,
  49. '@overleaf/metrics': {
  50. timeAsyncMethod: sinon.stub(),
  51. },
  52. './UserGetter': this.UserGetter,
  53. '../Institutions/InstitutionsAPI': (this.InstitutionsAPI = {
  54. addAffiliation: this.addAffiliation,
  55. removeAffiliation: this.removeAffiliation,
  56. promises: {
  57. addAffiliation: sinon.stub(),
  58. removeAffiliation: sinon.stub(),
  59. },
  60. }),
  61. '../Email/EmailHandler': (this.EmailHandler = {
  62. promises: {
  63. sendEmail: sinon.stub(),
  64. },
  65. }),
  66. '../../infrastructure/Features': (this.Features = {
  67. hasFeature: sinon.stub().returns(false),
  68. }),
  69. '../Subscription/FeaturesUpdater': (this.FeaturesUpdater = {
  70. refreshFeatures: this.refreshFeatures,
  71. promises: {
  72. refreshFeatures: sinon.stub().resolves(),
  73. },
  74. }),
  75. '@overleaf/settings': (this.settings = {}),
  76. request: (this.request = {}),
  77. '../Newsletter/NewsletterManager': this.NewsletterManager,
  78. '../Subscription/RecurlyWrapper': this.RecurlyWrapper,
  79. './UserAuditLogHandler': (this.UserAuditLogHandler = {
  80. promises: {
  81. addEntry: sinon.stub().resolves(),
  82. },
  83. }),
  84. '../Analytics/AnalyticsManager': this.AnalyticsManager,
  85. },
  86. })
  87. this.stubbedUserEmail = 'hello@world.com'
  88. this.stubbedUser = {
  89. _id: '3131231',
  90. name: 'bob',
  91. email: this.stubbedUserEmail,
  92. emails: [
  93. {
  94. email: this.stubbedUserEmail,
  95. },
  96. ],
  97. }
  98. this.newEmail = 'bob@bob.com'
  99. this.callback = sinon.stub()
  100. })
  101. afterEach(function () {
  102. return tk.reset()
  103. })
  104. describe('addAffiliationForNewUser', function (done) {
  105. beforeEach(function () {
  106. this.UserUpdater.updateUser = sinon
  107. .stub()
  108. .yields(null, { matchedCount: 1, modifiedCount: 1 })
  109. })
  110. it('should not remove affiliationUnchecked flag if v1 returns an error', function (done) {
  111. this.addAffiliation.yields(true)
  112. this.UserUpdater.addAffiliationForNewUser(
  113. this.stubbedUser._id,
  114. this.newEmail,
  115. (error, updated) => {
  116. expect(error).to.exist
  117. expect(updated).to.be.undefined
  118. sinon.assert.notCalled(this.UserUpdater.updateUser)
  119. done()
  120. }
  121. )
  122. })
  123. it('should remove affiliationUnchecked flag if v1 does not return an error', function (done) {
  124. this.addAffiliation.yields()
  125. this.UserUpdater.addAffiliationForNewUser(
  126. this.stubbedUser._id,
  127. this.newEmail,
  128. error => {
  129. expect(error).not.to.exist
  130. sinon.assert.calledOnce(this.UserUpdater.updateUser)
  131. sinon.assert.calledWithMatch(
  132. this.UserUpdater.updateUser,
  133. { _id: this.stubbedUser._id, 'emails.email': this.newEmail },
  134. { $unset: { 'emails.$.affiliationUnchecked': 1 } }
  135. )
  136. done()
  137. }
  138. )
  139. })
  140. })
  141. describe('changeEmailAddress', function () {
  142. beforeEach(function () {
  143. this.auditLog = {
  144. initiatorId: 'abc123',
  145. ipAddress: '0:0:0:0',
  146. }
  147. this.UserGetter.getUserEmail.callsArgWith(1, null, this.stubbedUser.email)
  148. this.UserUpdater.addEmailAddress = sinon.stub().callsArgWith(4)
  149. this.UserUpdater.setDefaultEmailAddress = sinon.stub().yields()
  150. this.UserUpdater.removeEmailAddress = sinon.stub().callsArgWith(2)
  151. })
  152. it('change email', function (done) {
  153. this.UserUpdater.changeEmailAddress(
  154. this.stubbedUser._id,
  155. this.newEmail,
  156. this.auditLog,
  157. err => {
  158. expect(err).not.to.exist
  159. this.UserUpdater.addEmailAddress
  160. .calledWith(this.stubbedUser._id, this.newEmail, {}, this.auditLog)
  161. .should.equal(true)
  162. this.UserUpdater.setDefaultEmailAddress
  163. .calledWith(
  164. this.stubbedUser._id,
  165. this.newEmail,
  166. true,
  167. this.auditLog,
  168. true
  169. )
  170. .should.equal(true)
  171. this.UserUpdater.removeEmailAddress
  172. .calledWith(this.stubbedUser._id, this.stubbedUser.email)
  173. .should.equal(true)
  174. done()
  175. }
  176. )
  177. })
  178. it('validates email', function (done) {
  179. this.UserUpdater.changeEmailAddress(
  180. this.stubbedUser._id,
  181. 'foo',
  182. this.auditLog,
  183. err => {
  184. expect(err).to.exist
  185. done()
  186. }
  187. )
  188. })
  189. it('handle error', function (done) {
  190. this.UserUpdater.removeEmailAddress.callsArgWith(2, new Error('nope'))
  191. this.UserUpdater.changeEmailAddress(
  192. this.stubbedUser._id,
  193. this.newEmail,
  194. this.auditLog,
  195. err => {
  196. expect(err).to.exist
  197. done()
  198. }
  199. )
  200. })
  201. })
  202. describe('addEmailAddress', function () {
  203. beforeEach(function () {
  204. this.UserGetter.promises.ensureUniqueEmailAddress = sinon
  205. .stub()
  206. .resolves()
  207. this.UserUpdater.promises.updateUser = sinon.stub().resolves()
  208. })
  209. it('add email', function (done) {
  210. this.UserUpdater.addEmailAddress(
  211. this.stubbedUser._id,
  212. this.newEmail,
  213. {},
  214. { initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
  215. err => {
  216. this.UserGetter.promises.ensureUniqueEmailAddress.called.should.equal(
  217. true
  218. )
  219. expect(err).to.not.exist
  220. const reversedHostname = this.newEmail
  221. .split('@')[1]
  222. .split('')
  223. .reverse()
  224. .join('')
  225. this.UserUpdater.promises.updateUser
  226. .calledWith(this.stubbedUser._id, {
  227. $push: {
  228. emails: {
  229. email: this.newEmail,
  230. createdAt: sinon.match.date,
  231. reversedHostname,
  232. },
  233. },
  234. })
  235. .should.equal(true)
  236. done()
  237. }
  238. )
  239. })
  240. it('add affiliation', function (done) {
  241. const affiliationOptions = {
  242. university: { id: 1 },
  243. role: 'Prof',
  244. department: 'Math',
  245. }
  246. this.UserUpdater.addEmailAddress(
  247. this.stubbedUser._id,
  248. this.newEmail,
  249. affiliationOptions,
  250. { initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
  251. err => {
  252. expect(err).not.to.exist
  253. this.InstitutionsAPI.promises.addAffiliation.calledOnce.should.equal(
  254. true
  255. )
  256. const { args } = this.InstitutionsAPI.promises.addAffiliation.lastCall
  257. args[0].should.equal(this.stubbedUser._id)
  258. args[1].should.equal(this.newEmail)
  259. args[2].should.equal(affiliationOptions)
  260. done()
  261. }
  262. )
  263. })
  264. it('handle affiliation error', function (done) {
  265. this.InstitutionsAPI.promises.addAffiliation.rejects(new Error('nope'))
  266. this.UserUpdater.addEmailAddress(
  267. this.stubbedUser._id,
  268. this.newEmail,
  269. {},
  270. { initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
  271. err => {
  272. expect(err).to.exist
  273. this.UserUpdater.promises.updateUser.called.should.equal(false)
  274. done()
  275. }
  276. )
  277. })
  278. it('validates email', function (done) {
  279. this.UserUpdater.addEmailAddress(
  280. this.stubbedUser._id,
  281. 'bar',
  282. {},
  283. { initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
  284. err => {
  285. expect(err).to.exist
  286. done()
  287. }
  288. )
  289. })
  290. it('updates the audit log', function (done) {
  291. this.ip = '127:0:0:0'
  292. this.UserUpdater.addEmailAddress(
  293. this.stubbedUser._id,
  294. this.newEmail,
  295. {},
  296. { initiatorId: this.stubbedUser._id, ipAddress: this.ip },
  297. error => {
  298. expect(error).to.not.exist
  299. this.InstitutionsAPI.promises.addAffiliation.calledOnce.should.equal(
  300. true
  301. )
  302. const { args } = this.UserAuditLogHandler.promises.addEntry.lastCall
  303. expect(args[0]).to.equal(this.stubbedUser._id)
  304. expect(args[1]).to.equal('add-email')
  305. expect(args[2]).to.equal(this.stubbedUser._id)
  306. expect(args[3]).to.equal(this.ip)
  307. expect(args[4]).to.deep.equal({
  308. newSecondaryEmail: this.newEmail,
  309. })
  310. done()
  311. }
  312. )
  313. })
  314. describe('errors', function () {
  315. describe('via UserAuditLogHandler', function () {
  316. const anError = new Error('oops')
  317. beforeEach(function () {
  318. this.UserAuditLogHandler.promises.addEntry.throws(anError)
  319. })
  320. it('should not add email and should return error', function (done) {
  321. this.UserUpdater.addEmailAddress(
  322. this.stubbedUser._id,
  323. this.newEmail,
  324. {},
  325. { initiatorId: this.stubbedUser._id, ipAddress: '127:0:0:0' },
  326. error => {
  327. expect(error).to.exist
  328. expect(error).to.equal(anError)
  329. expect(this.UserUpdater.promises.updateUser).to.not.have.been
  330. .called
  331. done()
  332. }
  333. )
  334. })
  335. })
  336. })
  337. })
  338. describe('removeEmailAddress', function () {
  339. beforeEach(function () {
  340. this.UserUpdater.promises.updateUser = sinon
  341. .stub()
  342. .returns({ matchedCount: 1 })
  343. })
  344. it('remove email', function (done) {
  345. this.UserUpdater.removeEmailAddress(
  346. this.stubbedUser._id,
  347. this.newEmail,
  348. err => {
  349. expect(err).not.to.exist
  350. this.UserUpdater.promises.updateUser
  351. .calledWith(
  352. { _id: this.stubbedUser._id, email: { $ne: this.newEmail } },
  353. { $pull: { emails: { email: this.newEmail } } }
  354. )
  355. .should.equal(true)
  356. done()
  357. }
  358. )
  359. })
  360. it('remove affiliation', function (done) {
  361. this.UserUpdater.removeEmailAddress(
  362. this.stubbedUser._id,
  363. this.newEmail,
  364. err => {
  365. expect(err).not.to.exist
  366. this.InstitutionsAPI.promises.removeAffiliation.calledOnce.should.equal(
  367. true
  368. )
  369. const { args } =
  370. this.InstitutionsAPI.promises.removeAffiliation.lastCall
  371. args[0].should.equal(this.stubbedUser._id)
  372. args[1].should.equal(this.newEmail)
  373. done()
  374. }
  375. )
  376. })
  377. it('refresh features', function (done) {
  378. this.UserUpdater.removeEmailAddress(
  379. this.stubbedUser._id,
  380. this.newEmail,
  381. err => {
  382. expect(err).not.to.exist
  383. sinon.assert.calledWith(
  384. this.FeaturesUpdater.promises.refreshFeatures,
  385. this.stubbedUser._id
  386. )
  387. done()
  388. }
  389. )
  390. })
  391. it('handle error from updateUser', function (done) {
  392. const anError = new Error('nope')
  393. this.UserUpdater.promises.updateUser.rejects(anError)
  394. this.UserUpdater.removeEmailAddress(
  395. this.stubbedUser._id,
  396. this.newEmail,
  397. err => {
  398. expect(err).to.exist
  399. expect(err).to.deep.equal(anError)
  400. expect(err._oErrorTags[0].message).to.equal(
  401. 'problem removing users email'
  402. )
  403. expect(
  404. this.FeaturesUpdater.promises.refreshFeatures.callCount
  405. ).to.equal(0)
  406. done()
  407. }
  408. )
  409. })
  410. it('handle missed update', function (done) {
  411. this.UserUpdater.promises.updateUser = sinon
  412. .stub()
  413. .returns({ matchedCount: 0 })
  414. this.UserUpdater.removeEmailAddress(
  415. this.stubbedUser._id,
  416. this.newEmail,
  417. err => {
  418. expect(err).to.exist
  419. expect(err.message).to.equal('Cannot remove email')
  420. expect(
  421. this.FeaturesUpdater.promises.refreshFeatures.callCount
  422. ).to.equal(0)
  423. done()
  424. }
  425. )
  426. })
  427. it('handle affiliation error', function (done) {
  428. const anError = new Error('nope')
  429. this.InstitutionsAPI.promises.removeAffiliation.rejects(anError)
  430. this.UserUpdater.removeEmailAddress(
  431. this.stubbedUser._id,
  432. this.newEmail,
  433. err => {
  434. expect(err).to.exist
  435. expect(err).to.deep.equal(anError)
  436. this.UserUpdater.promises.updateUser.called.should.equal(false)
  437. expect(
  438. this.FeaturesUpdater.promises.refreshFeatures.callCount
  439. ).to.equal(0)
  440. done()
  441. }
  442. )
  443. })
  444. it('returns error when removing primary email', function (done) {
  445. this.UserGetter.promises.getUserByMainEmail = sinon
  446. .stub()
  447. .returns({ _id: '123abc' })
  448. this.UserUpdater.removeEmailAddress(
  449. this.stubbedUser._id,
  450. this.newEmail,
  451. err => {
  452. expect(err).to.exist
  453. expect(err.message).to.deep.equal('cannot remove primary email')
  454. expect(this.UserUpdater.promises.updateUser.callCount).to.equal(0)
  455. expect(
  456. this.FeaturesUpdater.promises.refreshFeatures.callCount
  457. ).to.equal(0)
  458. done()
  459. }
  460. )
  461. })
  462. it('validates email', function (done) {
  463. this.UserUpdater.removeEmailAddress(this.stubbedUser._id, 'baz', err => {
  464. expect(err).to.exist
  465. expect(err.message).to.equal('invalid email')
  466. done()
  467. })
  468. })
  469. it('skip email validation when skipParseEmail included', function (done) {
  470. const skipParseEmail = true
  471. this.UserUpdater.removeEmailAddress(
  472. this.stubbedUser._id,
  473. 'baz',
  474. skipParseEmail,
  475. err => {
  476. expect(err).to.not.exist
  477. done()
  478. }
  479. )
  480. })
  481. it('returns an error when skipParseEmail included but email is not a string', function (done) {
  482. const skipParseEmail = true
  483. this.UserUpdater.removeEmailAddress(
  484. this.stubbedUser._id,
  485. 1,
  486. skipParseEmail,
  487. err => {
  488. expect(err).to.exist
  489. expect(err.message).to.equal('email must be a string')
  490. done()
  491. }
  492. )
  493. })
  494. })
  495. describe('setDefaultEmailAddress', function () {
  496. beforeEach(function () {
  497. this.auditLog = {
  498. initiatorId: this.stubbedUser,
  499. ipAddress: '0:0:0:0',
  500. }
  501. this.stubbedUser.emails = [
  502. {
  503. email: this.newEmail,
  504. confirmedAt: new Date(),
  505. },
  506. ]
  507. this.UserGetter.promises.getUser.resolves(this.stubbedUser)
  508. this.NewsletterManager.promises.changeEmail.callsArgWith(2, null)
  509. this.RecurlyWrapper.promises.updateAccountEmailAddress.resolves()
  510. })
  511. it('set default', function (done) {
  512. this.UserUpdater.promises.updateUser = sinon
  513. .stub()
  514. .resolves({ matchedCount: 1 })
  515. this.UserUpdater.setDefaultEmailAddress(
  516. this.stubbedUser._id,
  517. this.newEmail,
  518. false,
  519. this.auditLog,
  520. err => {
  521. expect(err).not.to.exist
  522. this.UserUpdater.promises.updateUser
  523. .calledWith(
  524. { _id: this.stubbedUser._id, 'emails.email': this.newEmail },
  525. {
  526. $set: {
  527. email: this.newEmail,
  528. lastPrimaryEmailCheck: sinon.match.date,
  529. },
  530. }
  531. )
  532. .should.equal(true)
  533. done()
  534. }
  535. )
  536. })
  537. it('set changed the email in newsletter', function (done) {
  538. this.UserUpdater.promises.updateUser = sinon
  539. .stub()
  540. .resolves({ matchedCount: 1 })
  541. this.UserUpdater.setDefaultEmailAddress(
  542. this.stubbedUser._id,
  543. this.newEmail,
  544. false,
  545. this.auditLog,
  546. err => {
  547. expect(err).not.to.exist
  548. this.NewsletterManager.promises.changeEmail
  549. .calledWith(this.stubbedUser, this.newEmail)
  550. .should.equal(true)
  551. this.RecurlyWrapper.promises.updateAccountEmailAddress
  552. .calledWith(this.stubbedUser._id, this.newEmail)
  553. .should.equal(true)
  554. done()
  555. }
  556. )
  557. })
  558. it('handle error', function (done) {
  559. this.UserUpdater.promises.updateUser = sinon.stub().rejects(Error('nope'))
  560. this.UserUpdater.setDefaultEmailAddress(
  561. this.stubbedUser._id,
  562. this.newEmail,
  563. false,
  564. this.auditLog,
  565. err => {
  566. expect(err).to.exist
  567. done()
  568. }
  569. )
  570. })
  571. it('handle missed update', function (done) {
  572. this.UserUpdater.promises.updateUser = sinon
  573. .stub()
  574. .resolves({ matchedCount: 0 })
  575. this.UserUpdater.setDefaultEmailAddress(
  576. this.stubbedUser._id,
  577. this.newEmail,
  578. false,
  579. this.auditLog,
  580. err => {
  581. expect(err).to.exist
  582. done()
  583. }
  584. )
  585. })
  586. it('validates email', function (done) {
  587. this.UserUpdater.setDefaultEmailAddress(
  588. this.stubbedUser._id,
  589. '.edu',
  590. false,
  591. this.auditLog,
  592. err => {
  593. expect(err).to.exist
  594. done()
  595. }
  596. )
  597. })
  598. it('updates audit log', function (done) {
  599. this.UserUpdater.promises.updateUser = sinon
  600. .stub()
  601. .resolves({ matchedCount: 1 })
  602. this.UserUpdater.setDefaultEmailAddress(
  603. this.stubbedUser._id,
  604. this.newEmail,
  605. false,
  606. this.auditLog,
  607. error => {
  608. expect(error).to.not.exist
  609. expect(
  610. this.UserAuditLogHandler.promises.addEntry
  611. ).to.have.been.calledWith(
  612. this.stubbedUser._id,
  613. 'change-primary-email',
  614. this.auditLog.initiatorId,
  615. this.auditLog.ipAddress,
  616. {
  617. newPrimaryEmail: this.newEmail,
  618. oldPrimaryEmail: this.stubbedUser.email,
  619. }
  620. )
  621. done()
  622. }
  623. )
  624. })
  625. it('blocks email update if audit log returns an error', function (done) {
  626. this.UserUpdater.promises.updateUser = sinon.stub()
  627. this.UserAuditLogHandler.promises.addEntry.rejects(new Error('oops'))
  628. this.UserUpdater.setDefaultEmailAddress(
  629. this.stubbedUser._id,
  630. this.newEmail,
  631. false,
  632. this.auditLog,
  633. error => {
  634. expect(error).to.exist
  635. expect(this.UserUpdater.promises.updateUser).to.not.have.been.called
  636. done()
  637. }
  638. )
  639. })
  640. describe('when email not confirmed', function () {
  641. beforeEach(function () {
  642. this.stubbedUser.emails = [
  643. {
  644. email: this.newEmail,
  645. confirmedAt: null,
  646. },
  647. ]
  648. this.UserUpdater.promises.updateUser = sinon.stub()
  649. })
  650. it('should callback with error', function () {
  651. this.UserUpdater.setDefaultEmailAddress(
  652. this.stubbedUser._id,
  653. this.newEmail,
  654. false,
  655. this.auditLog,
  656. error => {
  657. expect(error).to.exist
  658. expect(error.name).to.equal('UnconfirmedEmailError')
  659. this.UserUpdater.promises.updateUser.callCount.should.equal(0)
  660. this.NewsletterManager.promises.changeEmail.callCount.should.equal(
  661. 0
  662. )
  663. }
  664. )
  665. })
  666. })
  667. describe('when email does not belong to user', function () {
  668. beforeEach(function () {
  669. this.stubbedUser.emails = []
  670. this.UserGetter.promises.getUser.resolves(this.stubbedUser)
  671. this.UserUpdater.promises.updateUser = sinon.stub()
  672. })
  673. it('should callback with error', function () {
  674. this.UserUpdater.setDefaultEmailAddress(
  675. this.stubbedUser._id,
  676. this.newEmail,
  677. false,
  678. this.auditLog,
  679. error => {
  680. expect(error).to.exist
  681. expect(error.name).to.equal('Error')
  682. this.UserUpdater.promises.updateUser.callCount.should.equal(0)
  683. this.NewsletterManager.promises.changeEmail.callCount.should.equal(
  684. 0
  685. )
  686. }
  687. )
  688. })
  689. })
  690. describe('security alert', function () {
  691. it('should be sent to old and new email when sendSecurityAlert=true', function (done) {
  692. // this.UserGetter.promises.getUser.resolves(this.stubbedUser)
  693. this.UserUpdater.promises.updateUser = sinon
  694. .stub()
  695. .resolves({ matchedCount: 1 })
  696. this.UserUpdater.setDefaultEmailAddress(
  697. this.stubbedUser._id,
  698. this.newEmail,
  699. false,
  700. this.auditLog,
  701. true,
  702. error => {
  703. expect(error).to.not.exist
  704. this.EmailHandler.promises.sendEmail.callCount.should.equal(2)
  705. const toOldEmailAlert =
  706. this.EmailHandler.promises.sendEmail.firstCall
  707. expect(toOldEmailAlert.args[0]).to.equal('securityAlert')
  708. const toNewEmailAlert =
  709. this.EmailHandler.promises.sendEmail.lastCall
  710. expect(toOldEmailAlert.args[1].to).to.equal(this.stubbedUser.email)
  711. expect(toNewEmailAlert.args[0]).to.equal('securityAlert')
  712. expect(toNewEmailAlert.args[1].to).to.equal(this.newEmail)
  713. done()
  714. }
  715. )
  716. })
  717. describe('errors', function () {
  718. const anError = new Error('oops')
  719. describe('EmailHandler', function () {
  720. beforeEach(function () {
  721. this.EmailHandler.promises.sendEmail.rejects(anError)
  722. this.UserUpdater.promises.updateUser = sinon
  723. .stub()
  724. .resolves({ matchedCount: 1 })
  725. })
  726. it('should log but not pass back the error', function (done) {
  727. this.UserUpdater.setDefaultEmailAddress(
  728. this.stubbedUser._id,
  729. this.newEmail,
  730. false,
  731. this.auditLog,
  732. true,
  733. error => {
  734. expect(error).to.not.exist
  735. const loggerCall = this.logger.error.firstCall
  736. expect(loggerCall.args[0]).to.deep.equal({
  737. error: anError,
  738. userId: this.stubbedUser._id,
  739. })
  740. expect(loggerCall.args[1]).to.contain(
  741. 'could not send security alert email when primary email changed'
  742. )
  743. done()
  744. }
  745. )
  746. })
  747. })
  748. })
  749. })
  750. })
  751. describe('confirmEmail', function () {
  752. beforeEach(function () {
  753. this.UserUpdater.promises.updateUser = sinon
  754. .stub()
  755. .resolves({ matchedCount: 1 })
  756. })
  757. it('should update the email record', function (done) {
  758. this.UserUpdater.confirmEmail(
  759. this.stubbedUser._id,
  760. this.stubbedUserEmail,
  761. err => {
  762. expect(err).not.to.exist
  763. this.UserUpdater.promises.updateUser
  764. .calledWith(
  765. {
  766. _id: this.stubbedUser._id,
  767. 'emails.email': this.stubbedUserEmail,
  768. },
  769. {
  770. $set: {
  771. 'emails.$.reconfirmedAt': new Date(),
  772. },
  773. $min: {
  774. 'emails.$.confirmedAt': new Date(),
  775. },
  776. }
  777. )
  778. .should.equal(true)
  779. done()
  780. }
  781. )
  782. })
  783. it('add affiliation', function (done) {
  784. this.UserUpdater.confirmEmail(
  785. this.stubbedUser._id,
  786. this.newEmail,
  787. err => {
  788. expect(err).not.to.exist
  789. this.InstitutionsAPI.promises.addAffiliation.calledOnce.should.equal(
  790. true
  791. )
  792. sinon.assert.calledWith(
  793. this.InstitutionsAPI.promises.addAffiliation,
  794. this.stubbedUser._id,
  795. this.newEmail,
  796. { confirmedAt: new Date() }
  797. )
  798. done()
  799. }
  800. )
  801. })
  802. it('handle error', function (done) {
  803. this.UserUpdater.promises.updateUser = sinon
  804. .stub()
  805. .throws(new Error('nope'))
  806. this.UserUpdater.confirmEmail(
  807. this.stubbedUser._id,
  808. this.newEmail,
  809. err => {
  810. expect(err).to.exist
  811. done()
  812. }
  813. )
  814. })
  815. it('handle missed update', function (done) {
  816. this.UserUpdater.promises.updateUser = sinon
  817. .stub()
  818. .resolves({ matchedCount: 0 })
  819. this.UserUpdater.confirmEmail(
  820. this.stubbedUser._id,
  821. this.newEmail,
  822. err => {
  823. expect(err).to.exist
  824. done()
  825. }
  826. )
  827. })
  828. it('validates email', function (done) {
  829. this.UserUpdater.confirmEmail(this.stubbedUser._id, '@', err => {
  830. expect(err).to.exist
  831. done()
  832. })
  833. })
  834. it('handle affiliation error', function (done) {
  835. this.InstitutionsAPI.promises.addAffiliation.throws(Error('nope'))
  836. this.UserUpdater.confirmEmail(
  837. this.stubbedUser._id,
  838. this.newEmail,
  839. err => {
  840. expect(err).to.exist
  841. this.UserUpdater.promises.updateUser.called.should.equal(false)
  842. done()
  843. }
  844. )
  845. })
  846. it('refresh features', function (done) {
  847. this.UserUpdater.confirmEmail(
  848. this.stubbedUser._id,
  849. this.newEmail,
  850. err => {
  851. expect(err).not.to.exist
  852. sinon.assert.calledWith(
  853. this.FeaturesUpdater.promises.refreshFeatures,
  854. this.stubbedUser._id
  855. )
  856. done()
  857. }
  858. )
  859. })
  860. })
  861. })