/** * Subscription Entity Tests * * Unit tests for the Subscription domain entity */ import { Subscription } from './subscription.entity'; import { SubscriptionPlan } from '../value-objects/subscription-plan.vo'; import { SubscriptionStatus } from '../value-objects/subscription-status.vo'; import { InvalidSubscriptionDowngradeException, SubscriptionNotActiveException, } from '../exceptions/subscription.exceptions'; describe('Subscription Entity', () => { const createValidSubscription = () => { return Subscription.create({ id: 'sub-123', organizationId: 'org-123', }); }; describe('create', () => { it('should create a subscription with default FREE plan', () => { const subscription = createValidSubscription(); expect(subscription.id).toBe('sub-123'); expect(subscription.organizationId).toBe('org-123'); expect(subscription.plan.value).toBe('FREE'); expect(subscription.status.value).toBe('ACTIVE'); expect(subscription.cancelAtPeriodEnd).toBe(false); }); it('should create a subscription with custom plan', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.starter(), }); expect(subscription.plan.value).toBe('STARTER'); }); it('should create a subscription with Stripe IDs', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', stripeCustomerId: 'cus_123', stripeSubscriptionId: 'sub_stripe_123', }); expect(subscription.stripeCustomerId).toBe('cus_123'); expect(subscription.stripeSubscriptionId).toBe('sub_stripe_123'); }); }); describe('fromPersistence', () => { it('should reconstitute a subscription from persistence data', () => { const subscription = Subscription.fromPersistence({ id: 'sub-123', organizationId: 'org-123', plan: 'PRO', status: 'ACTIVE', stripeCustomerId: 'cus_123', stripeSubscriptionId: 'sub_stripe_123', currentPeriodStart: new Date('2024-01-01'), currentPeriodEnd: new Date('2024-02-01'), cancelAtPeriodEnd: true, createdAt: new Date('2024-01-01'), updatedAt: new Date('2024-01-15'), }); expect(subscription.id).toBe('sub-123'); expect(subscription.plan.value).toBe('PRO'); expect(subscription.status.value).toBe('ACTIVE'); expect(subscription.cancelAtPeriodEnd).toBe(true); }); }); describe('maxLicenses', () => { it('should return correct limits for FREE plan', () => { const subscription = createValidSubscription(); expect(subscription.maxLicenses).toBe(2); }); it('should return correct limits for STARTER plan', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.starter(), }); expect(subscription.maxLicenses).toBe(5); }); it('should return correct limits for PRO plan', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.pro(), }); expect(subscription.maxLicenses).toBe(20); }); it('should return -1 for ENTERPRISE plan (unlimited)', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.enterprise(), }); expect(subscription.maxLicenses).toBe(-1); }); }); describe('isUnlimited', () => { it('should return false for FREE plan', () => { const subscription = createValidSubscription(); expect(subscription.isUnlimited()).toBe(false); }); it('should return true for ENTERPRISE plan', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.enterprise(), }); expect(subscription.isUnlimited()).toBe(true); }); }); describe('isActive', () => { it('should return true for ACTIVE status', () => { const subscription = createValidSubscription(); expect(subscription.isActive()).toBe(true); }); it('should return true for TRIALING status', () => { const subscription = Subscription.fromPersistence({ id: 'sub-123', organizationId: 'org-123', plan: 'FREE', status: 'TRIALING', stripeCustomerId: null, stripeSubscriptionId: null, currentPeriodStart: null, currentPeriodEnd: null, cancelAtPeriodEnd: false, createdAt: new Date(), updatedAt: new Date(), }); expect(subscription.isActive()).toBe(true); }); it('should return false for CANCELED status', () => { const subscription = Subscription.fromPersistence({ id: 'sub-123', organizationId: 'org-123', plan: 'FREE', status: 'CANCELED', stripeCustomerId: null, stripeSubscriptionId: null, currentPeriodStart: null, currentPeriodEnd: null, cancelAtPeriodEnd: false, createdAt: new Date(), updatedAt: new Date(), }); expect(subscription.isActive()).toBe(false); }); }); describe('canAllocateLicenses', () => { it('should return true when licenses are available', () => { const subscription = createValidSubscription(); expect(subscription.canAllocateLicenses(0, 1)).toBe(true); expect(subscription.canAllocateLicenses(1, 1)).toBe(true); }); it('should return false when no licenses available', () => { const subscription = createValidSubscription(); expect(subscription.canAllocateLicenses(2, 1)).toBe(false); // FREE has 2 licenses }); it('should always return true for ENTERPRISE plan', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.enterprise(), }); expect(subscription.canAllocateLicenses(1000, 100)).toBe(true); }); it('should return false when subscription is not active', () => { const subscription = Subscription.fromPersistence({ id: 'sub-123', organizationId: 'org-123', plan: 'FREE', status: 'CANCELED', stripeCustomerId: null, stripeSubscriptionId: null, currentPeriodStart: null, currentPeriodEnd: null, cancelAtPeriodEnd: false, createdAt: new Date(), updatedAt: new Date(), }); expect(subscription.canAllocateLicenses(0, 1)).toBe(false); }); }); describe('canUpgradeTo', () => { it('should allow upgrade from FREE to STARTER', () => { const subscription = createValidSubscription(); expect(subscription.canUpgradeTo(SubscriptionPlan.starter())).toBe(true); }); it('should allow upgrade from FREE to PRO', () => { const subscription = createValidSubscription(); expect(subscription.canUpgradeTo(SubscriptionPlan.pro())).toBe(true); }); it('should not allow downgrade via canUpgradeTo', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.starter(), }); expect(subscription.canUpgradeTo(SubscriptionPlan.free())).toBe(false); }); }); describe('canDowngradeTo', () => { it('should allow downgrade when user count fits', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.starter(), }); expect(subscription.canDowngradeTo(SubscriptionPlan.free(), 1)).toBe(true); }); it('should prevent downgrade when user count exceeds new plan', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.starter(), }); expect(subscription.canDowngradeTo(SubscriptionPlan.free(), 5)).toBe(false); }); }); describe('updatePlan', () => { it('should update to new plan when valid', () => { const subscription = createValidSubscription(); const updated = subscription.updatePlan(SubscriptionPlan.starter(), 1); expect(updated.plan.value).toBe('STARTER'); }); it('should throw when subscription is not active', () => { const subscription = Subscription.fromPersistence({ id: 'sub-123', organizationId: 'org-123', plan: 'FREE', status: 'CANCELED', stripeCustomerId: null, stripeSubscriptionId: null, currentPeriodStart: null, currentPeriodEnd: null, cancelAtPeriodEnd: false, createdAt: new Date(), updatedAt: new Date(), }); expect(() => subscription.updatePlan(SubscriptionPlan.starter(), 0)).toThrow( SubscriptionNotActiveException, ); }); it('should throw when downgrading with too many users', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.pro(), }); expect(() => subscription.updatePlan(SubscriptionPlan.starter(), 10)).toThrow( InvalidSubscriptionDowngradeException, ); }); }); describe('updateStatus', () => { it('should update subscription status', () => { const subscription = createValidSubscription(); const updated = subscription.updateStatus(SubscriptionStatus.pastDue()); expect(updated.status.value).toBe('PAST_DUE'); }); }); describe('updateStripeCustomerId', () => { it('should update Stripe customer ID', () => { const subscription = createValidSubscription(); const updated = subscription.updateStripeCustomerId('cus_new_123'); expect(updated.stripeCustomerId).toBe('cus_new_123'); }); }); describe('updateStripeSubscription', () => { it('should update Stripe subscription details', () => { const subscription = createValidSubscription(); const periodStart = new Date('2024-02-01'); const periodEnd = new Date('2024-03-01'); const updated = subscription.updateStripeSubscription({ stripeSubscriptionId: 'sub_new_123', currentPeriodStart: periodStart, currentPeriodEnd: periodEnd, cancelAtPeriodEnd: true, }); expect(updated.stripeSubscriptionId).toBe('sub_new_123'); expect(updated.currentPeriodStart).toEqual(periodStart); expect(updated.currentPeriodEnd).toEqual(periodEnd); expect(updated.cancelAtPeriodEnd).toBe(true); }); }); describe('scheduleCancellation', () => { it('should mark subscription for cancellation', () => { const subscription = createValidSubscription(); const updated = subscription.scheduleCancellation(); expect(updated.cancelAtPeriodEnd).toBe(true); }); }); describe('unscheduleCancellation', () => { it('should unmark subscription for cancellation', () => { const subscription = Subscription.fromPersistence({ id: 'sub-123', organizationId: 'org-123', plan: 'STARTER', status: 'ACTIVE', stripeCustomerId: 'cus_123', stripeSubscriptionId: 'sub_123', currentPeriodStart: new Date(), currentPeriodEnd: new Date(), cancelAtPeriodEnd: true, createdAt: new Date(), updatedAt: new Date(), }); const updated = subscription.unscheduleCancellation(); expect(updated.cancelAtPeriodEnd).toBe(false); }); }); describe('cancel', () => { it('should cancel the subscription immediately', () => { const subscription = createValidSubscription(); const updated = subscription.cancel(); expect(updated.status.value).toBe('CANCELED'); expect(updated.cancelAtPeriodEnd).toBe(false); }); }); describe('isFree and isPaid', () => { it('should return true for isFree when FREE plan', () => { const subscription = createValidSubscription(); expect(subscription.isFree()).toBe(true); expect(subscription.isPaid()).toBe(false); }); it('should return true for isPaid when STARTER plan', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', plan: SubscriptionPlan.starter(), }); expect(subscription.isFree()).toBe(false); expect(subscription.isPaid()).toBe(true); }); }); describe('toObject', () => { it('should convert to plain object for persistence', () => { const subscription = Subscription.create({ id: 'sub-123', organizationId: 'org-123', stripeCustomerId: 'cus_123', }); const obj = subscription.toObject(); expect(obj.id).toBe('sub-123'); expect(obj.organizationId).toBe('org-123'); expect(obj.plan).toBe('FREE'); expect(obj.status).toBe('ACTIVE'); expect(obj.stripeCustomerId).toBe('cus_123'); }); }); });