UserCreator.test.mjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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. })
  92. describe('createNewUser', function () {
  93. describe('with callbacks', function () {
  94. it('should take the opts and put them in the model', async function (ctx) {
  95. const user = await ctx.UserCreator.promises.createNewUser({
  96. email: ctx.email,
  97. holdingAccount: true,
  98. })
  99. assert.equal(user.email, ctx.email)
  100. assert.equal(user.holdingAccount, true)
  101. assert.equal(user.first_name, 'bob.oswald')
  102. })
  103. it('should use the start of the email if the first name is empty string', async function (ctx) {
  104. const user = await ctx.UserCreator.promises.createNewUser({
  105. email: ctx.email,
  106. holdingAccount: true,
  107. first_name: '',
  108. })
  109. assert.equal(user.email, ctx.email)
  110. assert.equal(user.holdingAccount, true)
  111. assert.equal(user.first_name, 'bob.oswald')
  112. })
  113. it('should use the first name if passed', async function (ctx) {
  114. const user = await ctx.UserCreator.promises.createNewUser({
  115. email: ctx.email,
  116. holdingAccount: true,
  117. first_name: 'fiiirstname',
  118. })
  119. assert.equal(user.email, ctx.email)
  120. assert.equal(user.holdingAccount, true)
  121. assert.equal(user.first_name, 'fiiirstname')
  122. })
  123. it('should use the last name if passed', async function (ctx) {
  124. const user = await ctx.UserCreator.promises.createNewUser({
  125. email: ctx.email,
  126. holdingAccount: true,
  127. last_name: 'lastNammmmeee',
  128. })
  129. assert.equal(user.email, ctx.email)
  130. assert.equal(user.holdingAccount, true)
  131. assert.equal(user.last_name, 'lastNammmmeee')
  132. })
  133. it('should set emails attribute', async function (ctx) {
  134. const user = await ctx.UserCreator.promises.createNewUser({
  135. email: ctx.email,
  136. })
  137. user.email.should.equal(ctx.email)
  138. user.emails.length.should.equal(1)
  139. user.emails[0].email.should.equal(ctx.email)
  140. user.emails[0].createdAt.should.be.a('date')
  141. user.emails[0].reversedHostname.should.equal('moc.liamg')
  142. })
  143. describe('with affiliations feature', function () {
  144. let attributes, user
  145. beforeEach(function (ctx) {
  146. attributes = { email: ctx.email }
  147. ctx.Features.hasFeature = sinon
  148. .stub()
  149. .withArgs('affiliations')
  150. .returns(true)
  151. })
  152. describe('when v1 affiliations API does not return an error', function () {
  153. beforeEach(async function (ctx) {
  154. user = await ctx.UserCreator.promises.createNewUser(attributes)
  155. })
  156. it('should flag that affiliation is unchecked', function () {
  157. user.emails[0].affiliationUnchecked.should.equal(true)
  158. })
  159. it('should try to add affiliation to v1', function (ctx) {
  160. sinon.assert.calledOnce(
  161. ctx.UserUpdater.promises.addAffiliationForNewUser
  162. )
  163. sinon.assert.calledWithMatch(
  164. ctx.UserUpdater.promises.addAffiliationForNewUser,
  165. user._id,
  166. ctx.email
  167. )
  168. })
  169. it('should query for updated user data', function (ctx) {
  170. sinon.assert.calledOnce(ctx.UserGetter.promises.getUser)
  171. })
  172. })
  173. describe('when v1 affiliations API does return an error', function () {
  174. beforeEach(async function (ctx) {
  175. ctx.UserUpdater.promises.addAffiliationForNewUser.rejects()
  176. user = await ctx.UserCreator.promises.createNewUser(attributes)
  177. })
  178. it('should flag that affiliation is unchecked', function () {
  179. user.emails[0].affiliationUnchecked.should.equal(true)
  180. })
  181. it('should try to add affiliation to v1', function (ctx) {
  182. sinon.assert.calledOnce(
  183. ctx.UserUpdater.promises.addAffiliationForNewUser
  184. )
  185. sinon.assert.calledWithMatch(
  186. ctx.UserUpdater.promises.addAffiliationForNewUser,
  187. user._id,
  188. ctx.email
  189. )
  190. })
  191. it('should not query for updated user data', function (ctx) {
  192. sinon.assert.notCalled(ctx.UserGetter.promises.getUser)
  193. })
  194. it('should log error', function (ctx) {
  195. sinon.assert.calledOnce(ctx.logger.error)
  196. })
  197. })
  198. describe('when v1 affiliations API returns an error and requireAffiliation=true', function () {
  199. beforeEach(async function (ctx) {
  200. ctx.UserUpdater.promises.addAffiliationForNewUser.rejects()
  201. user = await ctx.UserCreator.promises.createNewUser(attributes)
  202. })
  203. it('should flag that affiliation is unchecked', function () {
  204. user.emails[0].affiliationUnchecked.should.equal(true)
  205. })
  206. it('should try to add affiliation to v1', function (ctx) {
  207. sinon.assert.calledOnce(
  208. ctx.UserUpdater.promises.addAffiliationForNewUser
  209. )
  210. sinon.assert.calledWithMatch(
  211. ctx.UserUpdater.promises.addAffiliationForNewUser,
  212. user._id,
  213. ctx.email
  214. )
  215. })
  216. it('should not query for updated user data', function (ctx) {
  217. sinon.assert.notCalled(ctx.UserGetter.promises.getUser)
  218. })
  219. it('should log error', function (ctx) {
  220. sinon.assert.calledOnce(ctx.logger.error)
  221. })
  222. })
  223. })
  224. it('should not add affiliation when without affiliation feature', async function (ctx) {
  225. const attributes = { email: ctx.email }
  226. await ctx.UserCreator.promises.createNewUser(attributes)
  227. sinon.assert.notCalled(
  228. ctx.UserUpdater.promises.addAffiliationForNewUser
  229. )
  230. })
  231. })
  232. describe('with promises', function () {
  233. it('should take the opts and put them in the model', async function (ctx) {
  234. const opts = {
  235. email: ctx.email,
  236. holdingAccount: true,
  237. }
  238. const user = await ctx.UserCreator.promises.createNewUser(opts)
  239. assert.equal(user.email, ctx.email)
  240. assert.equal(user.holdingAccount, true)
  241. assert.equal(user.first_name, 'bob.oswald')
  242. })
  243. it('should add affiliation when with affiliation feature', async function (ctx) {
  244. ctx.Features.hasFeature = sinon
  245. .stub()
  246. .withArgs('affiliations')
  247. .returns(true)
  248. const attributes = { email: ctx.email }
  249. const user = await ctx.UserCreator.promises.createNewUser(attributes)
  250. sinon.assert.calledOnce(
  251. ctx.UserUpdater.promises.addAffiliationForNewUser
  252. )
  253. sinon.assert.calledWithMatch(
  254. ctx.UserUpdater.promises.addAffiliationForNewUser,
  255. user._id,
  256. ctx.email
  257. )
  258. })
  259. it('should not add affiliation when without affiliation feature', async function (ctx) {
  260. ctx.Features.hasFeature = sinon.stub().returns(false)
  261. const attributes = { email: ctx.email }
  262. await ctx.UserCreator.promises.createNewUser(attributes)
  263. sinon.assert.notCalled(
  264. ctx.UserUpdater.promises.addAffiliationForNewUser
  265. )
  266. })
  267. it('should include SAML provider ID with email', async function (ctx) {
  268. const attributes = {
  269. email: ctx.email,
  270. samlIdentifiers: [{ email: ctx.email, providerId: '1' }],
  271. }
  272. const user = await ctx.UserCreator.promises.createNewUser(attributes)
  273. assert.equal(user.emails[0].samlProviderId, '1')
  274. })
  275. it('should fire an analytics event and user property on registration', async function (ctx) {
  276. const user = await ctx.UserCreator.promises.createNewUser({
  277. email: ctx.email,
  278. })
  279. assert.equal(user.email, ctx.email)
  280. sinon.assert.calledWith(
  281. ctx.Analytics.recordEventForUserInBackground,
  282. user._id,
  283. 'user-registered'
  284. )
  285. sinon.assert.calledWith(
  286. ctx.Analytics.setUserPropertyForUser,
  287. user._id,
  288. 'created-at'
  289. )
  290. })
  291. it('should schedule post registration jobs on registration with saas feature', async function (ctx) {
  292. ctx.Features.hasFeature = sinon.stub().withArgs('saas').returns(true)
  293. const user = await ctx.UserCreator.promises.createNewUser({
  294. email: ctx.email,
  295. })
  296. assert.equal(user.email, ctx.email)
  297. sinon.assert.calledWith(
  298. ctx.UserOnboardingEmailManager.scheduleOnboardingEmail,
  299. user
  300. )
  301. sinon.assert.calledWith(
  302. ctx.UserPostRegistrationAnalyticsManager
  303. .schedulePostRegistrationAnalytics,
  304. user
  305. )
  306. })
  307. it('should not schedule post registration checks when without saas feature', async function (ctx) {
  308. const attributes = { email: ctx.email }
  309. await ctx.UserCreator.promises.createNewUser(attributes)
  310. sinon.assert.notCalled(
  311. ctx.UserOnboardingEmailManager.scheduleOnboardingEmail
  312. )
  313. sinon.assert.notCalled(
  314. ctx.UserPostRegistrationAnalyticsManager
  315. .schedulePostRegistrationAnalytics
  316. )
  317. })
  318. })
  319. })
  320. })