| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- import { vi, expect } from 'vitest'
- import assert from 'node:assert'
- import sinon from 'sinon'
- import EmailHelper from '../../../../app/src/Features/Helpers/EmailHelper.mjs'
- const modulePath = '../../../../app/src/Features/User/UserRegistrationHandler'
- describe('UserRegistrationHandler', function () {
- beforeEach(async function (ctx) {
- ctx.analyticsId = '123456'
- ctx.user = {
- _id: (ctx.user_id = '31j2lk21kjl'),
- analyticsId: ctx.analyticsId,
- }
- ctx.User = {
- updateOne: sinon.stub().returns({ exec: sinon.stub().resolves() }),
- }
- ctx.UserGetter = {
- promises: {
- getUserByAnyEmail: sinon.stub(),
- },
- }
- ctx.UserCreator = {
- promises: {
- createNewUser: sinon.stub().resolves(ctx.user),
- },
- }
- ctx.AuthenticationManager = {
- validateEmail: sinon.stub().returns(null),
- validatePassword: sinon.stub().returns(null),
- promises: {
- setUserPassword: sinon.stub().resolves(ctx.user),
- },
- }
- ctx.Modules = {
- promises: {
- hooks: {
- fire: sinon.stub().resolves([]),
- },
- },
- }
- ctx.EmailHandler = {
- promises: { sendEmail: sinon.stub().resolves() },
- }
- ctx.OneTimeTokenHandler = { promises: { getNewToken: sinon.stub() } }
- vi.doMock('../../../../app/src/models/User', () => ({
- User: ctx.User,
- }))
- vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
- default: ctx.UserGetter,
- }))
- vi.doMock('../../../../app/src/Features/User/UserCreator', () => ({
- default: ctx.UserCreator,
- }))
- vi.doMock(
- '../../../../app/src/Features/Authentication/AuthenticationManager',
- () => ({
- default: ctx.AuthenticationManager,
- })
- )
- vi.doMock('../../../../app/src/infrastructure/Modules', () => ({
- default: ctx.Modules,
- }))
- vi.doMock('crypto', () => ({
- default: (ctx.crypto = {
- randomUUID: sinon
- .stub()
- .returns('8055c676-bcc7-4e64-a66f-8069f9a0bd92'),
- }),
- }))
- vi.doMock('../../../../app/src/Features/Email/EmailHandler', () => ({
- default: ctx.EmailHandler,
- }))
- vi.doMock(
- '../../../../app/src/Features/Security/OneTimeTokenHandler',
- () => ({
- default: ctx.OneTimeTokenHandler,
- })
- )
- vi.doMock(
- '../../../../app/src/Features/Analytics/AnalyticsManager',
- () => ({
- default: (ctx.AnalyticsManager = {
- recordEventForUser: sinon.stub(),
- setUserPropertyForUser: sinon.stub(),
- identifyUser: sinon.stub(),
- }),
- })
- )
- vi.doMock('@overleaf/settings', () => ({
- default: (ctx.settings = {
- siteUrl: 'http://sl.example.com',
- }),
- }))
- vi.doMock('../../../../app/src/Features/Helpers/EmailHelper', () => ({
- default: EmailHelper,
- }))
- ctx.handler = (await import(modulePath)).default
- ctx.passingRequest = {
- email: 'something@email.com',
- password: '123',
- analyticsId: ctx.analyticsId,
- }
- })
- describe('validate Register Request', function () {
- it('allows passing validation through', function (ctx) {
- const result = ctx.handler.promises._registrationRequestIsValid(
- ctx.passingRequest
- )
- result.should.equal(true)
- })
- describe('failing email validation', function () {
- beforeEach(function (ctx) {
- ctx.AuthenticationManager.validateEmail.returns({
- message: 'email not set',
- })
- })
- it('does not allow through', function (ctx) {
- const result = ctx.handler.promises._registrationRequestIsValid(
- ctx.passingRequest
- )
- return result.should.equal(false)
- })
- })
- describe('failing password validation', function () {
- beforeEach(function (ctx) {
- ctx.AuthenticationManager.validatePassword.returns({
- message: 'password is too short',
- })
- })
- it('does not allow through', function (ctx) {
- const result = ctx.handler.promises._registrationRequestIsValid(
- ctx.passingRequest
- )
- result.should.equal(false)
- })
- })
- })
- describe('registerNewUser', function () {
- describe('holdingAccount', function (done) {
- beforeEach(function (ctx) {
- ctx.user.holdingAccount = true
- ctx.handler.promises._registrationRequestIsValid = sinon
- .stub()
- .returns(true)
- ctx.UserGetter.promises.getUserByAnyEmail.resolves(ctx.user)
- })
- it('should not create a new user if there is a holding account there', async function (ctx) {
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- ctx.UserCreator.promises.createNewUser.called.should.equal(false)
- })
- it('should set holding account to false', async function (ctx) {
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- const update = ctx.User.updateOne.args[0]
- assert.deepEqual(update[0], { _id: ctx.user._id })
- assert.deepEqual(update[1], { $set: { holdingAccount: false } })
- })
- })
- describe('invalidRequest', function () {
- it('should not create a new user if the the request is not valid', async function (ctx) {
- ctx.handler.promises._registrationRequestIsValid = sinon
- .stub()
- .returns(false)
- expect(ctx.handler.promises.registerNewUser(ctx.passingRequest)).to.be
- .rejected
- ctx.UserCreator.promises.createNewUser.called.should.equal(false)
- })
- it('should return email registered in the error if there is a non holdingAccount there', async function (ctx) {
- ctx.UserGetter.promises.getUserByAnyEmail.resolves(
- (ctx.user = { holdingAccount: false })
- )
- expect(
- ctx.handler.promises.registerNewUser(ctx.passingRequest)
- ).to.be.rejectedWith('EmailAlreadyRegistered')
- })
- })
- describe('validRequest', function () {
- beforeEach(function (ctx) {
- ctx.handler.promises._registrationRequestIsValid = sinon
- .stub()
- .returns(true)
- ctx.UserGetter.promises.getUserByAnyEmail.resolves()
- })
- it('should create a new user', async function (ctx) {
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- sinon.assert.calledWith(ctx.UserCreator.promises.createNewUser, {
- email: ctx.passingRequest.email,
- holdingAccount: false,
- first_name: ctx.passingRequest.first_name,
- last_name: ctx.passingRequest.last_name,
- analyticsId: ctx.user.analyticsId,
- })
- })
- it('lower case email', async function (ctx) {
- ctx.passingRequest.email = 'soMe@eMail.cOm'
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- ctx.UserCreator.promises.createNewUser.args[0][0].email.should.equal(
- 'some@email.com'
- )
- })
- it('trim white space from email', async function (ctx) {
- ctx.passingRequest.email = ' some@email.com '
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- ctx.UserCreator.promises.createNewUser.args[0][0].email.should.equal(
- 'some@email.com'
- )
- })
- it('should set the password', async function (ctx) {
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- ctx.AuthenticationManager.promises.setUserPassword
- .calledWith(ctx.user, ctx.passingRequest.password)
- .should.equal(true)
- })
- it('should add the user to the newsletter if accepted terms', async function (ctx) {
- ctx.passingRequest.subscribeToNewsletter = 'true'
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
- 'updateTopicSubscription',
- ctx.user._id,
- 'newsletter',
- true
- )
- })
- it('should not add the user to the newsletter if not accepted terms', async function (ctx) {
- await ctx.handler.promises.registerNewUser(ctx.passingRequest)
- expect(ctx.Modules.promises.hooks.fire).to.not.have.been.calledWith(
- 'updateTopicSubscription',
- sinon.match.any,
- sinon.match.any,
- true
- )
- })
- })
- })
- describe('registerNewUserAndSendActivationEmail', function () {
- beforeEach(function (ctx) {
- ctx.email = 'Email@example.com'
- ctx.crypto.randomBytes = sinon.stub().returns({
- toString: () => {
- return (ctx.password = 'mock-password')
- },
- })
- ctx.OneTimeTokenHandler.promises.getNewToken.resolves(
- (ctx.token = 'mock-token')
- )
- ctx.handler.promises.registerNewUser = sinon.stub()
- })
- describe('with a new user', function () {
- beforeEach(async function (ctx) {
- ctx.user.email = ctx.email.toLowerCase()
- ctx.handler.promises.registerNewUser.resolves(ctx.user)
- ctx.result =
- await ctx.handler.promises.registerNewUserAndSendActivationEmail(
- ctx.email
- )
- })
- it('should ask the UserRegistrationHandler to register user', function (ctx) {
- sinon.assert.calledWith(ctx.handler.promises.registerNewUser, {
- email: ctx.email,
- password: ctx.password,
- analyticsId: '8055c676-bcc7-4e64-a66f-8069f9a0bd92',
- })
- })
- it('should generate a new password reset token', function (ctx) {
- const data = {
- user_id: ctx.user._id.toString(),
- email: ctx.user.email,
- }
- ctx.OneTimeTokenHandler.promises.getNewToken
- .calledWith('password', data, { expiresIn: 7 * 24 * 60 * 60 })
- .should.equal(true)
- })
- it('should send a registered email', function (ctx) {
- ctx.EmailHandler.promises.sendEmail
- .calledWith('registered', {
- to: ctx.user.email,
- setNewPasswordUrl: `${ctx.settings.siteUrl}/user/activate?token=${ctx.token}&user_id=${ctx.user_id}`,
- })
- .should.equal(true)
- })
- it('should return the user and new password url', function (ctx) {
- const { user, setNewPasswordUrl } = ctx.result
- expect(user).to.deep.equal(ctx.user)
- expect(setNewPasswordUrl).to.equal(
- `${ctx.settings.siteUrl}/user/activate?token=${ctx.token}&user_id=${ctx.user_id}`
- )
- })
- })
- describe('with a user that already exists', function () {
- beforeEach(async function (ctx) {
- ctx.handler.promises.registerNewUser.rejects(
- new Error('EmailAlreadyRegistered')
- )
- ctx.UserGetter.promises.getUserByAnyEmail.resolves(ctx.user)
- await ctx.handler.promises.registerNewUserAndSendActivationEmail(
- ctx.email
- )
- })
- it('should still generate a new password token and email', function (ctx) {
- ctx.OneTimeTokenHandler.promises.getNewToken.called.should.equal(true)
- ctx.EmailHandler.promises.sendEmail.called.should.equal(true)
- })
- })
- })
- })
|