const SandboxedModule = require('sandboxed-module') const chai = require('chai') const sinon = require('sinon') const { assert } = chai const modulePath = '../../../../app/src/Features/User/UserCreator.js' describe('UserCreator', function() { beforeEach(function() { const self = this this.user = { _id: '12390i', ace: {} } this.user.save = sinon.stub().resolves(self.user) this.UserModel = class Project { constructor() { return self.user } } this.UserCreator = SandboxedModule.require(modulePath, { globals: { console: console }, requires: { '../../models/User': { User: this.UserModel }, 'logger-sharelatex': (this.Logger = { error: sinon.stub() }), '@overleaf/metrics': { timeAsyncMethod() {} }, '../../infrastructure/Features': (this.Features = { hasFeature: sinon.stub().returns(false) }), './UserDeleter': (this.UserDeleter = { promises: { deleteNewUser: sinon.stub().resolves() } }), './UserGetter': (this.UserGetter = { promises: { getUser: sinon.stub().resolves(this.user) } }), './UserUpdater': (this.UserUpdater = { promises: { addAffiliationForNewUser: sinon .stub() .resolves({ n: 1, nModified: 1, ok: 1 }), updateUser: sinon.stub().resolves() } }), '../Analytics/AnalyticsManager': (this.Analytics = { recordEvent: sinon.stub() }), './UserOnboardingEmailManager': (this.UserOnboardingEmailManager = { scheduleOnboardingEmail: sinon.stub() }) } }) this.email = 'bob.oswald@gmail.com' }) describe('createNewUser', function() { describe('with callbacks', function() { it('should take the opts and put them in the model', async function() { const user = await this.UserCreator.promises.createNewUser({ email: this.email, holdingAccount: true }) assert.equal(user.email, this.email) assert.equal(user.holdingAccount, true) assert.equal(user.first_name, 'bob.oswald') }) it('should use the start of the email if the first name is empty string', async function() { const user = await this.UserCreator.promises.createNewUser({ email: this.email, holdingAccount: true, first_name: '' }) assert.equal(user.email, this.email) assert.equal(user.holdingAccount, true) assert.equal(user.first_name, 'bob.oswald') }) it('should use the first name if passed', async function() { const user = await this.UserCreator.promises.createNewUser({ email: this.email, holdingAccount: true, first_name: 'fiiirstname' }) assert.equal(user.email, this.email) assert.equal(user.holdingAccount, true) assert.equal(user.first_name, 'fiiirstname') }) it('should use the last name if passed', async function() { const user = await this.UserCreator.promises.createNewUser({ email: this.email, holdingAccount: true, last_name: 'lastNammmmeee' }) assert.equal(user.email, this.email) assert.equal(user.holdingAccount, true) assert.equal(user.last_name, 'lastNammmmeee') }) it('should set emails attribute', async function() { const user = await this.UserCreator.promises.createNewUser({ email: this.email }) user.email.should.equal(this.email) user.emails.length.should.equal(1) user.emails[0].email.should.equal(this.email) user.emails[0].createdAt.should.be.a('date') user.emails[0].reversedHostname.should.equal('moc.liamg') }) describe('with affiliations feature', function() { let attributes, user beforeEach(function() { attributes = { email: this.email } this.Features.hasFeature = sinon .stub() .withArgs('affiliations') .returns(true) }) describe('when v1 affiliations API does not return an error', function() { beforeEach(async function() { user = await this.UserCreator.promises.createNewUser(attributes) }) it('should flag that affiliation is unchecked', function() { user.emails[0].affiliationUnchecked.should.equal(true) }) it('should try to add affiliation to v1', function() { sinon.assert.calledOnce( this.UserUpdater.promises.addAffiliationForNewUser ) sinon.assert.calledWithMatch( this.UserUpdater.promises.addAffiliationForNewUser, user._id, this.email ) }) it('should query for updated user data', function() { sinon.assert.calledOnce(this.UserGetter.promises.getUser) }) }) describe('when v1 affiliations API does return an error', function() { beforeEach(async function() { this.UserUpdater.promises.addAffiliationForNewUser.rejects() user = await this.UserCreator.promises.createNewUser(attributes) }) it('should flag that affiliation is unchecked', function() { user.emails[0].affiliationUnchecked.should.equal(true) }) it('should try to add affiliation to v1', function() { sinon.assert.calledOnce( this.UserUpdater.promises.addAffiliationForNewUser ) sinon.assert.calledWithMatch( this.UserUpdater.promises.addAffiliationForNewUser, user._id, this.email ) }) it('should not query for updated user data', function() { sinon.assert.notCalled(this.UserGetter.promises.getUser) }) it('should log error', function() { sinon.assert.calledOnce(this.Logger.error) }) }) describe('when v1 affiliations API returns an error and requireAffiliation=true', function() { beforeEach(async function() { this.UserUpdater.promises.addAffiliationForNewUser.rejects() user = await this.UserCreator.promises.createNewUser(attributes) }) it('should flag that affiliation is unchecked', function() { user.emails[0].affiliationUnchecked.should.equal(true) }) it('should try to add affiliation to v1', function() { sinon.assert.calledOnce( this.UserUpdater.promises.addAffiliationForNewUser ) sinon.assert.calledWithMatch( this.UserUpdater.promises.addAffiliationForNewUser, user._id, this.email ) }) it('should not query for updated user data', function() { sinon.assert.notCalled(this.UserGetter.promises.getUser) }) it('should log error', function() { sinon.assert.calledOnce(this.Logger.error) }) }) }) it('should not add affiliation when without affiliation feature', async function() { const attributes = { email: this.email } await this.UserCreator.promises.createNewUser(attributes) sinon.assert.notCalled( this.UserUpdater.promises.addAffiliationForNewUser ) }) }) describe('with promises', function() { it('should take the opts and put them in the model', async function() { const opts = { email: this.email, holdingAccount: true } const user = await this.UserCreator.promises.createNewUser(opts) assert.equal(user.email, this.email) assert.equal(user.holdingAccount, true) assert.equal(user.first_name, 'bob.oswald') }) it('should add affiliation when with affiliation feature', async function() { this.Features.hasFeature = sinon .stub() .withArgs('affiliations') .returns(true) const attributes = { email: this.email } const user = await this.UserCreator.promises.createNewUser(attributes) sinon.assert.calledOnce( this.UserUpdater.promises.addAffiliationForNewUser ) sinon.assert.calledWithMatch( this.UserUpdater.promises.addAffiliationForNewUser, user._id, this.email ) }) it('should not add affiliation when without affiliation feature', async function() { this.Features.hasFeature = sinon.stub().returns(false) const attributes = { email: this.email } await this.UserCreator.promises.createNewUser(attributes) sinon.assert.notCalled( this.UserUpdater.promises.addAffiliationForNewUser ) }) it('should include SAML provider ID with email', async function() { const attributes = { email: this.email, samlIdentifiers: [{ email: this.email, providerId: '1' }] } const user = await this.UserCreator.promises.createNewUser(attributes) assert.equal(user.emails[0].samlProviderId, '1') }) it('should fire an analytics event on registration', async function() { const user = await this.UserCreator.promises.createNewUser({ email: this.email }) assert.equal(user.email, this.email) sinon.assert.calledWith( this.Analytics.recordEvent, user._id, 'user-registered' ) }) it('should schedule an onboarding email on registration', async function() { const user = await this.UserCreator.promises.createNewUser({ email: this.email }) assert.equal(user.email, this.email) sinon.assert.calledWith( this.UserOnboardingEmailManager.scheduleOnboardingEmail, user ) }) }) }) })