UserCreatorTests.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const { assert } = require('chai')
  4. const modulePath = '../../../../app/src/Features/User/UserCreator.js'
  5. describe('UserCreator', function () {
  6. beforeEach(function () {
  7. const self = this
  8. this.user = { _id: '12390i', ace: {} }
  9. this.user.save = sinon.stub().resolves(self.user)
  10. this.UserModel = class Project {
  11. constructor() {
  12. return self.user
  13. }
  14. }
  15. this.UserCreator = SandboxedModule.require(modulePath, {
  16. requires: {
  17. '../../models/User': {
  18. User: this.UserModel,
  19. },
  20. '@overleaf/metrics': { timeAsyncMethod() {} },
  21. '../../infrastructure/Features': (this.Features = {
  22. hasFeature: sinon.stub().returns(false),
  23. }),
  24. './UserDeleter': (this.UserDeleter = {
  25. promises: {
  26. deleteNewUser: sinon.stub().resolves(),
  27. },
  28. }),
  29. './UserGetter': (this.UserGetter = {
  30. promises: {
  31. getUser: sinon.stub().resolves(this.user),
  32. },
  33. }),
  34. './UserUpdater': (this.UserUpdater = {
  35. promises: {
  36. addAffiliationForNewUser: sinon.stub().resolves({
  37. matchedCount: 1,
  38. modifiedCount: 1,
  39. acknowledged: true,
  40. }),
  41. updateUser: sinon.stub().resolves(),
  42. },
  43. }),
  44. '../Analytics/AnalyticsManager': (this.Analytics = {
  45. recordEventForUser: sinon.stub(),
  46. setUserPropertyForUser: sinon.stub(),
  47. }),
  48. '../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
  49. promises: {
  50. getAssignmentForUser: sinon.stub().resolves({ variant: 'active' }),
  51. },
  52. }),
  53. './UserOnboardingEmailManager': (this.UserOnboardingEmailManager = {
  54. scheduleOnboardingEmail: sinon.stub(),
  55. }),
  56. './UserPostRegistrationAnalyticsManager':
  57. (this.UserPostRegistrationAnalyticsManager = {
  58. schedulePostRegistrationAnalytics: sinon.stub(),
  59. }),
  60. },
  61. })
  62. this.email = 'bob.oswald@gmail.com'
  63. })
  64. describe('createNewUser', function () {
  65. describe('with callbacks', function () {
  66. it('should take the opts and put them in the model', async function () {
  67. const user = await this.UserCreator.promises.createNewUser({
  68. email: this.email,
  69. holdingAccount: true,
  70. })
  71. assert.equal(user.email, this.email)
  72. assert.equal(user.holdingAccount, true)
  73. assert.equal(user.first_name, 'bob.oswald')
  74. })
  75. it('should use the start of the email if the first name is empty string', async function () {
  76. const user = await this.UserCreator.promises.createNewUser({
  77. email: this.email,
  78. holdingAccount: true,
  79. first_name: '',
  80. })
  81. assert.equal(user.email, this.email)
  82. assert.equal(user.holdingAccount, true)
  83. assert.equal(user.first_name, 'bob.oswald')
  84. })
  85. it('should use the first name if passed', async function () {
  86. const user = await this.UserCreator.promises.createNewUser({
  87. email: this.email,
  88. holdingAccount: true,
  89. first_name: 'fiiirstname',
  90. })
  91. assert.equal(user.email, this.email)
  92. assert.equal(user.holdingAccount, true)
  93. assert.equal(user.first_name, 'fiiirstname')
  94. })
  95. it('should use the last name if passed', async function () {
  96. const user = await this.UserCreator.promises.createNewUser({
  97. email: this.email,
  98. holdingAccount: true,
  99. last_name: 'lastNammmmeee',
  100. })
  101. assert.equal(user.email, this.email)
  102. assert.equal(user.holdingAccount, true)
  103. assert.equal(user.last_name, 'lastNammmmeee')
  104. })
  105. it('should set emails attribute', async function () {
  106. const user = await this.UserCreator.promises.createNewUser({
  107. email: this.email,
  108. })
  109. user.email.should.equal(this.email)
  110. user.emails.length.should.equal(1)
  111. user.emails[0].email.should.equal(this.email)
  112. user.emails[0].createdAt.should.be.a('date')
  113. user.emails[0].reversedHostname.should.equal('moc.liamg')
  114. })
  115. describe('with affiliations feature', function () {
  116. let attributes, user
  117. beforeEach(function () {
  118. attributes = { email: this.email }
  119. this.Features.hasFeature = sinon
  120. .stub()
  121. .withArgs('affiliations')
  122. .returns(true)
  123. })
  124. describe('when v1 affiliations API does not return an error', function () {
  125. beforeEach(async function () {
  126. user = await this.UserCreator.promises.createNewUser(attributes)
  127. })
  128. it('should flag that affiliation is unchecked', function () {
  129. user.emails[0].affiliationUnchecked.should.equal(true)
  130. })
  131. it('should try to add affiliation to v1', function () {
  132. sinon.assert.calledOnce(
  133. this.UserUpdater.promises.addAffiliationForNewUser
  134. )
  135. sinon.assert.calledWithMatch(
  136. this.UserUpdater.promises.addAffiliationForNewUser,
  137. user._id,
  138. this.email
  139. )
  140. })
  141. it('should query for updated user data', function () {
  142. sinon.assert.calledOnce(this.UserGetter.promises.getUser)
  143. })
  144. })
  145. describe('when v1 affiliations API does return an error', function () {
  146. beforeEach(async function () {
  147. this.UserUpdater.promises.addAffiliationForNewUser.rejects()
  148. user = await this.UserCreator.promises.createNewUser(attributes)
  149. })
  150. it('should flag that affiliation is unchecked', function () {
  151. user.emails[0].affiliationUnchecked.should.equal(true)
  152. })
  153. it('should try to add affiliation to v1', function () {
  154. sinon.assert.calledOnce(
  155. this.UserUpdater.promises.addAffiliationForNewUser
  156. )
  157. sinon.assert.calledWithMatch(
  158. this.UserUpdater.promises.addAffiliationForNewUser,
  159. user._id,
  160. this.email
  161. )
  162. })
  163. it('should not query for updated user data', function () {
  164. sinon.assert.notCalled(this.UserGetter.promises.getUser)
  165. })
  166. it('should log error', function () {
  167. sinon.assert.calledOnce(this.logger.error)
  168. })
  169. })
  170. describe('when v1 affiliations API returns an error and requireAffiliation=true', function () {
  171. beforeEach(async function () {
  172. this.UserUpdater.promises.addAffiliationForNewUser.rejects()
  173. user = await this.UserCreator.promises.createNewUser(attributes)
  174. })
  175. it('should flag that affiliation is unchecked', function () {
  176. user.emails[0].affiliationUnchecked.should.equal(true)
  177. })
  178. it('should try to add affiliation to v1', function () {
  179. sinon.assert.calledOnce(
  180. this.UserUpdater.promises.addAffiliationForNewUser
  181. )
  182. sinon.assert.calledWithMatch(
  183. this.UserUpdater.promises.addAffiliationForNewUser,
  184. user._id,
  185. this.email
  186. )
  187. })
  188. it('should not query for updated user data', function () {
  189. sinon.assert.notCalled(this.UserGetter.promises.getUser)
  190. })
  191. it('should log error', function () {
  192. sinon.assert.calledOnce(this.logger.error)
  193. })
  194. })
  195. })
  196. it('should not add affiliation when without affiliation feature', async function () {
  197. const attributes = { email: this.email }
  198. await this.UserCreator.promises.createNewUser(attributes)
  199. sinon.assert.notCalled(
  200. this.UserUpdater.promises.addAffiliationForNewUser
  201. )
  202. })
  203. })
  204. describe('with promises', function () {
  205. it('should take the opts and put them in the model', async function () {
  206. const opts = {
  207. email: this.email,
  208. holdingAccount: true,
  209. }
  210. const user = await this.UserCreator.promises.createNewUser(opts)
  211. assert.equal(user.email, this.email)
  212. assert.equal(user.holdingAccount, true)
  213. assert.equal(user.first_name, 'bob.oswald')
  214. })
  215. it('should add affiliation when with affiliation feature', async function () {
  216. this.Features.hasFeature = sinon
  217. .stub()
  218. .withArgs('affiliations')
  219. .returns(true)
  220. const attributes = { email: this.email }
  221. const user = await this.UserCreator.promises.createNewUser(attributes)
  222. sinon.assert.calledOnce(
  223. this.UserUpdater.promises.addAffiliationForNewUser
  224. )
  225. sinon.assert.calledWithMatch(
  226. this.UserUpdater.promises.addAffiliationForNewUser,
  227. user._id,
  228. this.email
  229. )
  230. })
  231. it('should not add affiliation when without affiliation feature', async function () {
  232. this.Features.hasFeature = sinon.stub().returns(false)
  233. const attributes = { email: this.email }
  234. await this.UserCreator.promises.createNewUser(attributes)
  235. sinon.assert.notCalled(
  236. this.UserUpdater.promises.addAffiliationForNewUser
  237. )
  238. })
  239. it('should include SAML provider ID with email', async function () {
  240. const attributes = {
  241. email: this.email,
  242. samlIdentifiers: [{ email: this.email, providerId: '1' }],
  243. }
  244. const user = await this.UserCreator.promises.createNewUser(attributes)
  245. assert.equal(user.emails[0].samlProviderId, '1')
  246. })
  247. it('should fire an analytics event and user property on registration', async function () {
  248. const user = await this.UserCreator.promises.createNewUser({
  249. email: this.email,
  250. })
  251. assert.equal(user.email, this.email)
  252. sinon.assert.calledWith(
  253. this.Analytics.recordEventForUser,
  254. user._id,
  255. 'user-registered'
  256. )
  257. sinon.assert.calledWith(
  258. this.Analytics.setUserPropertyForUser,
  259. user._id,
  260. 'created-at'
  261. )
  262. })
  263. it('should schedule post registration jobs on registration with saas feature', async function () {
  264. this.Features.hasFeature = sinon.stub().withArgs('saas').returns(true)
  265. const user = await this.UserCreator.promises.createNewUser({
  266. email: this.email,
  267. })
  268. assert.equal(user.email, this.email)
  269. sinon.assert.calledWith(
  270. this.UserOnboardingEmailManager.scheduleOnboardingEmail,
  271. user
  272. )
  273. sinon.assert.calledWith(
  274. this.UserPostRegistrationAnalyticsManager
  275. .schedulePostRegistrationAnalytics,
  276. user
  277. )
  278. })
  279. it('should not schedule post registration checks when without saas feature', async function () {
  280. const attributes = { email: this.email }
  281. await this.UserCreator.promises.createNewUser(attributes)
  282. sinon.assert.notCalled(
  283. this.UserOnboardingEmailManager.scheduleOnboardingEmail
  284. )
  285. sinon.assert.notCalled(
  286. this.UserPostRegistrationAnalyticsManager
  287. .schedulePostRegistrationAnalytics
  288. )
  289. })
  290. })
  291. })
  292. })