UserControllerTests.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. const sinon = require('sinon')
  2. const chai = require('chai')
  3. const { expect } = chai
  4. const modulePath = '../../../../app/src/Features/User/UserController.js'
  5. const SandboxedModule = require('sandboxed-module')
  6. const OError = require('@overleaf/o-error')
  7. const Errors = require('../../../../app/src/Features/Errors/Errors')
  8. const HttpErrors = require('@overleaf/o-error/http')
  9. describe('UserController', function() {
  10. beforeEach(function() {
  11. this.user_id = '323123'
  12. this.user = {
  13. _id: this.user_id,
  14. save: sinon.stub().callsArgWith(0),
  15. ace: {}
  16. }
  17. this.req = {
  18. user: {},
  19. session: {
  20. destroy() {},
  21. user: {
  22. _id: this.user_id,
  23. email: 'old@something.com'
  24. }
  25. },
  26. sessionID: '123',
  27. body: {},
  28. i18n: {
  29. translate: text => text
  30. },
  31. query: {}
  32. }
  33. this.UserDeleter = { deleteUser: sinon.stub().yields() }
  34. this.UserGetter = {
  35. getUser: sinon.stub().callsArgWith(1, null, this.user),
  36. promises: { getUser: sinon.stub().resolves(this.user) }
  37. }
  38. this.User = { findById: sinon.stub().callsArgWith(1, null, this.user) }
  39. this.NewsLetterManager = { unsubscribe: sinon.stub().callsArgWith(1) }
  40. this.UserRegistrationHandler = { registerNewUser: sinon.stub() }
  41. this.AuthenticationController = {
  42. establishUserSession: sinon.stub().callsArg(2),
  43. getLoggedInUserId: sinon.stub().returns(this.user._id),
  44. getSessionUser: sinon.stub().returns(this.req.session.user),
  45. setInSessionUser: sinon.stub()
  46. }
  47. this.AuthenticationManager = {
  48. authenticate: sinon.stub(),
  49. setUserPassword: sinon.stub(),
  50. validatePassword: sinon.stub()
  51. }
  52. this.ReferalAllocator = { allocate: sinon.stub() }
  53. this.SubscriptionDomainHandler = { autoAllocate: sinon.stub() }
  54. this.UserUpdater = {
  55. changeEmailAddress: sinon.stub(),
  56. promises: {
  57. confirmEmail: sinon.stub().resolves(),
  58. addAffiliationForNewUser: sinon.stub().resolves()
  59. }
  60. }
  61. this.settings = { siteUrl: 'sharelatex.example.com' }
  62. this.UserHandler = { populateTeamInvites: sinon.stub().callsArgWith(1) }
  63. this.UserSessionsManager = {
  64. trackSession: sinon.stub(),
  65. untrackSession: sinon.stub(),
  66. revokeAllUserSessions: sinon.stub().callsArgWith(2, null)
  67. }
  68. this.SudoModeHandler = { clearSudoMode: sinon.stub() }
  69. this.HttpErrorHandler = {
  70. unprocessableEntity: sinon.stub()
  71. }
  72. this.UserController = SandboxedModule.require(modulePath, {
  73. globals: {
  74. console: console
  75. },
  76. requires: {
  77. './UserGetter': this.UserGetter,
  78. './UserDeleter': this.UserDeleter,
  79. './UserUpdater': this.UserUpdater,
  80. '../../models/User': {
  81. User: this.User
  82. },
  83. '../Newsletter/NewsletterManager': this.NewsLetterManager,
  84. './UserRegistrationHandler': this.UserRegistrationHandler,
  85. '../Authentication/AuthenticationController': this
  86. .AuthenticationController,
  87. '../Authentication/AuthenticationManager': this.AuthenticationManager,
  88. '../../infrastructure/Features': (this.Features = {
  89. hasFeature: sinon.stub()
  90. }),
  91. '../Referal/ReferalAllocator': this.ReferalAllocator,
  92. '../Subscription/SubscriptionDomainHandler': this
  93. .SubscriptionDomainHandler,
  94. './UserHandler': this.UserHandler,
  95. './UserSessionsManager': this.UserSessionsManager,
  96. '../SudoMode/SudoModeHandler': this.SudoModeHandler,
  97. '../Errors/HttpErrorHandler': this.HttpErrorHandler,
  98. 'settings-sharelatex': this.settings,
  99. 'logger-sharelatex': {
  100. log() {},
  101. warn() {},
  102. err() {},
  103. error() {}
  104. },
  105. 'metrics-sharelatex': {
  106. inc() {}
  107. },
  108. '../Errors/Errors': Errors,
  109. '@overleaf/o-error': OError,
  110. '@overleaf/o-error/http': HttpErrors,
  111. '../Email/EmailHandler': { sendEmail: sinon.stub() }
  112. }
  113. })
  114. this.res = {
  115. send: sinon.stub(),
  116. status: sinon.stub(),
  117. sendStatus: sinon.stub(),
  118. json: sinon.stub()
  119. }
  120. this.res.status.returns(this.res)
  121. this.next = sinon.stub()
  122. this.callback = sinon.stub()
  123. })
  124. describe('tryDeleteUser', function() {
  125. beforeEach(function() {
  126. this.req.body.password = 'wat'
  127. this.req.logout = sinon.stub()
  128. this.req.session.destroy = sinon.stub().callsArgWith(0, null)
  129. this.AuthenticationController.getLoggedInUserId = sinon
  130. .stub()
  131. .returns(this.user._id)
  132. this.AuthenticationManager.authenticate = sinon
  133. .stub()
  134. .callsArgWith(2, null, this.user)
  135. })
  136. it('should send 200', function(done) {
  137. this.res.sendStatus = code => {
  138. code.should.equal(200)
  139. done()
  140. }
  141. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  142. })
  143. it('should try to authenticate user', function(done) {
  144. this.res.sendStatus = code => {
  145. this.AuthenticationManager.authenticate.callCount.should.equal(1)
  146. this.AuthenticationManager.authenticate
  147. .calledWith({ _id: this.user._id }, this.req.body.password)
  148. .should.equal(true)
  149. done()
  150. }
  151. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  152. })
  153. it('should delete the user', function(done) {
  154. this.res.sendStatus = code => {
  155. this.UserDeleter.deleteUser.callCount.should.equal(1)
  156. this.UserDeleter.deleteUser.calledWith(this.user._id).should.equal(true)
  157. done()
  158. }
  159. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  160. })
  161. describe('when no password is supplied', function() {
  162. beforeEach(function() {
  163. this.req.body.password = ''
  164. })
  165. it('should return 403', function(done) {
  166. this.res.sendStatus = code => {
  167. code.should.equal(403)
  168. done()
  169. }
  170. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  171. })
  172. })
  173. describe('when authenticate produces an error', function() {
  174. beforeEach(function() {
  175. this.AuthenticationManager.authenticate = sinon
  176. .stub()
  177. .callsArgWith(2, new Error('woops'))
  178. })
  179. it('should call next with an error', function(done) {
  180. this.next = err => {
  181. expect(err).to.not.equal(null)
  182. expect(err).to.be.instanceof(Error)
  183. done()
  184. }
  185. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  186. })
  187. })
  188. describe('when authenticate does not produce a user', function() {
  189. beforeEach(function() {
  190. this.AuthenticationManager.authenticate = sinon
  191. .stub()
  192. .callsArgWith(2, null, null)
  193. })
  194. it('should return 403', function(done) {
  195. this.res.sendStatus = code => {
  196. code.should.equal(403)
  197. done()
  198. }
  199. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  200. })
  201. })
  202. describe('when deleteUser produces an error', function() {
  203. beforeEach(function() {
  204. this.UserDeleter.deleteUser = sinon.stub().yields(new Error('woops'))
  205. })
  206. it('should call next with an error', function(done) {
  207. this.next = err => {
  208. expect(err).to.not.equal(null)
  209. expect(err).to.be.instanceof(Error)
  210. done()
  211. }
  212. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  213. })
  214. })
  215. describe('when deleteUser produces a known error', function() {
  216. beforeEach(function() {
  217. this.UserDeleter.deleteUser = sinon
  218. .stub()
  219. .yields(new Errors.SubscriptionAdminDeletionError())
  220. })
  221. it('should return a HTTP Unprocessable Entity error', function(done) {
  222. this.HttpErrorHandler.unprocessableEntity = sinon.spy(
  223. (req, res, message, info) => {
  224. expect(req).to.exist
  225. expect(res).to.exist
  226. expect(message).to.equal('error while deleting user account')
  227. expect(info).to.deep.equal({
  228. error: 'SubscriptionAdminDeletionError'
  229. })
  230. done()
  231. }
  232. )
  233. this.UserController.tryDeleteUser(this.req, this.res)
  234. })
  235. })
  236. describe('when session.destroy produces an error', function() {
  237. beforeEach(function() {
  238. this.req.session.destroy = sinon
  239. .stub()
  240. .callsArgWith(0, new Error('woops'))
  241. })
  242. it('should call next with an error', function(done) {
  243. this.next = err => {
  244. expect(err).to.not.equal(null)
  245. expect(err).to.be.instanceof(Error)
  246. done()
  247. }
  248. this.UserController.tryDeleteUser(this.req, this.res, this.next)
  249. })
  250. })
  251. })
  252. describe('unsubscribe', function() {
  253. it('should send the user to unsubscribe', function(done) {
  254. this.res.sendStatus = () => {
  255. this.NewsLetterManager.unsubscribe
  256. .calledWith(this.user)
  257. .should.equal(true)
  258. done()
  259. }
  260. this.UserController.unsubscribe(this.req, this.res)
  261. })
  262. })
  263. describe('updateUserSettings', function() {
  264. beforeEach(function() {
  265. this.newEmail = 'hello@world.com'
  266. this.req.externalAuthenticationSystemUsed = sinon.stub().returns(false)
  267. })
  268. it('should call save', function(done) {
  269. this.req.body = {}
  270. this.res.sendStatus = code => {
  271. this.user.save.called.should.equal(true)
  272. done()
  273. }
  274. this.UserController.updateUserSettings(this.req, this.res)
  275. })
  276. it('should set the first name', function(done) {
  277. this.req.body = { first_name: 'bobby ' }
  278. this.res.sendStatus = code => {
  279. this.user.first_name.should.equal('bobby')
  280. done()
  281. }
  282. this.UserController.updateUserSettings(this.req, this.res)
  283. })
  284. it('should set the role', function(done) {
  285. this.req.body = { role: 'student' }
  286. this.res.sendStatus = code => {
  287. this.user.role.should.equal('student')
  288. done()
  289. }
  290. this.UserController.updateUserSettings(this.req, this.res)
  291. })
  292. it('should set the institution', function(done) {
  293. this.req.body = { institution: 'MIT' }
  294. this.res.sendStatus = code => {
  295. this.user.institution.should.equal('MIT')
  296. done()
  297. }
  298. this.UserController.updateUserSettings(this.req, this.res)
  299. })
  300. it('should set some props on ace', function(done) {
  301. this.req.body = { editorTheme: 'something' }
  302. this.res.sendStatus = code => {
  303. this.user.ace.theme.should.equal('something')
  304. done()
  305. }
  306. this.UserController.updateUserSettings(this.req, this.res)
  307. })
  308. it('should set the overall theme', function(done) {
  309. this.req.body = { overallTheme: 'green-ish' }
  310. this.res.sendStatus = code => {
  311. this.user.ace.overallTheme.should.equal('green-ish')
  312. done()
  313. }
  314. this.UserController.updateUserSettings(this.req, this.res)
  315. })
  316. it('should send an error if the email is 0 len', function(done) {
  317. this.req.body.email = ''
  318. this.res.sendStatus = function(code) {
  319. code.should.equal(400)
  320. done()
  321. }
  322. this.UserController.updateUserSettings(this.req, this.res)
  323. })
  324. it('should send an error if the email does not contain an @', function(done) {
  325. this.req.body.email = 'bob at something dot com'
  326. this.res.sendStatus = function(code) {
  327. code.should.equal(400)
  328. done()
  329. }
  330. this.UserController.updateUserSettings(this.req, this.res)
  331. })
  332. it('should call the user updater with the new email and user _id', function(done) {
  333. this.req.body.email = this.newEmail.toUpperCase()
  334. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  335. this.res.sendStatus = code => {
  336. code.should.equal(200)
  337. this.UserUpdater.changeEmailAddress
  338. .calledWith(this.user_id, this.newEmail)
  339. .should.equal(true)
  340. done()
  341. }
  342. this.UserController.updateUserSettings(this.req, this.res)
  343. })
  344. it('should update the email on the session', function(done) {
  345. this.req.body.email = this.newEmail.toUpperCase()
  346. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  347. let callcount = 0
  348. this.User.findById = (id, cb) => {
  349. if (++callcount === 2) {
  350. this.user.email = this.newEmail
  351. }
  352. cb(null, this.user)
  353. }
  354. this.res.sendStatus = code => {
  355. code.should.equal(200)
  356. this.AuthenticationController.setInSessionUser
  357. .calledWith(this.req, {
  358. email: this.newEmail,
  359. first_name: undefined,
  360. last_name: undefined
  361. })
  362. .should.equal(true)
  363. done()
  364. }
  365. this.UserController.updateUserSettings(this.req, this.res)
  366. })
  367. it('should call populateTeamInvites', function(done) {
  368. this.req.body.email = this.newEmail.toUpperCase()
  369. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  370. this.res.sendStatus = code => {
  371. code.should.equal(200)
  372. this.UserHandler.populateTeamInvites
  373. .calledWith(this.user)
  374. .should.equal(true)
  375. done()
  376. }
  377. this.UserController.updateUserSettings(this.req, this.res)
  378. })
  379. describe('when changeEmailAddress yields an error', function() {
  380. it('should pass on an error and not send a success status', function(done) {
  381. this.req.body.email = this.newEmail.toUpperCase()
  382. this.UserUpdater.changeEmailAddress.callsArgWith(2, new Error())
  383. const next = err => {
  384. expect(err).to.exist
  385. process.nextTick(() => {
  386. // logic in User.findById
  387. expect(this.res.send.called).to.equal(false)
  388. expect(this.res.sendStatus.called).to.equal(false)
  389. // logic after error handling
  390. expect(this.User.findById.callCount).to.equal(1)
  391. done()
  392. })
  393. }
  394. this.UserController.updateUserSettings(this.req, this.res, next)
  395. })
  396. })
  397. describe('when using an external auth source', function() {
  398. beforeEach(function() {
  399. this.UserUpdater.changeEmailAddress.callsArgWith(2)
  400. this.newEmail = 'someone23@example.com'
  401. this.req.externalAuthenticationSystemUsed = sinon.stub().returns(true)
  402. })
  403. it('should not set a new email', function(done) {
  404. this.req.body.email = this.newEmail
  405. this.res.sendStatus = code => {
  406. code.should.equal(200)
  407. this.UserUpdater.changeEmailAddress
  408. .calledWith(this.user_id, this.newEmail)
  409. .should.equal(false)
  410. done()
  411. }
  412. this.UserController.updateUserSettings(this.req, this.res)
  413. })
  414. })
  415. })
  416. describe('logout', function() {
  417. it('should destroy the session', function(done) {
  418. this.req.session.destroy = sinon.stub().callsArgWith(0)
  419. this.res.redirect = url => {
  420. url.should.equal('/login')
  421. this.req.session.destroy.called.should.equal(true)
  422. done()
  423. }
  424. this.UserController.logout(this.req, this.res)
  425. })
  426. it('should clear sudo-mode', function(done) {
  427. this.req.session.destroy = sinon.stub().callsArgWith(0)
  428. this.SudoModeHandler.clearSudoMode = sinon.stub()
  429. this.res.redirect = url => {
  430. url.should.equal('/login')
  431. this.SudoModeHandler.clearSudoMode.callCount.should.equal(1)
  432. this.SudoModeHandler.clearSudoMode
  433. .calledWith(this.user._id)
  434. .should.equal(true)
  435. done()
  436. }
  437. this.UserController.logout(this.req, this.res)
  438. })
  439. it('should untrack session', function(done) {
  440. this.req.session.destroy = sinon.stub().callsArgWith(0)
  441. this.SudoModeHandler.clearSudoMode = sinon.stub()
  442. this.res.redirect = url => {
  443. url.should.equal('/login')
  444. this.UserSessionsManager.untrackSession.callCount.should.equal(1)
  445. this.UserSessionsManager.untrackSession
  446. .calledWith(sinon.match(this.req.user), this.req.sessionID)
  447. .should.equal(true)
  448. done()
  449. }
  450. this.UserController.logout(this.req, this.res)
  451. })
  452. it('should redirect after logout', function(done) {
  453. this.req.body.redirect = '/institutional-login'
  454. this.req.session.destroy = sinon.stub().callsArgWith(0)
  455. this.SudoModeHandler.clearSudoMode = sinon.stub()
  456. this.res.redirect = url => {
  457. url.should.equal(this.req.body.redirect)
  458. done()
  459. }
  460. this.UserController.logout(this.req, this.res)
  461. })
  462. it('should redirect to login after logout when no redirect set', function(done) {
  463. this.req.session.destroy = sinon.stub().callsArgWith(0)
  464. this.SudoModeHandler.clearSudoMode = sinon.stub()
  465. this.res.redirect = url => {
  466. url.should.equal('/login')
  467. done()
  468. }
  469. this.UserController.logout(this.req, this.res)
  470. })
  471. })
  472. describe('register', function() {
  473. beforeEach(function() {
  474. this.UserRegistrationHandler.registerNewUserAndSendActivationEmail = sinon
  475. .stub()
  476. .callsArgWith(1, null, this.user, (this.url = 'mock/url'))
  477. this.req.body.email = this.user.email = this.email = 'email@example.com'
  478. this.UserController.register(this.req, this.res)
  479. })
  480. it('should register the user and send them an email', function() {
  481. this.UserRegistrationHandler.registerNewUserAndSendActivationEmail
  482. .calledWith(this.email)
  483. .should.equal(true)
  484. })
  485. it('should return the user and activation url', function() {
  486. this.res.json
  487. .calledWith({
  488. email: this.email,
  489. setNewPasswordUrl: this.url
  490. })
  491. .should.equal(true)
  492. })
  493. })
  494. describe('clearSessions', function() {
  495. it('should call revokeAllUserSessions', function(done) {
  496. this.UserController.clearSessions(this.req, this.res)
  497. this.UserSessionsManager.revokeAllUserSessions.callCount.should.equal(1)
  498. done()
  499. })
  500. it('send a 201 response', function(done) {
  501. this.res.sendStatus = status => {
  502. status.should.equal(201)
  503. done()
  504. }
  505. this.UserController.clearSessions(this.req, this.res)
  506. })
  507. describe('when revokeAllUserSessions produces an error', function() {
  508. it('should call next with an error', function(done) {
  509. this.UserSessionsManager.revokeAllUserSessions.callsArgWith(
  510. 2,
  511. new Error('woops')
  512. )
  513. const next = err => {
  514. expect(err).to.not.equal(null)
  515. expect(err).to.be.instanceof(Error)
  516. done()
  517. }
  518. this.UserController.clearSessions(this.req, this.res, next)
  519. })
  520. })
  521. })
  522. describe('changePassword', function() {
  523. it('should check the old password is the current one at the moment', function() {
  524. this.AuthenticationManager.authenticate.yields()
  525. this.req.body = { currentPassword: 'oldpasshere' }
  526. this.UserController.changePassword(this.req, this.res, this.callback)
  527. this.AuthenticationManager.authenticate.should.have.been.calledWith(
  528. { _id: this.user._id },
  529. 'oldpasshere'
  530. )
  531. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  532. })
  533. it('it should not set the new password if they do not match', function() {
  534. this.AuthenticationManager.authenticate.yields(null, {})
  535. this.req.body = {
  536. newPassword1: '1',
  537. newPassword2: '2'
  538. }
  539. this.UserController.changePassword(this.req, this.res, this.callback)
  540. this.res.status.should.have.been.calledWith(400)
  541. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  542. })
  543. it('should set the new password if they do match', function() {
  544. this.AuthenticationManager.authenticate.yields(null, this.user)
  545. this.AuthenticationManager.setUserPassword.yields()
  546. this.req.body = {
  547. newPassword1: 'newpass',
  548. newPassword2: 'newpass'
  549. }
  550. this.UserController.changePassword(this.req, this.res, this.callback)
  551. this.AuthenticationManager.setUserPassword.should.have.been.calledWith(
  552. this.user._id,
  553. 'newpass'
  554. )
  555. })
  556. it('it should not set the new password if it is invalid', function() {
  557. this.AuthenticationManager.validatePassword = sinon
  558. .stub()
  559. .returns({ message: 'validation-error' })
  560. this.AuthenticationManager.authenticate.yields(null, {})
  561. this.req.body = {
  562. newPassword1: 'newpass',
  563. newPassword2: 'newpass'
  564. }
  565. this.UserController.changePassword(this.req, this.res, this.callback)
  566. this.AuthenticationManager.setUserPassword.callCount.should.equal(0)
  567. this.res.status.should.have.been.calledWith(400)
  568. this.res.json.should.have.been.calledWith({
  569. message: {
  570. type: 'error',
  571. text: 'validation-error'
  572. }
  573. })
  574. })
  575. })
  576. describe('ensureAffiliationMiddleware', function() {
  577. describe('without affiliations feature', function() {
  578. beforeEach(async function() {
  579. await this.UserController.promises.ensureAffiliationMiddleware(
  580. this.req,
  581. this.res,
  582. this.next
  583. )
  584. })
  585. it('should not run affiliation check', function() {
  586. expect(this.UserGetter.promises.getUser).to.not.have.been.called
  587. expect(this.UserUpdater.promises.confirmEmail).to.not.have.been.called
  588. expect(this.UserUpdater.promises.addAffiliationForNewUser).to.not.have
  589. .been.called
  590. })
  591. it('should not return an error', function() {
  592. expect(this.next).to.be.calledWith()
  593. })
  594. })
  595. describe('without ensureAffiliation query parameter', function() {
  596. beforeEach(async function() {
  597. this.Features.hasFeature.withArgs('affiliations').returns(true)
  598. await this.UserController.promises.ensureAffiliationMiddleware(
  599. this.req,
  600. this.res,
  601. this.next
  602. )
  603. })
  604. it('should not run middleware', function() {
  605. expect(this.UserGetter.promises.getUser).to.not.have.been.called
  606. expect(this.UserUpdater.promises.confirmEmail).to.not.have.been.called
  607. expect(this.UserUpdater.promises.addAffiliationForNewUser).to.not.have
  608. .been.called
  609. })
  610. it('should not return an error', function() {
  611. expect(this.next).to.be.calledWith()
  612. })
  613. })
  614. describe('no flagged email', function() {
  615. beforeEach(async function() {
  616. const email = 'unit-test@overleaf.com'
  617. this.user.email = email
  618. this.user.emails = [
  619. {
  620. email
  621. }
  622. ]
  623. this.Features.hasFeature.withArgs('affiliations').returns(true)
  624. this.req.query.ensureAffiliation = true
  625. await this.UserController.promises.ensureAffiliationMiddleware(
  626. this.req,
  627. this.res,
  628. this.next
  629. )
  630. })
  631. it('should get the user', function() {
  632. expect(this.UserGetter.promises.getUser).to.have.been.calledWith(
  633. this.user._id
  634. )
  635. })
  636. it('should not try to add affiliation or update user', function() {
  637. expect(this.UserUpdater.promises.addAffiliationForNewUser).to.not.have
  638. .been.called
  639. })
  640. it('should not return an error', function() {
  641. expect(this.next).to.be.calledWith()
  642. })
  643. })
  644. describe('flagged non-SSO email', function() {
  645. let emailFlagged
  646. beforeEach(async function() {
  647. emailFlagged = 'flagged@overleaf.com'
  648. this.user.email = emailFlagged
  649. this.user.emails = [
  650. {
  651. email: emailFlagged,
  652. affiliationUnchecked: true
  653. }
  654. ]
  655. this.Features.hasFeature.withArgs('affiliations').returns(true)
  656. this.req.query.ensureAffiliation = true
  657. await this.UserController.promises.ensureAffiliationMiddleware(
  658. this.req,
  659. this.res,
  660. this.next
  661. )
  662. })
  663. it('should unflag the emails but not confirm', function() {
  664. expect(
  665. this.UserUpdater.promises.addAffiliationForNewUser
  666. ).to.have.been.calledWith(this.user._id, emailFlagged)
  667. expect(
  668. this.UserUpdater.promises.confirmEmail
  669. ).to.not.have.been.calledWith(this.user._id, emailFlagged)
  670. })
  671. it('should not return an error', function() {
  672. expect(this.next).to.be.calledWith()
  673. })
  674. })
  675. describe('flagged SSO email', function() {
  676. let emailFlagged
  677. beforeEach(async function() {
  678. emailFlagged = 'flagged@overleaf.com'
  679. this.user.email = emailFlagged
  680. this.user.emails = [
  681. {
  682. email: emailFlagged,
  683. affiliationUnchecked: true,
  684. samlProviderId: '123'
  685. }
  686. ]
  687. this.Features.hasFeature.withArgs('affiliations').returns(true)
  688. this.req.query.ensureAffiliation = true
  689. await this.UserController.promises.ensureAffiliationMiddleware(
  690. this.req,
  691. this.res,
  692. this.next
  693. )
  694. })
  695. it('should add affiliation to v1, unflag and confirm on v2', function() {
  696. expect(this.UserUpdater.promises.addAffiliationForNewUser).to.have.not
  697. .been.called
  698. expect(this.UserUpdater.promises.confirmEmail).to.have.been.calledWith(
  699. this.user._id,
  700. emailFlagged
  701. )
  702. })
  703. it('should not return an error', function() {
  704. expect(this.next).to.be.calledWith()
  705. })
  706. })
  707. describe('when v1 returns an error', function() {
  708. let emailFlagged
  709. beforeEach(async function() {
  710. this.UserUpdater.promises.addAffiliationForNewUser.rejects()
  711. emailFlagged = 'flagged@overleaf.com'
  712. this.user.email = emailFlagged
  713. this.user.emails = [
  714. {
  715. email: emailFlagged,
  716. affiliationUnchecked: true
  717. }
  718. ]
  719. this.Features.hasFeature.withArgs('affiliations').returns(true)
  720. this.req.query.ensureAffiliation = true
  721. await this.UserController.promises.ensureAffiliationMiddleware(
  722. this.req,
  723. this.res,
  724. this.next
  725. )
  726. })
  727. it('should return the error', function() {
  728. expect(this.next).to.be.calledWith(sinon.match.instanceOf(Error))
  729. })
  730. })
  731. })
  732. })