xpeditis2.0/apps/backend/src/application/services/subscription-cancellation.spec.ts
2026-09-14 11:19:29 +02:00

57 lines
2.7 KiB
TypeScript

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<Request>;
await expect(controller.handleWebhook('signature', req)).rejects.toMatchObject({ status: 500 });
handleStripeWebhook.mockResolvedValue(undefined);
await expect(controller.handleWebhook('signature', req)).resolves.toEqual({ received: true });
});
});