Просмотр исходного кода

Merge pull request #22584 from overleaf/ls-test-for-group-subscription-upgrade

Add tests for group subscription upgrade

GitOrigin-RevId: 0718dd420930d56602831f127494102045cb6cfc
Liangjun Song 1 год назад
Родитель
Сommit
7a48061d31

+ 1 - 1
services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx

@@ -28,7 +28,7 @@ function UpgradeSubscriptionPlanDetails() {
         <b>{preview.nextInvoice.plan.name}</b>
         <Row xs="auto" className="gx-2">
           <Col>
-            <span className="per-user-price">
+            <span className="per-user-price" data-testid="per-user-price">
               <b>
                 {formatCurrencyLocalized(
                   licenseUnitPrice,

+ 3 - 3
services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx

@@ -38,7 +38,7 @@ function UpgradeSummary({ subscriptionChange }: UpgradeSummaryProps) {
                 {subscriptionChange.nextInvoice.plan.name} x {totalLicenses}{' '}
                 {t('users')}
               </span>
-              <span>
+              <span data-testid="subtotal">
                 {formatCurrencyLocalized(
                   subscriptionChange.immediateCharge.subtotal,
                   subscriptionChange.currency
@@ -47,7 +47,7 @@ function UpgradeSummary({ subscriptionChange }: UpgradeSummaryProps) {
             </ListGroup.Item>
             <ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
               <span className="me-auto">{t('sales_tax')}</span>
-              <span>
+              <span data-testid="tax">
                 {formatCurrencyLocalized(
                   subscriptionChange.immediateCharge.tax,
                   subscriptionChange.currency
@@ -56,7 +56,7 @@ function UpgradeSummary({ subscriptionChange }: UpgradeSummaryProps) {
             </ListGroup.Item>
             <ListGroup.Item className="bg-transparent border-0 px-0 gap-3 card-description-secondary">
               <strong className="me-auto">{t('total_due_today')}</strong>
-              <strong>
+              <strong data-testid="total">
                 {formatCurrencyLocalized(
                   subscriptionChange.immediateCharge.total,
                   subscriptionChange.currency

+ 5 - 2
services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription.tsx

@@ -10,6 +10,7 @@ import RequestStatus from '../request-status'
 import UpgradeSummary, {
   SubscriptionChange,
 } from './upgrade-subscription-upgrade-summary'
+import { debugConsole } from '@/utils/debugging'
 
 function UpgradeSubscription() {
   const { t } = useTranslation()
@@ -17,7 +18,9 @@ function UpgradeSubscription() {
   const preview = getMeta('ol-subscriptionChangePreview') as SubscriptionChange
   const { isError, runAsync, isSuccess, isLoading } = useAsync()
   const onSubmit = () => {
-    runAsync(postJSON('/user/subscription/group/upgrade-subscription'))
+    runAsync(postJSON('/user/subscription/group/upgrade-subscription')).catch(
+      debugConsole.error
+    )
   }
 
   if (isSuccess) {
@@ -51,7 +54,7 @@ function UpgradeSubscription() {
     <div className="container">
       <Row>
         <Col xl={{ span: 8, offset: 2 }}>
-          <div className="group-heading">
+          <div className="group-heading" data-testid="group-heading">
             <IconButton
               variant="ghost"
               href="/user/subscription"

+ 120 - 0
services/web/test/frontend/features/group-management/components/upgrade-subscription.spec.tsx

@@ -0,0 +1,120 @@
+import '../../../helpers/bootstrap-5'
+import UpgradeSubscription from '@/features/group-management/components/upgrade-subscription/upgrade-subscription'
+import { SplitTestProvider } from '@/shared/context/split-test-context'
+
+describe('<UpgradeSubscription />', function () {
+  beforeEach(function () {
+    this.totalLicenses = 2
+    this.preview = {
+      change: {
+        type: 'group-plan-upgrade',
+        prevPlan: { name: 'Overleaf Standard Group' },
+      },
+      currency: 'USD',
+      immediateCharge: { subtotal: 353.99, tax: 70.8, total: 424.79 },
+      paymentMethod: 'Visa **** 1111',
+      nextPlan: { annual: true },
+      nextInvoice: {
+        date: '2025-11-05T11:35:32.000Z',
+        plan: { name: 'Overleaf Professional Group', amount: 0 },
+        addOns: [
+          {
+            code: 'additional-license',
+            name: 'Seat',
+            quantity: 2,
+            unitAmount: 399,
+            amount: 798,
+          },
+        ],
+        subtotal: 798,
+        tax: { rate: 0.2, amount: 159.6 },
+        total: 957.6,
+      },
+    }
+
+    cy.window().then(win => {
+      win.metaAttributesCache.set('ol-groupName', 'My Awesome Team')
+      win.metaAttributesCache.set('ol-totalLicenses', this.totalLicenses)
+      win.metaAttributesCache.set('ol-subscriptionChangePreview', this.preview)
+    })
+
+    cy.mount(
+      <SplitTestProvider>
+        <UpgradeSubscription />
+      </SplitTestProvider>
+    )
+  })
+
+  it('shows the group name', function () {
+    cy.findByTestId('group-heading').within(() => {
+      cy.findByRole('heading', { name: 'My Awesome Team' })
+    })
+  })
+
+  it('shows the "Add more users to my plan" label', function () {
+    cy.findByText(/add more users to my plan/i).should(
+      'have.attr',
+      'href',
+      '/user/subscription/group/add-users'
+    )
+  })
+
+  it('shows the "Upgrade" and "Cancel" buttons', function () {
+    cy.findByRole('button', { name: /upgrade/i })
+    cy.findByRole('button', { name: /cancel/i }).should(
+      'have.attr',
+      'href',
+      '/user/subscription'
+    )
+  })
+
+  describe('shows plan details', function () {
+    it('shows per user price', function () {
+      cy.findByTestId('per-user-price').within(() => {
+        cy.findByText('$399')
+      })
+    })
+
+    it('shows additional features', function () {
+      cy.findByText(/unlimited collaborators per project/i)
+      cy.findByText(/sso/i)
+      cy.findByText(/managed user accounts/i)
+    })
+  })
+
+  describe('shows upgrade summary', function () {
+    it('shows subtotal, tax and total price', function () {
+      cy.findByTestId('subtotal').within(() => {
+        cy.findByText('$353.99')
+      })
+      cy.findByTestId('tax').within(() => {
+        cy.findByText('$70.80')
+      })
+      cy.findByTestId('total').within(() => {
+        cy.findByText('$424.79')
+      })
+    })
+
+    it('shows total users', function () {
+      cy.findByText(/you have 2 users on your subscription./i)
+    })
+  })
+
+  describe('submit upgrade request', function () {
+    it('request succeeded', function () {
+      cy.intercept('POST', '/user/subscription/group/upgrade-subscription', {
+        statusCode: 200,
+      }).as('upgradeRequest')
+      cy.findByRole('button', { name: /upgrade/i }).click()
+      cy.findByText(/you’ve upgraded your plan!/i)
+    })
+
+    it('request failed', function () {
+      cy.intercept('POST', '/user/subscription/group/upgrade-subscription', {
+        statusCode: 400,
+      }).as('upgradeRequest')
+      cy.findByRole('button', { name: /upgrade/i }).click()
+      cy.findByText(/something went wrong/i)
+    })
+  })
+})

+ 76 - 0
services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs

@@ -56,6 +56,9 @@ describe('SubscriptionGroupController', function () {
           .stub()
           .resolves(this.createSubscriptionChangeData),
         ensureFlexibleLicensingEnabled: sinon.stub().resolves(),
+        getGroupPlanUpgradePreview: sinon
+          .stub()
+          .resolves(this.previewSubscriptionChangeData),
       },
     }
 
@@ -496,4 +499,77 @@ describe('SubscriptionGroupController', function () {
       this.Controller.flexibleLicensingSplitTest(this.req, res, next, done)
     })
   })
+
+  describe('subscriptionUpgradePage', function () {
+    it('should render "subscription upgrade" page', function (done) {
+      const olSubscription = { membersLimit: 1, teamName: 'test team' }
+      this.SubscriptionModel.Subscription.findOne = () => {
+        return {
+          exec: () => olSubscription,
+        }
+      }
+
+      const res = {
+        render: (page, data) => {
+          this.SubscriptionGroupHandler.promises.getGroupPlanUpgradePreview
+            .calledWith(this.req.session.user._id)
+            .should.equal(true)
+          page.should.equal('subscriptions/upgrade-group-subscription-react')
+          data.totalLicenses.should.equal(olSubscription.membersLimit)
+          data.groupName.should.equal(olSubscription.teamName)
+          data.changePreview.should.equal(this.previewSubscriptionChangeData)
+          done()
+        },
+      }
+
+      this.Controller.subscriptionUpgradePage(this.req, res)
+    })
+
+    it('should redirect if failed to generate preview', function (done) {
+      this.SubscriptionGroupHandler.promises.getGroupPlanUpgradePreview = sinon
+        .stub()
+        .rejects()
+
+      const res = {
+        redirect: url => {
+          url.should.equal('/user/subscription')
+          done()
+        },
+      }
+
+      this.Controller.subscriptionUpgradePage(this.req, res)
+    })
+  })
+
+  describe('upgradeSubscription', function () {
+    it('should send 200 response', function (done) {
+      this.SubscriptionGroupHandler.promises.upgradeGroupPlan = sinon
+        .stub()
+        .resolves()
+
+      const res = {
+        sendStatus: code => {
+          code.should.equal(200)
+          done()
+        },
+      }
+
+      this.Controller.upgradeSubscription(this.req, res)
+    })
+
+    it('should send 500 response', function (done) {
+      this.SubscriptionGroupHandler.promises.upgradeGroupPlan = sinon
+        .stub()
+        .rejects()
+
+      const res = {
+        sendStatus: code => {
+          code.should.equal(500)
+          done()
+        },
+      }
+
+      this.Controller.upgradeSubscription(this.req, res)
+    })
+  })
 })

+ 49 - 0
services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js

@@ -28,6 +28,9 @@ describe('SubscriptionGroupHandler', function () {
 
     this.changeRequest = {
       timeframe: 'now',
+      subscription: {
+        id: 'test_id',
+      },
     }
 
     this.recurlySubscription = {
@@ -39,6 +42,9 @@ describe('SubscriptionGroupHandler', function () {
         },
       ],
       getRequestForAddOnUpdate: sinon.stub().returns(this.changeRequest),
+      getRequestForFlexibleLicensingGroupPlanUpgrade: sinon
+        .stub()
+        .returns(this.changeRequest),
     }
 
     this.SubscriptionLocator = {
@@ -81,6 +87,9 @@ describe('SubscriptionGroupHandler', function () {
           quantity: this.recurlySubscription.addOns[0].quantity + this.adding,
         },
       ],
+      subscription: {
+        planName: 'test plan',
+      },
     }
 
     this.applySubscriptionChange = {}
@@ -362,4 +371,44 @@ describe('SubscriptionGroupHandler', function () {
       })
     ).to.not.be.rejected
   })
+
+  describe('upgradeGroupPlan', function () {
+    it('should upgrade the subscription', async function () {
+      this.SubscriptionLocator.promises.getUsersSubscription = sinon
+        .stub()
+        .resolves({ groupPlan: true, planCode: 'group_collaborator' })
+      await this.Handler.promises.upgradeGroupPlan(this.user_id)
+      this.RecurlyClient.promises.applySubscriptionChangeRequest
+        .calledWith(this.changeRequest)
+        .should.equal(true)
+      this.SubscriptionHandler.promises.syncSubscription
+        .calledWith({ uuid: this.changeRequest.subscription.id }, this.user_id)
+        .should.equal(true)
+    })
+
+    it('should fail the upgrade if not eligible', async function () {
+      this.SubscriptionLocator.promises.getUsersSubscription = sinon
+        .stub()
+        .resolves({ groupPlan: true, planCode: 'group_professional' })
+      await expect(
+        this.Handler.promises.upgradeGroupPlan(this.user_id)
+      ).to.be.rejectedWith('Not eligible for group plan upgrade')
+    })
+  })
+
+  describe('getGroupPlanUpgradePreview', function () {
+    it('should generate preview for subscription upgrade', async function () {
+      this.SubscriptionLocator.promises.getUsersSubscription = sinon
+        .stub()
+        .resolves({ groupPlan: true, planCode: 'group_collaborator' })
+      const result = await this.Handler.promises.getGroupPlanUpgradePreview(
+        this.user_id
+      )
+      this.RecurlyClient.promises.previewSubscriptionChange
+        .calledWith(this.changeRequest)
+        .should.equal(true)
+
+      result.should.equal(this.changePreview)
+    })
+  })
 })