From ddae24eedf992a3fde26e9e4b8cc91e53fc8c329 Mon Sep 17 00:00:00 2001 From: Alessandro Magionami Date: Tue, 25 Mar 2025 17:40:09 +0100 Subject: [PATCH] chore(workspaces): add test and make product selection more robust --- .../gatekeeper/graph/resolvers/index.ts | 7 +++- .../gatekeeper/services/subscriptions.ts | 26 +++++++++++++ .../intergration/workspace.graph.spec.ts | 4 +- .../tests/unit/subscriptions.spec.ts | 37 +++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/packages/server/modules/gatekeeper/graph/resolvers/index.ts b/packages/server/modules/gatekeeper/graph/resolvers/index.ts index fc2c9575e..e424d97d4 100644 --- a/packages/server/modules/gatekeeper/graph/resolvers/index.ts +++ b/packages/server/modules/gatekeeper/graph/resolvers/index.ts @@ -67,6 +67,7 @@ import { } from '@/modules/gatekeeper/repositories/workspaceSeat' import { assignWorkspaceSeatFactory } from '@/modules/workspaces/services/workspaceSeat' import { getEventBus } from '@/modules/shared/services/eventBus' +import { getTotalSeatsCountByPlanFactory } from '@/modules/gatekeeper/services/subscriptions' const { FF_GATEKEEPER_MODULE_ENABLED, FF_BILLING_INTEGRATION_ENABLED } = getFeatureFlags() @@ -177,14 +178,16 @@ export = FF_GATEKEEPER_MODULE_ENABLED } } // Only editor seats are considered - const totalSeatsCount = parent.subscriptionData.products[0].quantity const assignedSeatsCount = await countSeatsByTypeInWorkspaceFactory({ db })({ workspaceId: parent.workspaceId, type: 'editor' }) return { assigned: assignedSeatsCount, - totalCount: totalSeatsCount, + totalCount: getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({ + workspacePlan, + subscriptionData: parent.subscriptionData + }), // These values have no reference in the new plans guest: 0, plan: 0 diff --git a/packages/server/modules/gatekeeper/services/subscriptions.ts b/packages/server/modules/gatekeeper/services/subscriptions.ts index a069545fd..6ea8989c1 100644 --- a/packages/server/modules/gatekeeper/services/subscriptions.ts +++ b/packages/server/modules/gatekeeper/services/subscriptions.ts @@ -31,6 +31,7 @@ import { cloneDeep, isEqual, sum } from 'lodash' import { mutateSubscriptionDataWithNewValidSeatNumbers } from '@/modules/gatekeeper/services/subscriptions/mutateSubscriptionDataWithNewValidSeatNumbers' import { calculateNewBillingCycleEnd } from '@/modules/gatekeeper/services/subscriptions/calculateNewBillingCycleEnd' import { CountSeatsByTypeInWorkspace } from '@/modules/gatekeeper/domain/operations' +import { WorkspacePlan } from '@/modules/gatekeeperCore/domain/billing' export const handleSubscriptionUpdateFactory = ({ @@ -411,3 +412,28 @@ export const manageSubscriptionDownscaleFactory = log.info({ updatedWorkspaceSubscription }, 'Updated workspace billing cycle end') } } + +export const getTotalSeatsCountByPlanFactory = + ({ + getWorkspacePlanProductId + }: { + getWorkspacePlanProductId: GetWorkspacePlanProductId + }) => + ({ + workspacePlan, + subscriptionData + }: { + workspacePlan: Pick + subscriptionData: Pick + }) => { + if (workspacePlan.name === 'free') { + return 3 // Max editors seats in the free plan + } + const productId = getWorkspacePlanProductId({ + workspacePlan: workspacePlan.name as 'pro' | 'team' + }) + const product = subscriptionData.products.find( + (product) => product.productId === productId + ) + return product?.quantity ?? 0 + } diff --git a/packages/server/modules/gatekeeper/tests/intergration/workspace.graph.spec.ts b/packages/server/modules/gatekeeper/tests/intergration/workspace.graph.spec.ts index 6fc1fdc63..fe6edd795 100644 --- a/packages/server/modules/gatekeeper/tests/intergration/workspace.graph.spec.ts +++ b/packages/server/modules/gatekeeper/tests/intergration/workspace.graph.spec.ts @@ -119,7 +119,7 @@ describe('Workspaces Billing', () => { 'workspace.subscription', () => { describe('subscription.seats', () => { - it('should return the number of total seats', async () => { + it('should return the number of total and assigned seats', async () => { const user = await createTestUser({ name: createRandomString(), email: createRandomEmail(), @@ -166,7 +166,7 @@ describe('Workspaces Billing', () => { expect(res).to.not.haveGraphQLErrors() const seats = res.data?.workspace.subscription?.seats - expect(seats).to.deep.eq({ guest: 0, plan: 0, assigned: 1, totalCount: 12 }) + expect(seats?.assigned).to.eq(1) }) }) } diff --git a/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts b/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts index e3f08d820..903c320cf 100644 --- a/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts +++ b/packages/server/modules/gatekeeper/tests/unit/subscriptions.spec.ts @@ -16,6 +16,7 @@ import { addWorkspaceSubscriptionSeatIfNeededFactoryNew, addWorkspaceSubscriptionSeatIfNeededFactoryOld, downscaleWorkspaceSubscriptionFactory, + getTotalSeatsCountByPlanFactory, handleSubscriptionUpdateFactory, manageSubscriptionDownscaleFactory } from '@/modules/gatekeeper/services/subscriptions' @@ -2178,4 +2179,40 @@ describe('subscriptions @gatekeeper', () => { expect(newProduct!.priceId).to.equal('newPlanPrice') }) }) + describe('getTotalSeatsCountByPlanFactory returns a function that, ', () => { + it('should return the fixed value for the free plan', () => { + const getWorkspacePlanProductId = () => expect.fail() + expect( + getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({ + workspacePlan: { name: 'free' }, + subscriptionData: { products: [] } + }) + ).to.eq(3) + }) + it('should return 0 if subscription data has no product', () => { + const getWorkspacePlanProductId = () => 'any' + expect( + getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({ + workspacePlan: { name: 'pro' }, + subscriptionData: { products: [] } + }) + ).to.eq(0) + }) + it('should return the number of purchased seats in the current billing period for the subscription', () => { + const getWorkspacePlanProductId = () => 'productId' + expect( + getTotalSeatsCountByPlanFactory({ getWorkspacePlanProductId })({ + workspacePlan: { name: 'pro' }, + subscriptionData: { + products: [ + { + productId: 'productId', + quantity: 4 + } as SubscriptionData['products'][number] + ] + } + }) + ).to.eq(4) + }) + }) })