test(auth,payments,subs): add 58 unit tests for critical auth, payment, and subscription paths
Cover auth handlers (RegisterUser, LoginUser, RefreshToken), TokenService (token rotation, reuse attack detection), payment callback edge cases (duplicate/concurrent callbacks, multi-provider), subscription lifecycle transitions (expire, pastDue, renew), and throttler proxy guard. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import { SubscriptionEntity } from '../entities/subscription.entity';
|
||||
|
||||
describe('SubscriptionEntity — lifecycle edge cases', () => {
|
||||
const makeSub = (status?: 'ACTIVE' | 'PAST_DUE' | 'CANCELLED' | 'EXPIRED') => {
|
||||
const sub = SubscriptionEntity.createNew(
|
||||
'sub-1',
|
||||
'user-1',
|
||||
'plan-1',
|
||||
'AGENT_PRO',
|
||||
new Date('2026-01-01'),
|
||||
new Date('2026-02-01'),
|
||||
);
|
||||
sub.clearDomainEvents();
|
||||
|
||||
if (status === 'PAST_DUE') sub.markPastDue();
|
||||
if (status === 'CANCELLED') sub.cancel();
|
||||
if (status === 'EXPIRED') sub.markExpired();
|
||||
|
||||
return sub;
|
||||
};
|
||||
|
||||
describe('markExpired', () => {
|
||||
it('transitions ACTIVE to EXPIRED', () => {
|
||||
const sub = makeSub('ACTIVE');
|
||||
sub.markExpired();
|
||||
expect(sub.status).toBe('EXPIRED');
|
||||
});
|
||||
|
||||
it('transitions PAST_DUE to EXPIRED', () => {
|
||||
const sub = makeSub('PAST_DUE');
|
||||
sub.markExpired();
|
||||
expect(sub.status).toBe('EXPIRED');
|
||||
});
|
||||
|
||||
it('throws when marking CANCELLED as expired', () => {
|
||||
const sub = makeSub('CANCELLED');
|
||||
expect(() => sub.markExpired()).toThrow('Không thể đánh dấu hết hạn');
|
||||
});
|
||||
|
||||
it('throws when already expired', () => {
|
||||
const sub = makeSub('EXPIRED');
|
||||
expect(() => sub.markExpired()).toThrow('Không thể đánh dấu hết hạn');
|
||||
});
|
||||
});
|
||||
|
||||
describe('markPastDue', () => {
|
||||
it('transitions ACTIVE to PAST_DUE', () => {
|
||||
const sub = makeSub('ACTIVE');
|
||||
sub.markPastDue();
|
||||
expect(sub.status).toBe('PAST_DUE');
|
||||
});
|
||||
|
||||
it('throws when marking PAST_DUE as past due again', () => {
|
||||
const sub = makeSub('PAST_DUE');
|
||||
expect(() => sub.markPastDue()).toThrow('Không thể đánh dấu quá hạn');
|
||||
});
|
||||
|
||||
it('throws when marking CANCELLED as past due', () => {
|
||||
const sub = makeSub('CANCELLED');
|
||||
expect(() => sub.markPastDue()).toThrow('Không thể đánh dấu quá hạn');
|
||||
});
|
||||
|
||||
it('throws when marking EXPIRED as past due', () => {
|
||||
const sub = makeSub('EXPIRED');
|
||||
expect(() => sub.markPastDue()).toThrow('Không thể đánh dấu quá hạn');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renewPeriod', () => {
|
||||
it('renews from PAST_DUE back to ACTIVE with new dates', () => {
|
||||
const sub = makeSub('PAST_DUE');
|
||||
const newStart = new Date('2026-02-01');
|
||||
const newEnd = new Date('2026-03-01');
|
||||
|
||||
sub.renewPeriod(newStart, newEnd);
|
||||
|
||||
expect(sub.status).toBe('ACTIVE');
|
||||
expect(sub.currentPeriodStart).toEqual(newStart);
|
||||
expect(sub.currentPeriodEnd).toEqual(newEnd);
|
||||
});
|
||||
|
||||
it('renews from ACTIVE (extending period)', () => {
|
||||
const sub = makeSub('ACTIVE');
|
||||
const newStart = new Date('2026-03-01');
|
||||
const newEnd = new Date('2026-04-01');
|
||||
|
||||
sub.renewPeriod(newStart, newEnd);
|
||||
|
||||
expect(sub.status).toBe('ACTIVE');
|
||||
expect(sub.currentPeriodStart).toEqual(newStart);
|
||||
expect(sub.currentPeriodEnd).toEqual(newEnd);
|
||||
});
|
||||
|
||||
it('renews from EXPIRED back to ACTIVE', () => {
|
||||
const sub = makeSub('EXPIRED');
|
||||
const newStart = new Date('2026-04-01');
|
||||
const newEnd = new Date('2026-05-01');
|
||||
|
||||
sub.renewPeriod(newStart, newEnd);
|
||||
|
||||
expect(sub.status).toBe('ACTIVE');
|
||||
});
|
||||
|
||||
it('renews from CANCELLED back to ACTIVE', () => {
|
||||
const sub = makeSub('CANCELLED');
|
||||
const newStart = new Date('2026-04-01');
|
||||
const newEnd = new Date('2026-05-01');
|
||||
|
||||
sub.renewPeriod(newStart, newEnd);
|
||||
|
||||
expect(sub.status).toBe('ACTIVE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isExpired', () => {
|
||||
it('returns true when period end is in the past', () => {
|
||||
const sub = SubscriptionEntity.createNew(
|
||||
'sub-2',
|
||||
'user-1',
|
||||
'plan-1',
|
||||
'FREE',
|
||||
new Date('2020-01-01'),
|
||||
new Date('2020-02-01'),
|
||||
);
|
||||
expect(sub.isExpired()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when period end is in the future', () => {
|
||||
const sub = SubscriptionEntity.createNew(
|
||||
'sub-3',
|
||||
'user-1',
|
||||
'plan-1',
|
||||
'FREE',
|
||||
new Date('2030-01-01'),
|
||||
new Date('2030-02-01'),
|
||||
);
|
||||
expect(sub.isExpired()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upgrade constraints', () => {
|
||||
it('throws when upgrading from PAST_DUE', () => {
|
||||
const sub = makeSub('PAST_DUE');
|
||||
expect(() => sub.upgrade('plan-2', 'ENTERPRISE')).toThrow();
|
||||
});
|
||||
|
||||
it('throws when upgrading from EXPIRED', () => {
|
||||
const sub = makeSub('EXPIRED');
|
||||
expect(() => sub.upgrade('plan-2', 'ENTERPRISE')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancel constraints', () => {
|
||||
it('cancels from PAST_DUE', () => {
|
||||
const sub = makeSub('PAST_DUE');
|
||||
sub.cancel();
|
||||
expect(sub.status).toBe('CANCELLED');
|
||||
expect(sub.cancelledAt).not.toBeNull();
|
||||
});
|
||||
|
||||
it('cancels from ACTIVE', () => {
|
||||
const sub = makeSub('ACTIVE');
|
||||
sub.cancel();
|
||||
expect(sub.status).toBe('CANCELLED');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user