xpeditis2.0/apps/backend/src/domain/entities/subscription.entity.spec.ts
2026-01-20 11:28:54 +01:00

406 lines
13 KiB
TypeScript

/**
* 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');
});
});
});