import { ConfigService } from '@nestjs/config'; import { RawBodyRequest } from '@nestjs/common'; import { Request } from 'express'; import { SubscriptionService } from './subscription.service'; import { SubscriptionsController } from '../controllers/subscriptions.controller'; import { Subscription } from '@domain/entities/subscription.entity'; import { SubscriptionPlan } from '@domain/value-objects/subscription-plan.vo'; import { SubscriptionRepository } from '@domain/ports/out/subscription.repository'; import { LicenseRepository } from '@domain/ports/out/license.repository'; import { OrganizationRepository } from '@domain/ports/out/organization.repository'; import { UserRepository } from '@domain/ports/out/user.repository'; import { StripePort } from '@domain/ports/out/stripe.port'; describe('Stripe subscription deletion', () => { it('persists cancellation with excess seats and accepts a duplicate event', async () => { let saved = Subscription.create({ id: 'sub', organizationId: 'org', plan: SubscriptionPlan.gold(), }); const save = jest.fn(async (value: Subscription) => { saved = value; }); const count = jest.fn(async () => 10); const service = new SubscriptionService( { findByStripeSubscriptionId: async () => saved, save } as unknown as SubscriptionRepository, { countActiveBySubscriptionIdExcludingAdmins: count } as unknown as LicenseRepository, { findById: async () => null } as unknown as OrganizationRepository, {} as UserRepository, { constructWebhookEvent: async () => ({ type: 'customer.subscription.deleted', data: { object: { id: 'stripe-sub' } }, }), } as unknown as StripePort, new ConfigService() ); await service.handleStripeWebhook(Buffer.from('signed fixture'), 'signature'); await service.handleStripeWebhook(Buffer.from('signed fixture'), 'signature'); expect(saved.plan.value).toBe('BRONZE'); expect(saved.status.value).toBe('CANCELED'); expect(save).toHaveBeenCalledTimes(2); }); it('does not acknowledge processing failures as successful delivery', async () => { const handleStripeWebhook = jest.fn().mockRejectedValue(new Error('storage unavailable')); const controller = new SubscriptionsController( { handleStripeWebhook } as unknown as SubscriptionService, {} as OrganizationRepository ); const req = { rawBody: Buffer.from('fixture') } as RawBodyRequest; await expect(controller.handleWebhook('signature', req)).rejects.toMatchObject({ status: 500 }); handleStripeWebhook.mockResolvedValue(undefined); await expect(controller.handleWebhook('signature', req)).resolves.toEqual({ received: true }); }); });