UserCreator.test.mjs 12 KB

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