diff --git a/src/services/tiers.service.ts b/src/services/tiers.service.ts index f6e26989..28dcb756 100644 --- a/src/services/tiers.service.ts +++ b/src/services/tiers.service.ts @@ -196,12 +196,23 @@ export class TiersService { await this.usersService.disableVPNTier(userUuid, featureId); } + /** + * Applies the mail configuration of the given tier to the user. + * + * The tier is the source of truth for the mail entitlement, so this both reactivates the account + * for a tier that includes mail and suspends it for one that does not. Keeping it symmetric is + * what covers a plan switch: `removeTier` only runs when a plan is cancelled or refunded, never + * when the price of a live subscription changes. + */ async applyMailFeatures(userWithEmail: { email: string; uuid: User['uuid'] }, tier: Tier): Promise { const { enabled } = tier.featuresPerService[Service.Mail]; if (enabled) { await this.mailService.reactivateAccount(userWithEmail.uuid); + return; } + + await this.removeMailFeatures(userWithEmail.uuid); } async removeMailFeatures(userUuid: User['uuid']): Promise { diff --git a/tests/src/services/tiers.service.test.ts b/tests/src/services/tiers.service.test.ts index ff154ec7..7efe264b 100644 --- a/tests/src/services/tiers.service.test.ts +++ b/tests/src/services/tiers.service.test.ts @@ -392,17 +392,33 @@ describe('TiersService tests', () => { expect(reactivateAccountSpy).toHaveBeenCalledWith(userWithEmail.uuid); }); - test('When Mail is disabled, then it does not send a request to reactivate the mail account', async () => { + test('When Mail is disabled, then the mail account is suspended instead of reactivated', async () => { const userWithEmail = { ...getUser(), email: 'test@internxt.com' }; const tier = newTier(); const reactivateAccountSpy = jest .spyOn(mailService, 'reactivateAccount') .mockImplementation(() => Promise.resolve()); + const suspendAccountSpy = jest.spyOn(mailService, 'suspendAccount').mockImplementation(() => Promise.resolve()); await tiersService.applyMailFeatures(userWithEmail, tier); expect(reactivateAccountSpy).not.toHaveBeenCalled(); + expect(suspendAccountSpy).toHaveBeenCalledWith(userWithEmail.uuid); + }); + + test('When Mail is enabled, then the mail account is not suspended', async () => { + const userWithEmail = { ...getUser(), email: 'test@internxt.com' }; + const tier = newTier(); + + tier.featuresPerService[Service.Mail].enabled = true; + + jest.spyOn(mailService, 'reactivateAccount').mockImplementation(() => Promise.resolve()); + const suspendAccountSpy = jest.spyOn(mailService, 'suspendAccount').mockImplementation(() => Promise.resolve()); + + await tiersService.applyMailFeatures(userWithEmail, tier); + + expect(suspendAccountSpy).not.toHaveBeenCalled(); }); }); diff --git a/tests/src/webhooks/events/invoices/InvoiceCompletedHandler.test.ts b/tests/src/webhooks/events/invoices/InvoiceCompletedHandler.test.ts index c4b43b5c..fd140ef5 100644 --- a/tests/src/webhooks/events/invoices/InvoiceCompletedHandler.test.ts +++ b/tests/src/webhooks/events/invoices/InvoiceCompletedHandler.test.ts @@ -436,6 +436,7 @@ describe('Testing the handler when an invoice is completed', () => { }); const applyDriveFeaturesSpy = jest.spyOn(tiersService, 'applyDriveFeatures').mockResolvedValue(); const applyVpnFeaturesSpy = jest.spyOn(tiersService, 'applyVpnFeatures').mockResolvedValue(); + jest.spyOn(tiersService, 'applyMailFeatures').mockResolvedValue(); const handleNewProduct = invoiceCompletedHandler['handleNewProduct'].bind(invoiceCompletedHandler); await handleNewProduct({ @@ -483,6 +484,7 @@ describe('Testing the handler when an invoice is completed', () => { .mockRejectedValue(unexpectedError); const applyDriveFeaturesSpy = jest.spyOn(tiersService, 'applyDriveFeatures').mockResolvedValue(); const applyVpnFeaturesSpy = jest.spyOn(tiersService, 'applyVpnFeatures').mockResolvedValue(); + jest.spyOn(tiersService, 'applyMailFeatures').mockResolvedValue(); const handleNewProduct = invoiceCompletedHandler['handleNewProduct'].bind(invoiceCompletedHandler); await handleNewProduct({