InstitutionsFeatures.test.mjs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { vi, expect } from 'vitest'
  2. import sinon from 'sinon'
  3. import path from 'node:path'
  4. const modulePath = path.join(
  5. import.meta.dirname,
  6. '../../../../app/src/Features/Institutions/InstitutionsFeatures.mjs'
  7. )
  8. describe('InstitutionsFeatures', function () {
  9. beforeEach(async function (ctx) {
  10. ctx.UserGetter = {
  11. promises: { getUserFullEmails: sinon.stub().resolves([]) },
  12. }
  13. ctx.PlansLocator = { findLocalPlanInSettings: sinon.stub() }
  14. ctx.institutionPlanCode = 'institution_plan_code'
  15. ctx.InstitutionsGetter = {
  16. promises: { getCurrentAffiliations: sinon.stub().resolves([]) },
  17. }
  18. vi.doMock('../../../../app/src/Features/User/UserGetter', () => ({
  19. default: ctx.UserGetter,
  20. }))
  21. vi.doMock('../../../../app/src/Features/Subscription/PlansLocator', () => ({
  22. default: ctx.PlansLocator,
  23. }))
  24. vi.doMock('@overleaf/settings', () => ({
  25. default: {
  26. institutionPlanCode: ctx.institutionPlanCode,
  27. overleaf: {},
  28. writefull: {
  29. quotaTierGranted: 'unlimited',
  30. },
  31. aiFeatures: {
  32. freeQuota: 'free',
  33. standardQuota: 'standard',
  34. basicQuota: 'basic',
  35. unlimitedQuota: 'unlimited',
  36. },
  37. quotaGrants: {
  38. ai: {
  39. free: 5,
  40. basic: 5,
  41. standard: 10,
  42. unlimited: 200,
  43. },
  44. },
  45. },
  46. }))
  47. vi.doMock(
  48. '../../../../app/src/Features/Institutions/InstitutionsGetter',
  49. () => ({
  50. default: ctx.InstitutionsGetter,
  51. })
  52. )
  53. ctx.InstitutionsFeatures = (await import(modulePath)).default
  54. ctx.emailDataWithLicense = [{ emailHasInstitutionLicence: true }]
  55. ctx.emailDataWithoutLicense = [{ emailHasInstitutionLicence: false }]
  56. ctx.userId = '12345abcde'
  57. ctx.affiliateWithAiBundle = {
  58. institution: { writefullCommonsAccount: true },
  59. }
  60. ctx.affiliateWithoutAiBundle = {
  61. institution: { writefullCommonsAccount: false },
  62. }
  63. ctx.testFeatures = { features: { institution: 'all' } }
  64. ctx.testFeaturesWithAiAddon = {
  65. features: {
  66. institution: 'all',
  67. aiUsageQuota: 'unlimited',
  68. aiErrorAssistant: true,
  69. },
  70. }
  71. ctx.testFeaturesWithNoAddon = {
  72. features: {
  73. institution: 'all',
  74. aiUsageQuota: 'basic',
  75. aiErrorAssistant: false,
  76. },
  77. }
  78. })
  79. describe('hasLicence', function () {
  80. it('should handle error', async function (ctx) {
  81. ctx.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
  82. let error
  83. try {
  84. await ctx.InstitutionsFeatures.promises.hasLicence(ctx.userId)
  85. } catch (err) {
  86. error = err
  87. }
  88. expect(error).to.exist
  89. })
  90. it('should return false if user has no paid affiliations', async function (ctx) {
  91. ctx.UserGetter.promises.getUserFullEmails.resolves(
  92. ctx.emailDataWithoutLicense
  93. )
  94. const hasLicence = await ctx.InstitutionsFeatures.promises.hasLicence(
  95. ctx.userId
  96. )
  97. expect(hasLicence).to.be.false
  98. })
  99. it('should return true if user has confirmed paid affiliation', async function (ctx) {
  100. const emailData = [
  101. { emailHasInstitutionLicence: true },
  102. { emailHasInstitutionLicence: false },
  103. ]
  104. ctx.UserGetter.promises.getUserFullEmails.resolves(emailData)
  105. const hasLicence = await ctx.InstitutionsFeatures.promises.hasLicence(
  106. ctx.userId
  107. )
  108. expect(hasLicence).to.be.true
  109. })
  110. })
  111. describe('getInstitutionsFeatures', function () {
  112. beforeEach(function (ctx) {
  113. ctx.testFeatures = { features: { institution: 'all' } }
  114. return ctx.PlansLocator.findLocalPlanInSettings
  115. .withArgs(ctx.institutionPlanCode)
  116. .returns(ctx.testFeatures)
  117. })
  118. it('should handle error', async function (ctx) {
  119. ctx.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
  120. await expect(
  121. ctx.InstitutionsFeatures.promises.getInstitutionsFeatures(ctx.userId)
  122. ).to.be.rejected
  123. })
  124. it('should return no features if user has no plan code', async function (ctx) {
  125. ctx.UserGetter.promises.getUserFullEmails.resolves(
  126. ctx.emailDataWithoutLicense
  127. )
  128. const features =
  129. await ctx.InstitutionsFeatures.promises.getInstitutionsFeatures(
  130. ctx.userId
  131. )
  132. expect(features).to.deep.equal({})
  133. })
  134. it('should return ai features if user has any affiliation with add-on bundle', async function (ctx) {
  135. ctx.InstitutionsGetter.promises.getCurrentAffiliations = sinon
  136. .stub()
  137. .resolves([ctx.affiliateWithoutAiBundle, ctx.affiliateWithAiBundle])
  138. ctx.UserGetter.promises.getUserFullEmails.resolves(
  139. ctx.emailDataWithLicense
  140. )
  141. const features =
  142. await ctx.InstitutionsFeatures.promises.getInstitutionsFeatures(
  143. ctx.userId
  144. )
  145. expect(features).to.deep.equal(ctx.testFeaturesWithAiAddon.features)
  146. })
  147. it('should return feaures if user has affiliations plan code', async function (ctx) {
  148. ctx.UserGetter.promises.getUserFullEmails.resolves(
  149. ctx.emailDataWithLicense
  150. )
  151. const features =
  152. await ctx.InstitutionsFeatures.promises.getInstitutionsFeatures(
  153. ctx.userId
  154. )
  155. expect(features).to.deep.equal(ctx.testFeatures.features)
  156. })
  157. })
  158. describe('getInstitutionsPlan', function () {
  159. it('should handle error', async function (ctx) {
  160. ctx.UserGetter.promises.getUserFullEmails.rejects(new Error('Nope'))
  161. await expect(
  162. ctx.InstitutionsFeatures.promises.getInstitutionsPlan(ctx.userId)
  163. ).to.be.rejected
  164. })
  165. it('should return no plan if user has no licence', async function (ctx) {
  166. ctx.UserGetter.promises.getUserFullEmails.resolves(
  167. ctx.emailDataWithoutLicense
  168. )
  169. const plan = await ctx.InstitutionsFeatures.promises.getInstitutionsPlan(
  170. ctx.userId
  171. )
  172. expect(plan).to.equal(null)
  173. })
  174. it('should return plan if user has licence', async function (ctx) {
  175. ctx.UserGetter.promises.getUserFullEmails.resolves(
  176. ctx.emailDataWithLicense
  177. )
  178. const plan = await ctx.InstitutionsFeatures.promises.getInstitutionsPlan(
  179. ctx.userId
  180. )
  181. expect(plan).to.equal(ctx.institutionPlanCode)
  182. })
  183. })
  184. })