UserRegistrationHandlerTests.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. const SandboxedModule = require('sandboxed-module')
  2. const assert = require('assert')
  3. const path = require('path')
  4. const modulePath = path.join(
  5. __dirname,
  6. '../../../../app/src/Features/User/UserRegistrationHandler'
  7. )
  8. const sinon = require('sinon')
  9. const { expect } = require('chai')
  10. const EmailHelper = require('../../../../app/src/Features/Helpers/EmailHelper')
  11. describe('UserRegistrationHandler', function () {
  12. beforeEach(function () {
  13. this.analyticsId = '123456'
  14. this.user = {
  15. _id: (this.user_id = '31j2lk21kjl'),
  16. analyticsId: this.analyticsId,
  17. }
  18. this.User = { updateOne: sinon.stub().callsArgWith(2) }
  19. this.UserGetter = { getUserByAnyEmail: sinon.stub() }
  20. this.UserCreator = {
  21. createNewUser: sinon.stub().callsArgWith(2, null, this.user),
  22. }
  23. this.AuthenticationManager = {
  24. validateEmail: sinon.stub().returns(null),
  25. validatePassword: sinon.stub().returns(null),
  26. setUserPassword: sinon.stub().callsArgWith(2),
  27. }
  28. this.NewsLetterManager = { subscribe: sinon.stub().callsArgWith(1) }
  29. this.EmailHandler = { sendEmail: sinon.stub().callsArgWith(2) }
  30. this.OneTimeTokenHandler = { getNewToken: sinon.stub() }
  31. this.handler = SandboxedModule.require(modulePath, {
  32. requires: {
  33. '../../models/User': { User: this.User },
  34. './UserGetter': this.UserGetter,
  35. './UserCreator': this.UserCreator,
  36. '../Authentication/AuthenticationManager': this.AuthenticationManager,
  37. '../Newsletter/NewsletterManager': this.NewsLetterManager,
  38. crypto: (this.crypto = {}),
  39. '../Email/EmailHandler': this.EmailHandler,
  40. '../Security/OneTimeTokenHandler': this.OneTimeTokenHandler,
  41. '../Analytics/AnalyticsManager': (this.AnalyticsManager = {
  42. recordEventForUser: sinon.stub(),
  43. setUserPropertyForUser: sinon.stub(),
  44. identifyUser: sinon.stub(),
  45. }),
  46. '@overleaf/settings': (this.settings = {
  47. siteUrl: 'http://sl.example.com',
  48. }),
  49. '../Helpers/EmailHelper': EmailHelper,
  50. },
  51. })
  52. this.passingRequest = {
  53. email: 'something@email.com',
  54. password: '123',
  55. analyticsId: this.analyticsId,
  56. }
  57. })
  58. describe('validate Register Request', function () {
  59. it('allows passing validation through', function () {
  60. const result = this.handler._registrationRequestIsValid(
  61. this.passingRequest
  62. )
  63. result.should.equal(true)
  64. })
  65. describe('failing email validation', function () {
  66. beforeEach(function () {
  67. this.AuthenticationManager.validateEmail.returns({
  68. message: 'email not set',
  69. })
  70. })
  71. it('does not allow through', function () {
  72. const result = this.handler._registrationRequestIsValid(
  73. this.passingRequest
  74. )
  75. return result.should.equal(false)
  76. })
  77. })
  78. describe('failing password validation', function () {
  79. beforeEach(function () {
  80. this.AuthenticationManager.validatePassword.returns({
  81. message: 'password is too short',
  82. })
  83. })
  84. it('does not allow through', function () {
  85. const result = this.handler._registrationRequestIsValid(
  86. this.passingRequest
  87. )
  88. result.should.equal(false)
  89. })
  90. })
  91. })
  92. describe('registerNewUser', function () {
  93. describe('holdingAccount', function (done) {
  94. beforeEach(function () {
  95. this.user.holdingAccount = true
  96. this.handler._registrationRequestIsValid = sinon.stub().returns(true)
  97. this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.user)
  98. })
  99. it('should not create a new user if there is a holding account there', function (done) {
  100. this.handler.registerNewUser(this.passingRequest, error => {
  101. this.UserCreator.createNewUser.called.should.equal(false)
  102. done(error)
  103. })
  104. })
  105. it('should set holding account to false', function (done) {
  106. this.handler.registerNewUser(this.passingRequest, error => {
  107. const update = this.User.updateOne.args[0]
  108. assert.deepEqual(update[0], { _id: this.user._id })
  109. assert.deepEqual(update[1], { $set: { holdingAccount: false } })
  110. done(error)
  111. })
  112. })
  113. })
  114. describe('invalidRequest', function () {
  115. it('should not create a new user if the the request is not valid', function (done) {
  116. this.handler._registrationRequestIsValid = sinon.stub().returns(false)
  117. this.handler.registerNewUser(this.passingRequest, error => {
  118. expect(error).to.be.instanceOf(Error)
  119. this.UserCreator.createNewUser.called.should.equal(false)
  120. done()
  121. })
  122. })
  123. it('should return email registered in the error if there is a non holdingAccount there', function (done) {
  124. this.UserGetter.getUserByAnyEmail.callsArgWith(
  125. 1,
  126. null,
  127. (this.user = { holdingAccount: false })
  128. )
  129. this.handler.registerNewUser(this.passingRequest, (err, user) => {
  130. expect(err).to.be.instanceOf(Error)
  131. expect(err).to.have.property('message', 'EmailAlreadyRegistered')
  132. user.should.deep.equal(this.user)
  133. done()
  134. })
  135. })
  136. })
  137. describe('validRequest', function () {
  138. beforeEach(function () {
  139. this.handler._registrationRequestIsValid = sinon.stub().returns(true)
  140. this.UserGetter.getUserByAnyEmail.callsArgWith(1)
  141. })
  142. it('should create a new user', function (done) {
  143. this.handler.registerNewUser(this.passingRequest, error => {
  144. sinon.assert.calledWith(this.UserCreator.createNewUser, {
  145. email: this.passingRequest.email,
  146. holdingAccount: false,
  147. first_name: this.passingRequest.first_name,
  148. last_name: this.passingRequest.last_name,
  149. analyticsId: this.user.analyticsId,
  150. })
  151. done(error)
  152. })
  153. })
  154. it('lower case email', function (done) {
  155. this.passingRequest.email = 'soMe@eMail.cOm'
  156. this.handler.registerNewUser(this.passingRequest, error => {
  157. this.UserCreator.createNewUser.args[0][0].email.should.equal(
  158. 'some@email.com'
  159. )
  160. done(error)
  161. })
  162. })
  163. it('trim white space from email', function (done) {
  164. this.passingRequest.email = ' some@email.com '
  165. this.handler.registerNewUser(this.passingRequest, error => {
  166. this.UserCreator.createNewUser.args[0][0].email.should.equal(
  167. 'some@email.com'
  168. )
  169. done(error)
  170. })
  171. })
  172. it('should set the password', function (done) {
  173. this.handler.registerNewUser(this.passingRequest, error => {
  174. this.AuthenticationManager.setUserPassword
  175. .calledWith(this.user, this.passingRequest.password)
  176. .should.equal(true)
  177. done(error)
  178. })
  179. })
  180. it('should add the user to the newsletter if accepted terms', function (done) {
  181. this.passingRequest.subscribeToNewsletter = 'true'
  182. this.handler.registerNewUser(this.passingRequest, error => {
  183. this.NewsLetterManager.subscribe
  184. .calledWith(this.user)
  185. .should.equal(true)
  186. done(error)
  187. })
  188. })
  189. it('should not add the user to the newsletter if not accepted terms', function (done) {
  190. this.handler.registerNewUser(this.passingRequest, error => {
  191. this.NewsLetterManager.subscribe
  192. .calledWith(this.user)
  193. .should.equal(false)
  194. done(error)
  195. })
  196. })
  197. })
  198. })
  199. describe('registerNewUserAndSendActivationEmail', function () {
  200. beforeEach(function () {
  201. this.email = 'Email@example.com'
  202. this.crypto.randomBytes = sinon.stub().returns({
  203. toString: () => {
  204. return (this.password = 'mock-password')
  205. },
  206. })
  207. this.OneTimeTokenHandler.getNewToken.yields(
  208. null,
  209. (this.token = 'mock-token')
  210. )
  211. this.handler.registerNewUser = sinon.stub()
  212. this.callback = sinon.stub()
  213. })
  214. describe('with a new user', function () {
  215. beforeEach(function () {
  216. this.user.email = this.email.toLowerCase()
  217. this.handler.registerNewUser.callsArgWith(1, null, this.user)
  218. this.handler.registerNewUserAndSendActivationEmail(
  219. this.email,
  220. this.callback
  221. )
  222. })
  223. it('should ask the UserRegistrationHandler to register user', function () {
  224. sinon.assert.calledWith(this.handler.registerNewUser, {
  225. email: this.email,
  226. password: this.password,
  227. })
  228. })
  229. it('should generate a new password reset token', function () {
  230. const data = {
  231. user_id: this.user._id.toString(),
  232. email: this.user.email,
  233. }
  234. this.OneTimeTokenHandler.getNewToken
  235. .calledWith('password', data, { expiresIn: 7 * 24 * 60 * 60 })
  236. .should.equal(true)
  237. })
  238. it('should send a registered email', function () {
  239. this.EmailHandler.sendEmail
  240. .calledWith('registered', {
  241. to: this.user.email,
  242. setNewPasswordUrl: `${this.settings.siteUrl}/user/activate?token=${this.token}&user_id=${this.user_id}`,
  243. })
  244. .should.equal(true)
  245. })
  246. it('should return the user', function () {
  247. this.callback
  248. .calledWith(
  249. null,
  250. this.user,
  251. `${this.settings.siteUrl}/user/activate?token=${this.token}&user_id=${this.user_id}`
  252. )
  253. .should.equal(true)
  254. })
  255. })
  256. describe('with a user that already exists', function () {
  257. beforeEach(function () {
  258. this.handler.registerNewUser.callsArgWith(
  259. 1,
  260. new Error('EmailAlreadyRegistered'),
  261. this.user
  262. )
  263. this.handler.registerNewUserAndSendActivationEmail(
  264. this.email,
  265. this.callback
  266. )
  267. })
  268. it('should still generate a new password token and email', function () {
  269. this.OneTimeTokenHandler.getNewToken.called.should.equal(true)
  270. this.EmailHandler.sendEmail.called.should.equal(true)
  271. })
  272. })
  273. })
  274. })