PlansLocator.test.mjs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import { vi, expect } from 'vitest'
  2. const modulePath = '../../../../app/src/Features/Subscription/PlansLocator'
  3. const plans = [
  4. {
  5. planCode: 'first',
  6. name: '1st',
  7. price_in_cents: 800,
  8. features: {},
  9. featureDescription: {},
  10. },
  11. {
  12. planCode: 'second',
  13. name: '2nd',
  14. price_in_cents: 1500,
  15. features: {},
  16. featureDescription: {},
  17. },
  18. {
  19. planCode: 'third',
  20. name: '3rd',
  21. price_in_cents: 3000,
  22. features: {},
  23. featureDescription: {},
  24. },
  25. ]
  26. describe('PlansLocator', function () {
  27. beforeEach(async function (ctx) {
  28. ctx.settings = { plans }
  29. ctx.AI_ADD_ON_CODE = 'assistant'
  30. vi.doMock('@overleaf/settings', () => ({
  31. default: ctx.settings,
  32. }))
  33. ctx.PlansLocator = (await import(modulePath)).default
  34. })
  35. describe('findLocalPlanInSettings', function () {
  36. it('should return the found plan', function (ctx) {
  37. const plan = ctx.PlansLocator.findLocalPlanInSettings('second')
  38. expect(plan).to.have.property('name', '2nd')
  39. expect(plan).to.have.property('price_in_cents', 1500)
  40. })
  41. it('should return null if no matching plan is found', function (ctx) {
  42. const plan = ctx.PlansLocator.findLocalPlanInSettings('gibberish')
  43. expect(plan).to.be.a('null')
  44. })
  45. })
  46. describe('buildStripeLookupKey', function () {
  47. it('should map "collaborator" plan code to stripe lookup keys', function (ctx) {
  48. const planCode = 'collaborator'
  49. const currency = 'eur'
  50. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  51. planCode,
  52. currency
  53. )
  54. expect(lookupKey).to.equal('standard_monthly_nov2025_eur')
  55. })
  56. it('should map "collaborator_free_trial_7_days" plan code to stripe lookup keys', function (ctx) {
  57. const planCode = 'collaborator_free_trial_7_days'
  58. const currency = 'eur'
  59. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  60. planCode,
  61. currency
  62. )
  63. expect(lookupKey).to.equal('standard_monthly_nov2025_eur')
  64. })
  65. it('should map "collaborator-annual" plan code to stripe lookup keys', function (ctx) {
  66. const planCode = 'collaborator-annual'
  67. const currency = 'eur'
  68. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  69. planCode,
  70. currency
  71. )
  72. expect(lookupKey).to.equal('standard_annual_nov2025_eur')
  73. })
  74. it('should map "professional" plan code to stripe lookup keys', function (ctx) {
  75. const planCode = 'professional'
  76. const currency = 'eur'
  77. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  78. planCode,
  79. currency
  80. )
  81. expect(lookupKey).to.equal('professional_monthly_nov2025_eur')
  82. })
  83. it('should map "professional_free_trial_7_days" plan code to stripe lookup keys', function (ctx) {
  84. const planCode = 'professional_free_trial_7_days'
  85. const currency = 'eur'
  86. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  87. planCode,
  88. currency
  89. )
  90. expect(lookupKey).to.equal('professional_monthly_nov2025_eur')
  91. })
  92. it('should map "professional-annual" plan code to stripe lookup keys', function (ctx) {
  93. const planCode = 'professional-annual'
  94. const currency = 'eur'
  95. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  96. planCode,
  97. currency
  98. )
  99. expect(lookupKey).to.equal('professional_annual_nov2025_eur')
  100. })
  101. it('should map "student" plan code to stripe lookup keys', function (ctx) {
  102. const planCode = 'student'
  103. const currency = 'eur'
  104. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  105. planCode,
  106. currency
  107. )
  108. expect(lookupKey).to.equal('student_monthly_nov2025_eur')
  109. })
  110. it('shoult map "student_free_trial_7_days" plan code to stripe lookup keys', function (ctx) {
  111. const planCode = 'student_free_trial_7_days'
  112. const currency = 'eur'
  113. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  114. planCode,
  115. currency
  116. )
  117. expect(lookupKey).to.equal('student_monthly_nov2025_eur')
  118. })
  119. it('should map "student-annual" plan code to stripe lookup keys', function (ctx) {
  120. const planCode = 'student-annual'
  121. const currency = 'eur'
  122. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  123. planCode,
  124. currency
  125. )
  126. expect(lookupKey).to.equal('student_annual_nov2025_eur')
  127. })
  128. it('should return null for unknown add-on codes', function (ctx) {
  129. const billingCycleInterval = 'month'
  130. const addOnCode = 'unknown_addon'
  131. const currency = 'gbp'
  132. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  133. addOnCode,
  134. currency,
  135. billingCycleInterval
  136. )
  137. expect(lookupKey).to.equal(null)
  138. })
  139. it('should handle missing input', function (ctx) {
  140. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  141. undefined,
  142. undefined
  143. )
  144. expect(lookupKey).to.equal(null)
  145. })
  146. it('returns the key for a monthly AI assist add-on', function (ctx) {
  147. const billingCycleInterval = 'month'
  148. const addOnCode = ctx.AI_ADD_ON_CODE
  149. const currency = 'gbp'
  150. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  151. addOnCode,
  152. currency,
  153. billingCycleInterval
  154. )
  155. expect(lookupKey).to.equal('assistant_monthly_nov2025_gbp')
  156. })
  157. it('returns the key for an annual AI assist add-on', function (ctx) {
  158. const billingCycleInterval = 'year'
  159. const addOnCode = ctx.AI_ADD_ON_CODE
  160. const currency = 'gbp'
  161. const lookupKey = ctx.PlansLocator.buildStripeLookupKey(
  162. addOnCode,
  163. currency,
  164. billingCycleInterval
  165. )
  166. expect(lookupKey).to.equal('assistant_annual_nov2025_gbp')
  167. })
  168. })
  169. describe('getPlanTypeAndPeriodFromRecurlyPlanCode', function () {
  170. it('should return the plan type and period for "collaborator"', function (ctx) {
  171. const { planType, period } =
  172. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode('collaborator')
  173. expect(planType).to.equal('individual')
  174. expect(period).to.equal('monthly')
  175. })
  176. it('should return the plan type and period for "collaborator_free_trial_7_days"', function (ctx) {
  177. const { planType, period } =
  178. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
  179. 'collaborator_free_trial_7_days'
  180. )
  181. expect(planType).to.equal('individual')
  182. expect(period).to.equal('monthly')
  183. })
  184. it('should return the plan type and period for "collaborator-annual"', function (ctx) {
  185. const { planType, period } =
  186. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
  187. 'collaborator-annual'
  188. )
  189. expect(planType).to.equal('individual')
  190. expect(period).to.equal('annual')
  191. })
  192. it('should return the plan type and period for "professional"', function (ctx) {
  193. const { planType, period } =
  194. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode('professional')
  195. expect(planType).to.equal('individual')
  196. expect(period).to.equal('monthly')
  197. })
  198. it('should return the plan type and period for "professional_free_trial_7_days"', function (ctx) {
  199. const { planType, period } =
  200. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
  201. 'professional_free_trial_7_days'
  202. )
  203. expect(planType).to.equal('individual')
  204. expect(period).to.equal('monthly')
  205. })
  206. it('should return the plan type and period for "professional-annual"', function (ctx) {
  207. const { planType, period } =
  208. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
  209. 'professional-annual'
  210. )
  211. expect(planType).to.equal('individual')
  212. expect(period).to.equal('annual')
  213. })
  214. it('should return the plan type and period for "student"', function (ctx) {
  215. const { planType, period } =
  216. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode('student')
  217. expect(planType).to.equal('student')
  218. expect(period).to.equal('monthly')
  219. })
  220. it('should return the plan type and period for "student_free_trial_7_days"', function (ctx) {
  221. const { planType, period } =
  222. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
  223. 'student_free_trial_7_days'
  224. )
  225. expect(planType).to.equal('student')
  226. expect(period).to.equal('monthly')
  227. })
  228. it('should return the plan type and period for "student-annual"', function (ctx) {
  229. const { planType, period } =
  230. ctx.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
  231. 'student-annual'
  232. )
  233. expect(planType).to.equal('student')
  234. expect(period).to.equal('annual')
  235. })
  236. })
  237. describe('convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded', function () {
  238. it('returns original plan name for non-group plan codes', function (ctx) {
  239. expect(
  240. ctx.PlansLocator.convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded(
  241. 'professional'
  242. )
  243. ).to.deep.equal({
  244. planCode: 'professional',
  245. quantity: 1,
  246. })
  247. })
  248. it('converts Recurly enterprise group plan codes to Stripe group plan codes', function (ctx) {
  249. expect(
  250. ctx.PlansLocator.convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded(
  251. 'group_collaborator_10_enterprise'
  252. )
  253. ).to.deep.equal({
  254. planCode: 'group_collaborator',
  255. quantity: 10,
  256. })
  257. })
  258. it('converts Recurly educational group plan codes to Stripe group plan codes', function (ctx) {
  259. expect(
  260. ctx.PlansLocator.convertLegacyGroupPlanCodeToConsolidatedGroupPlanCodeIfNeeded(
  261. 'group_professional_10_educational'
  262. )
  263. ).to.deep.equal({
  264. planCode: 'group_professional_educational',
  265. quantity: 10,
  266. })
  267. })
  268. })
  269. })