"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const typeorm_1 = require("@nestjs/typeorm"); const common_1 = require("@nestjs/common"); const billing_service_1 = require("../services/billing.service"); const subscription_entity_1 = require("../entities/subscription.entity"); const invoice_entity_1 = require("../entities/invoice.entity"); const payment_method_entity_1 = require("../entities/payment-method.entity"); describe('BillingService - Edge Cases', () => { let service; let subscriptionRepo; let invoiceRepo; let paymentMethodRepo; const mockTenantId = '550e8400-e29b-41d4-a716-446655440001'; const mockPlanId = '550e8400-e29b-41d4-a716-446655440002'; const createMockSubscription = (overrides = {}) => ({ id: 'sub-001', tenant_id: mockTenantId, plan_id: mockPlanId, plan: null, status: subscription_entity_1.SubscriptionStatus.ACTIVE, current_period_start: new Date('2026-01-01'), current_period_end: new Date('2026-02-01'), trial_end: null, cancelled_at: null, external_subscription_id: '', payment_provider: 'stripe', metadata: {}, created_at: new Date(), updated_at: new Date(), ...overrides, }); const createMockInvoice = (overrides = {}) => ({ id: 'inv-001', tenant_id: mockTenantId, subscription_id: 'sub-001', invoice_number: 'INV-202601-000001', status: invoice_entity_1.InvoiceStatus.OPEN, currency: 'MXN', subtotal: 100, tax: 16, total: 116, due_date: new Date('2026-01-15'), paid_at: null, external_invoice_id: '', pdf_url: null, line_items: [{ description: 'Pro Plan', quantity: 1, unit_price: 100, amount: 100 }], billing_details: null, created_at: new Date(), updated_at: new Date(), ...overrides, }); const createMockPaymentMethod = (overrides = {}) => ({ id: 'pm-001', tenant_id: mockTenantId, type: payment_method_entity_1.PaymentMethodType.CARD, card_last_four: '4242', card_brand: 'visa', card_exp_month: 12, card_exp_year: 2026, external_payment_method_id: '', payment_provider: 'stripe', is_default: true, is_active: true, metadata: {}, created_at: new Date(), updated_at: new Date(), ...overrides, }); beforeEach(async () => { const mockSubscriptionRepo = { create: jest.fn(), save: jest.fn(), findOne: jest.fn(), find: jest.fn(), update: jest.fn(), }; const mockInvoiceRepo = { create: jest.fn(), save: jest.fn(), findOne: jest.fn(), find: jest.fn(), findAndCount: jest.fn(), count: jest.fn(), }; const mockPaymentMethodRepo = { create: jest.fn(), save: jest.fn(), findOne: jest.fn(), find: jest.fn(), update: jest.fn(), }; const module = await testing_1.Test.createTestingModule({ providers: [ billing_service_1.BillingService, { provide: (0, typeorm_1.getRepositoryToken)(subscription_entity_1.Subscription), useValue: mockSubscriptionRepo }, { provide: (0, typeorm_1.getRepositoryToken)(invoice_entity_1.Invoice), useValue: mockInvoiceRepo }, { provide: (0, typeorm_1.getRepositoryToken)(payment_method_entity_1.PaymentMethod), useValue: mockPaymentMethodRepo }, ], }).compile(); service = module.get(billing_service_1.BillingService); subscriptionRepo = module.get((0, typeorm_1.getRepositoryToken)(subscription_entity_1.Subscription)); invoiceRepo = module.get((0, typeorm_1.getRepositoryToken)(invoice_entity_1.Invoice)); paymentMethodRepo = module.get((0, typeorm_1.getRepositoryToken)(payment_method_entity_1.PaymentMethod)); }); afterEach(() => { jest.clearAllMocks(); }); describe('createSubscription - Edge Cases', () => { it('should create subscription with far future trial end date', async () => { const farFutureTrial = new Date(); farFutureTrial.setFullYear(farFutureTrial.getFullYear() + 1); const trialSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.TRIAL, trial_end: farFutureTrial, }); subscriptionRepo.create.mockReturnValue(trialSub); subscriptionRepo.save.mockResolvedValue(trialSub); const dto = { tenant_id: mockTenantId, plan_id: mockPlanId, payment_provider: 'stripe', trial_end: farFutureTrial.toISOString(), }; const result = await service.createSubscription(dto); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.TRIAL); expect(result.trial_end).toEqual(farFutureTrial); }); it('should create subscription without optional payment_provider', async () => { const subscription = createMockSubscription({ payment_provider: undefined, }); subscriptionRepo.create.mockReturnValue(subscription); subscriptionRepo.save.mockResolvedValue(subscription); const dto = { tenant_id: mockTenantId, plan_id: mockPlanId, }; await service.createSubscription(dto); expect(subscriptionRepo.create).toHaveBeenCalled(); }); it('should set period end one month from creation', async () => { const now = new Date('2026-01-15T10:00:00Z'); jest.useFakeTimers(); jest.setSystemTime(now); const subscription = createMockSubscription({ current_period_start: now, current_period_end: new Date('2026-02-15T10:00:00Z'), }); subscriptionRepo.create.mockReturnValue(subscription); subscriptionRepo.save.mockResolvedValue(subscription); await service.createSubscription({ tenant_id: mockTenantId, plan_id: mockPlanId, payment_provider: 'stripe', }); expect(subscriptionRepo.create).toHaveBeenCalledWith(expect.objectContaining({ current_period_start: now, })); jest.useRealTimers(); }); }); describe('cancelSubscription - Edge Cases', () => { it('should cancel with custom reason in metadata', async () => { const existingSub = createMockSubscription({ metadata: { existing_key: 'existing_value' }, }); const cancelledSub = createMockSubscription({ cancelled_at: new Date(), metadata: { existing_key: 'existing_value', cancellation_reason: 'Too expensive', }, }); subscriptionRepo.findOne.mockResolvedValue(existingSub); subscriptionRepo.save.mockResolvedValue(cancelledSub); const result = await service.cancelSubscription(mockTenantId, { immediately: false, reason: 'Too expensive', }); expect(result.metadata.cancellation_reason).toBe('Too expensive'); expect(result.metadata.existing_key).toBe('existing_value'); }); it('should cancel trial subscription immediately', async () => { const trialSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.TRIAL, trial_end: new Date('2026-01-20'), }); const cancelledSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.CANCELLED, cancelled_at: new Date(), }); subscriptionRepo.findOne.mockResolvedValue(trialSub); subscriptionRepo.save.mockResolvedValue(cancelledSub); const result = await service.cancelSubscription(mockTenantId, { immediately: true, }); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.CANCELLED); }); it('should preserve active status when scheduling end-of-period cancellation', async () => { const activeSub = createMockSubscription({ current_period_end: new Date('2026-02-01'), }); subscriptionRepo.findOne.mockResolvedValue(activeSub); subscriptionRepo.save.mockImplementation((sub) => Promise.resolve({ ...sub, cancelled_at: new Date() })); const result = await service.cancelSubscription(mockTenantId, { immediately: false, }); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.ACTIVE); expect(result.cancelled_at).toBeDefined(); }); }); describe('changePlan - Edge Cases (Upgrade/Downgrade)', () => { it('should upgrade from basic to pro plan', async () => { const basicSub = createMockSubscription({ plan_id: 'plan-basic', }); const upgradedSub = createMockSubscription({ plan_id: 'plan-pro', metadata: { plan_changed_at: expect.any(String) }, }); subscriptionRepo.findOne.mockResolvedValue(basicSub); subscriptionRepo.save.mockResolvedValue(upgradedSub); const result = await service.changePlan(mockTenantId, 'plan-pro'); expect(result.plan_id).toBe('plan-pro'); expect(result.metadata.plan_changed_at).toBeDefined(); }); it('should downgrade from pro to basic plan', async () => { const proSub = createMockSubscription({ plan_id: 'plan-pro', }); const downgradedSub = createMockSubscription({ plan_id: 'plan-basic', metadata: { plan_changed_at: expect.any(String) }, }); subscriptionRepo.findOne.mockResolvedValue(proSub); subscriptionRepo.save.mockResolvedValue(downgradedSub); const result = await service.changePlan(mockTenantId, 'plan-basic'); expect(result.plan_id).toBe('plan-basic'); }); it('should preserve existing metadata when changing plan', async () => { const existingSub = createMockSubscription({ plan_id: 'plan-basic', metadata: { original_signup: '2025-01-01', referral_code: 'ABC123', }, }); subscriptionRepo.findOne.mockResolvedValue(existingSub); subscriptionRepo.save.mockImplementation((sub) => Promise.resolve(sub)); const result = await service.changePlan(mockTenantId, 'plan-pro'); expect(result.metadata.original_signup).toBe('2025-01-01'); expect(result.metadata.referral_code).toBe('ABC123'); expect(result.metadata.plan_changed_at).toBeDefined(); }); }); describe('renewSubscription - Edge Cases', () => { it('should renew expired subscription', async () => { const expiredSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.EXPIRED, current_period_start: new Date('2025-12-01'), current_period_end: new Date('2026-01-01'), }); const renewedSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.ACTIVE, current_period_start: new Date('2026-01-01'), current_period_end: new Date('2026-02-01'), }); subscriptionRepo.findOne.mockResolvedValue(expiredSub); subscriptionRepo.save.mockResolvedValue(renewedSub); const result = await service.renewSubscription(mockTenantId); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.ACTIVE); }); it('should renew past_due subscription after payment', async () => { const pastDueSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.PAST_DUE, current_period_start: new Date('2025-12-01'), current_period_end: new Date('2026-01-01'), }); const renewedSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.ACTIVE, current_period_start: new Date('2026-01-01'), current_period_end: new Date('2026-02-01'), }); subscriptionRepo.findOne.mockResolvedValue(pastDueSub); subscriptionRepo.save.mockResolvedValue(renewedSub); const result = await service.renewSubscription(mockTenantId); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.ACTIVE); }); it('should throw NotFoundException when renewing non-existent subscription', async () => { subscriptionRepo.findOne.mockResolvedValue(null); await expect(service.renewSubscription(mockTenantId)).rejects.toThrow(common_1.NotFoundException); }); it('should correctly calculate new period end across year boundary', async () => { const decemberSub = createMockSubscription({ current_period_start: new Date('2025-12-15'), current_period_end: new Date('2026-01-15'), }); subscriptionRepo.findOne.mockResolvedValue(decemberSub); subscriptionRepo.save.mockImplementation((sub) => Promise.resolve(sub)); const result = await service.renewSubscription(mockTenantId); expect(result.current_period_start).toEqual(new Date('2026-01-15')); const expectedEnd = new Date('2026-01-15'); expectedEnd.setMonth(expectedEnd.getMonth() + 1); expect(result.current_period_end).toEqual(expectedEnd); }); }); describe('createInvoice - Edge Cases', () => { it('should calculate tax correctly (16% IVA)', async () => { invoiceRepo.count.mockResolvedValue(0); invoiceRepo.create.mockImplementation((data) => data); invoiceRepo.save.mockImplementation((invoice) => Promise.resolve(invoice)); await service.createInvoice(mockTenantId, 'sub-001', [ { description: 'Pro Plan', quantity: 1, unit_price: 1000 }, ]); expect(invoiceRepo.create).toHaveBeenCalledWith(expect.objectContaining({ subtotal: 1000, tax: 160, total: 1160, })); }); it('should calculate totals for multiple line items', async () => { invoiceRepo.count.mockResolvedValue(0); invoiceRepo.create.mockImplementation((data) => data); invoiceRepo.save.mockImplementation((invoice) => Promise.resolve(invoice)); await service.createInvoice(mockTenantId, 'sub-001', [ { description: 'Pro Plan', quantity: 1, unit_price: 100 }, { description: 'Extra Users', quantity: 5, unit_price: 10 }, { description: 'Storage Add-on', quantity: 2, unit_price: 25 }, ]); expect(invoiceRepo.create).toHaveBeenCalledWith(expect.objectContaining({ subtotal: 200, tax: 32, total: 232, })); }); it('should generate unique invoice number', async () => { jest.useFakeTimers(); jest.setSystemTime(new Date('2026-03-15')); invoiceRepo.count.mockResolvedValue(42); invoiceRepo.create.mockImplementation((data) => data); invoiceRepo.save.mockImplementation((invoice) => Promise.resolve(invoice)); await service.createInvoice(mockTenantId, 'sub-001', [ { description: 'Pro Plan', quantity: 1, unit_price: 100 }, ]); expect(invoiceRepo.create).toHaveBeenCalledWith(expect.objectContaining({ invoice_number: 'INV-202603-000043', })); jest.useRealTimers(); }); it('should set due date 15 days from creation', async () => { const now = new Date('2026-01-10T12:00:00Z'); jest.useFakeTimers(); jest.setSystemTime(now); invoiceRepo.count.mockResolvedValue(0); invoiceRepo.create.mockImplementation((data) => data); invoiceRepo.save.mockImplementation((invoice) => Promise.resolve(invoice)); await service.createInvoice(mockTenantId, 'sub-001', [ { description: 'Pro Plan', quantity: 1, unit_price: 100 }, ]); const expectedDueDate = new Date('2026-01-25T12:00:00Z'); expect(invoiceRepo.create).toHaveBeenCalledWith(expect.objectContaining({ due_date: expectedDueDate, })); jest.useRealTimers(); }); it('should handle zero quantity line items', async () => { invoiceRepo.count.mockResolvedValue(0); invoiceRepo.create.mockImplementation((data) => data); invoiceRepo.save.mockImplementation((invoice) => Promise.resolve(invoice)); await service.createInvoice(mockTenantId, 'sub-001', [ { description: 'Pro Plan', quantity: 0, unit_price: 100 }, ]); expect(invoiceRepo.create).toHaveBeenCalledWith(expect.objectContaining({ subtotal: 0, tax: 0, total: 0, })); }); it('should handle empty line items array', async () => { invoiceRepo.count.mockResolvedValue(0); invoiceRepo.create.mockImplementation((data) => data); invoiceRepo.save.mockImplementation((invoice) => Promise.resolve(invoice)); await service.createInvoice(mockTenantId, 'sub-001', []); expect(invoiceRepo.create).toHaveBeenCalledWith(expect.objectContaining({ subtotal: 0, tax: 0, total: 0, line_items: [], })); }); }); describe('getInvoices - Edge Cases', () => { it('should return empty array when no invoices exist', async () => { invoiceRepo.findAndCount.mockResolvedValue([[], 0]); const result = await service.getInvoices(mockTenantId); expect(result.data).toHaveLength(0); expect(result.total).toBe(0); }); it('should handle high page numbers with no results', async () => { invoiceRepo.findAndCount.mockResolvedValue([[], 0]); const result = await service.getInvoices(mockTenantId, { page: 999, limit: 10 }); expect(result.data).toHaveLength(0); expect(result.page).toBe(999); }); it('should handle custom limit values', async () => { const invoices = Array(50) .fill(null) .map((_, i) => createMockInvoice({ id: `inv-${i}` })); invoiceRepo.findAndCount.mockResolvedValue([invoices.slice(0, 25), 50]); const result = await service.getInvoices(mockTenantId, { page: 1, limit: 25 }); expect(result.data).toHaveLength(25); expect(result.limit).toBe(25); expect(result.total).toBe(50); }); }); describe('voidInvoice - Edge Cases', () => { it('should void draft invoice', async () => { const draftInvoice = createMockInvoice({ status: invoice_entity_1.InvoiceStatus.DRAFT }); invoiceRepo.findOne.mockResolvedValue(draftInvoice); invoiceRepo.save.mockResolvedValue(createMockInvoice({ status: invoice_entity_1.InvoiceStatus.VOID })); const result = await service.voidInvoice('inv-001', mockTenantId); expect(result.status).toBe(invoice_entity_1.InvoiceStatus.VOID); }); it('should void open invoice', async () => { const openInvoice = createMockInvoice({ status: invoice_entity_1.InvoiceStatus.OPEN }); invoiceRepo.findOne.mockResolvedValue(openInvoice); invoiceRepo.save.mockResolvedValue(createMockInvoice({ status: invoice_entity_1.InvoiceStatus.VOID })); const result = await service.voidInvoice('inv-001', mockTenantId); expect(result.status).toBe(invoice_entity_1.InvoiceStatus.VOID); }); it('should throw when voiding already voided invoice', async () => { const voidedInvoice = createMockInvoice({ status: invoice_entity_1.InvoiceStatus.VOID }); invoiceRepo.findOne.mockResolvedValue(voidedInvoice); invoiceRepo.save.mockResolvedValue(voidedInvoice); const result = await service.voidInvoice('inv-001', mockTenantId); expect(result.status).toBe(invoice_entity_1.InvoiceStatus.VOID); }); }); describe('markInvoicePaid - Edge Cases', () => { it('should mark draft invoice as paid', async () => { const draftInvoice = createMockInvoice({ status: invoice_entity_1.InvoiceStatus.DRAFT }); invoiceRepo.findOne.mockResolvedValue(draftInvoice); invoiceRepo.save.mockResolvedValue(createMockInvoice({ status: invoice_entity_1.InvoiceStatus.PAID, paid_at: new Date() })); const result = await service.markInvoicePaid('inv-001', mockTenantId); expect(result.status).toBe(invoice_entity_1.InvoiceStatus.PAID); expect(result.paid_at).toBeDefined(); }); it('should update paid_at timestamp', async () => { const now = new Date('2026-01-15T14:30:00Z'); jest.useFakeTimers(); jest.setSystemTime(now); const openInvoice = createMockInvoice({ status: invoice_entity_1.InvoiceStatus.OPEN }); invoiceRepo.findOne.mockResolvedValue(openInvoice); invoiceRepo.save.mockImplementation((invoice) => Promise.resolve(invoice)); const result = await service.markInvoicePaid('inv-001', mockTenantId); expect(result.paid_at).toEqual(now); jest.useRealTimers(); }); }); describe('addPaymentMethod - Edge Cases', () => { it('should add non-default payment method without updating others', async () => { const newMethod = createMockPaymentMethod({ id: 'pm-002', is_default: false, }); paymentMethodRepo.create.mockReturnValue(newMethod); paymentMethodRepo.save.mockResolvedValue(newMethod); const dto = { type: payment_method_entity_1.PaymentMethodType.CARD, card_last_four: '1234', card_brand: 'mastercard', is_default: false, }; await service.addPaymentMethod(mockTenantId, dto); expect(paymentMethodRepo.update).not.toHaveBeenCalled(); }); it('should handle bank_transfer payment method type', async () => { const bankMethod = createMockPaymentMethod({ id: 'pm-003', type: payment_method_entity_1.PaymentMethodType.BANK_TRANSFER, }); paymentMethodRepo.create.mockReturnValue(bankMethod); paymentMethodRepo.save.mockResolvedValue(bankMethod); const dto = { type: payment_method_entity_1.PaymentMethodType.BANK_TRANSFER, is_default: false, }; await service.addPaymentMethod(mockTenantId, dto); expect(paymentMethodRepo.create).toHaveBeenCalledWith(expect.objectContaining({ type: payment_method_entity_1.PaymentMethodType.BANK_TRANSFER, })); }); }); describe('getPaymentMethods - Edge Cases', () => { it('should return empty array when no payment methods exist', async () => { paymentMethodRepo.find.mockResolvedValue([]); const result = await service.getPaymentMethods(mockTenantId); expect(result).toHaveLength(0); }); it('should return only active payment methods', async () => { const activeMethod = createMockPaymentMethod({ is_active: true }); paymentMethodRepo.find.mockResolvedValue([activeMethod]); const result = await service.getPaymentMethods(mockTenantId); expect(result).toHaveLength(1); expect(paymentMethodRepo.find).toHaveBeenCalledWith({ where: { tenant_id: mockTenantId, is_active: true }, order: { is_default: 'DESC', created_at: 'DESC' }, }); }); it('should order payment methods with default first', async () => { const methods = [ createMockPaymentMethod({ id: 'pm-001', is_default: false, created_at: new Date('2026-01-01') }), createMockPaymentMethod({ id: 'pm-002', is_default: true, created_at: new Date('2026-01-02') }), createMockPaymentMethod({ id: 'pm-003', is_default: false, created_at: new Date('2026-01-03') }), ]; paymentMethodRepo.find.mockResolvedValue(methods); await service.getPaymentMethods(mockTenantId); expect(paymentMethodRepo.find).toHaveBeenCalledWith(expect.objectContaining({ order: { is_default: 'DESC', created_at: 'DESC' }, })); }); }); describe('setDefaultPaymentMethod - Edge Cases', () => { it('should unset previous default when setting new default', async () => { const newDefault = createMockPaymentMethod({ id: 'pm-002', is_default: false, }); paymentMethodRepo.findOne.mockResolvedValue(newDefault); paymentMethodRepo.update.mockResolvedValue({ affected: 1 }); paymentMethodRepo.save.mockResolvedValue(createMockPaymentMethod({ id: 'pm-002', is_default: true })); await service.setDefaultPaymentMethod('pm-002', mockTenantId); expect(paymentMethodRepo.update).toHaveBeenCalledWith({ tenant_id: mockTenantId, is_default: true }, { is_default: false }); }); it('should handle setting already default payment method as default', async () => { const alreadyDefault = createMockPaymentMethod({ is_default: true }); paymentMethodRepo.findOne.mockResolvedValue(alreadyDefault); paymentMethodRepo.update.mockResolvedValue({ affected: 1 }); paymentMethodRepo.save.mockResolvedValue(alreadyDefault); const result = await service.setDefaultPaymentMethod('pm-001', mockTenantId); expect(result.is_default).toBe(true); }); }); describe('removePaymentMethod - Edge Cases', () => { it('should deactivate instead of delete payment method', async () => { const nonDefaultMethod = createMockPaymentMethod({ id: 'pm-002', is_default: false, is_active: true, }); paymentMethodRepo.findOne.mockResolvedValue(nonDefaultMethod); paymentMethodRepo.save.mockImplementation((pm) => Promise.resolve(pm)); await service.removePaymentMethod('pm-002', mockTenantId); expect(paymentMethodRepo.save).toHaveBeenCalledWith(expect.objectContaining({ is_active: false, })); }); it('should throw when trying to remove default payment method', async () => { const defaultMethod = createMockPaymentMethod({ is_default: true, is_active: true, }); paymentMethodRepo.findOne.mockResolvedValue(defaultMethod); await expect(service.removePaymentMethod('pm-001', mockTenantId)).rejects.toThrow(common_1.BadRequestException); }); }); describe('getBillingSummary - Edge Cases', () => { it('should return null values when no subscription or payment method', async () => { subscriptionRepo.findOne.mockResolvedValue(null); paymentMethodRepo.findOne.mockResolvedValue(null); invoiceRepo.find.mockResolvedValue([]); const result = await service.getBillingSummary(mockTenantId); expect(result.subscription).toBeNull(); expect(result.defaultPaymentMethod).toBeNull(); expect(result.pendingInvoices).toBe(0); expect(result.totalDue).toBe(0); }); it('should calculate total due from multiple pending invoices', async () => { const pendingInvoices = [ createMockInvoice({ id: 'inv-001', total: 116, status: invoice_entity_1.InvoiceStatus.OPEN }), createMockInvoice({ id: 'inv-002', total: 58, status: invoice_entity_1.InvoiceStatus.OPEN }), createMockInvoice({ id: 'inv-003', total: 232, status: invoice_entity_1.InvoiceStatus.OPEN }), ]; subscriptionRepo.findOne.mockResolvedValue(createMockSubscription()); paymentMethodRepo.findOne.mockResolvedValue(createMockPaymentMethod()); invoiceRepo.find.mockResolvedValue(pendingInvoices); const result = await service.getBillingSummary(mockTenantId); expect(result.pendingInvoices).toBe(3); expect(result.totalDue).toBe(406); }); it('should handle decimal totals correctly', async () => { const pendingInvoices = [ createMockInvoice({ id: 'inv-001', total: 116.5, status: invoice_entity_1.InvoiceStatus.OPEN }), createMockInvoice({ id: 'inv-002', total: 58.25, status: invoice_entity_1.InvoiceStatus.OPEN }), ]; subscriptionRepo.findOne.mockResolvedValue(createMockSubscription()); paymentMethodRepo.findOne.mockResolvedValue(createMockPaymentMethod()); invoiceRepo.find.mockResolvedValue(pendingInvoices); const result = await service.getBillingSummary(mockTenantId); expect(result.totalDue).toBe(174.75); }); }); describe('checkSubscriptionStatus - Edge Cases', () => { it('should return zero days remaining when period has ended', async () => { const expiredSub = createMockSubscription({ current_period_end: new Date('2025-12-01'), }); subscriptionRepo.findOne.mockResolvedValue(expiredSub); const result = await service.checkSubscriptionStatus(mockTenantId); expect(result.daysRemaining).toBe(0); }); it('should calculate days remaining correctly', async () => { jest.useFakeTimers(); jest.setSystemTime(new Date('2026-01-10')); const activeSub = createMockSubscription({ current_period_end: new Date('2026-01-25'), }); subscriptionRepo.findOne.mockResolvedValue(activeSub); const result = await service.checkSubscriptionStatus(mockTenantId); expect(result.daysRemaining).toBe(15); expect(result.isActive).toBe(true); jest.useRealTimers(); }); it('should return inactive for past_due subscription', async () => { const pastDueSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.PAST_DUE, current_period_end: new Date('2026-02-01'), }); subscriptionRepo.findOne.mockResolvedValue(pastDueSub); const result = await service.checkSubscriptionStatus(mockTenantId); expect(result.isActive).toBe(false); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.PAST_DUE); }); it('should return inactive for cancelled subscription', async () => { const cancelledSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.CANCELLED, current_period_end: new Date('2026-02-01'), }); subscriptionRepo.findOne.mockResolvedValue(cancelledSub); const result = await service.checkSubscriptionStatus(mockTenantId); expect(result.isActive).toBe(false); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.CANCELLED); }); it('should return active for trial subscription', async () => { const trialSub = createMockSubscription({ status: subscription_entity_1.SubscriptionStatus.TRIAL, current_period_end: new Date('2026-02-01'), trial_end: new Date('2026-01-15'), }); subscriptionRepo.findOne.mockResolvedValue(trialSub); const result = await service.checkSubscriptionStatus(mockTenantId); expect(result.isActive).toBe(true); expect(result.status).toBe(subscription_entity_1.SubscriptionStatus.TRIAL); }); }); }); //# sourceMappingURL=billing-edge-cases.spec.js.map