UserUpdaterTests.js 23 KB

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