Some checks failed
CD Preprod / Backend — Lint (push) Successful in 10m27s
CD Preprod / Frontend — Lint & Type-check (push) Successful in 10m55s
CD Preprod / Backend — Unit Tests (push) Successful in 10m10s
CD Preprod / Frontend — Unit Tests (push) Successful in 10m33s
CD Preprod / Backend — Integration Tests (push) Successful in 10m6s
CD Preprod / Build Backend (push) Successful in 16m5s
CD Preprod / Build Frontend (push) Successful in 35m0s
CD Preprod / Deploy to Preprod (push) Failing after 1s
CD Preprod / Smoke Tests (push) Has been skipped
CD Preprod / Notify Success (push) Has been skipped
CD Preprod / Notify Failure (push) Has been skipped
405 lines
13 KiB
TypeScript
405 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 BRONZE plan', () => {
|
|
const subscription = createValidSubscription();
|
|
|
|
expect(subscription.id).toBe('sub-123');
|
|
expect(subscription.organizationId).toBe('org-123');
|
|
expect(subscription.plan.value).toBe('BRONZE');
|
|
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.silver(),
|
|
});
|
|
|
|
expect(subscription.plan.value).toBe('SILVER');
|
|
});
|
|
|
|
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: 'GOLD',
|
|
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('GOLD');
|
|
expect(subscription.status.value).toBe('ACTIVE');
|
|
expect(subscription.cancelAtPeriodEnd).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('maxLicenses', () => {
|
|
it('should return correct limits for BRONZE plan', () => {
|
|
const subscription = createValidSubscription();
|
|
expect(subscription.maxLicenses).toBe(1);
|
|
});
|
|
|
|
it('should return correct limits for SILVER plan', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.silver(),
|
|
});
|
|
expect(subscription.maxLicenses).toBe(5);
|
|
});
|
|
|
|
it('should return correct limits for GOLD plan', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.gold(),
|
|
});
|
|
expect(subscription.maxLicenses).toBe(20);
|
|
});
|
|
|
|
it('should return -1 for PLATINIUM plan (unlimited)', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.platinium(),
|
|
});
|
|
expect(subscription.maxLicenses).toBe(-1);
|
|
});
|
|
});
|
|
|
|
describe('isUnlimited', () => {
|
|
it('should return false for BRONZE plan', () => {
|
|
const subscription = createValidSubscription();
|
|
expect(subscription.isUnlimited()).toBe(false);
|
|
});
|
|
|
|
it('should return true for PLATINIUM plan', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.platinium(),
|
|
});
|
|
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: 'BRONZE',
|
|
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: 'BRONZE',
|
|
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(); // BRONZE = 1 license
|
|
expect(subscription.canAllocateLicenses(0, 1)).toBe(true);
|
|
});
|
|
|
|
it('should return false when no licenses available', () => {
|
|
const subscription = createValidSubscription();
|
|
expect(subscription.canAllocateLicenses(1, 1)).toBe(false); // BRONZE has 1 license max
|
|
});
|
|
|
|
it('should always return true for PLATINIUM plan', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.platinium(),
|
|
});
|
|
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: 'BRONZE',
|
|
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 BRONZE to SILVER', () => {
|
|
const subscription = createValidSubscription();
|
|
expect(subscription.canUpgradeTo(SubscriptionPlan.silver())).toBe(true);
|
|
});
|
|
|
|
it('should allow upgrade from BRONZE to GOLD', () => {
|
|
const subscription = createValidSubscription();
|
|
expect(subscription.canUpgradeTo(SubscriptionPlan.gold())).toBe(true);
|
|
});
|
|
|
|
it('should not allow downgrade via canUpgradeTo', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.silver(),
|
|
});
|
|
expect(subscription.canUpgradeTo(SubscriptionPlan.bronze())).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('canDowngradeTo', () => {
|
|
it('should allow downgrade when user count fits', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.silver(),
|
|
});
|
|
expect(subscription.canDowngradeTo(SubscriptionPlan.bronze(), 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.silver(),
|
|
});
|
|
expect(subscription.canDowngradeTo(SubscriptionPlan.bronze(), 5)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('updatePlan', () => {
|
|
it('should update to new plan when valid', () => {
|
|
const subscription = createValidSubscription();
|
|
const updated = subscription.updatePlan(SubscriptionPlan.silver(), 1);
|
|
|
|
expect(updated.plan.value).toBe('SILVER');
|
|
});
|
|
|
|
it('should throw when subscription is not active', () => {
|
|
const subscription = Subscription.fromPersistence({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: 'BRONZE',
|
|
status: 'CANCELED',
|
|
stripeCustomerId: null,
|
|
stripeSubscriptionId: null,
|
|
currentPeriodStart: null,
|
|
currentPeriodEnd: null,
|
|
cancelAtPeriodEnd: false,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
});
|
|
|
|
expect(() => subscription.updatePlan(SubscriptionPlan.silver(), 0)).toThrow(
|
|
SubscriptionNotActiveException
|
|
);
|
|
});
|
|
|
|
it('should throw when downgrading with too many users', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.gold(),
|
|
});
|
|
|
|
expect(() => subscription.updatePlan(SubscriptionPlan.silver(), 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: 'SILVER',
|
|
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 BRONZE plan', () => {
|
|
const subscription = createValidSubscription();
|
|
expect(subscription.isFree()).toBe(true);
|
|
expect(subscription.isPaid()).toBe(false);
|
|
});
|
|
|
|
it('should return true for isPaid when SILVER plan', () => {
|
|
const subscription = Subscription.create({
|
|
id: 'sub-123',
|
|
organizationId: 'org-123',
|
|
plan: SubscriptionPlan.silver(),
|
|
});
|
|
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('BRONZE');
|
|
expect(obj.status).toBe('ACTIVE');
|
|
expect(obj.stripeCustomerId).toBe('cus_123');
|
|
});
|
|
});
|
|
});
|