UserUpdaterTests.js 24 KB

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