import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock the dependencies before importing the function vi.mock('@/lib/prisma', () => ({ prisma: { subscription: { findUnique: vi.fn(), update: vi.fn(), }, }, })); vi.mock('@/lib/stripe', () => ({ stripe: { subscriptions: { update: vi.fn(), }, }, })); import { cancelSubscription } from '@/lib/billing/cancel-subscription'; import { prisma } from '@/lib/prisma'; import { stripe } from '@/lib/stripe'; describe('cancelSubscription', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should return error when userId is missing', async () => { const result = await cancelSubscription(''); expect(result).toEqual({ success: false, error: 'User ID is required' }); expect(prisma.subscription.findUnique).not.toHaveBeenCalled(); }); it('should return error when no subscription is found in db', async () => { vi.mocked(prisma.subscription.findUnique).mockResolvedValue(null); const result = await cancelSubscription('user_123'); expect(result).toEqual({ success: false, error: 'No active subscription found' }); expect(prisma.subscription.findUnique).toHaveBeenCalledWith({ where: { userId: 'user_123' }, }); expect(stripe.subscriptions.update).not.toHaveBeenCalled(); expect(prisma.subscription.update).not.toHaveBeenCalled(); }); it('should cancel subscription in Stripe and update DB successfully', async () => { const mockDbSub = { userId: 'user_123', stripeSubscriptionId: 'sub_stripe_123', currentPeriodEnd: new Date(1702678400 * 1000), }; const mockStripeSub = { id: 'sub_stripe_123', canceled_at: 1700100000, current_period_end: 1702678400, }; vi.mocked(prisma.subscription.findUnique).mockResolvedValue(mockDbSub as any); vi.mocked(stripe.subscriptions.update).mockResolvedValue(mockStripeSub as any); vi.mocked(prisma.subscription.update).mockResolvedValue({} as any); const result = await cancelSubscription('user_123'); expect(result).toEqual({ success: true }); expect(stripe.subscriptions.update).toHaveBeenCalledWith('sub_stripe_123', { cancel_at_period_end: true, }); expect(prisma.subscription.update).toHaveBeenCalledWith({ where: { userId: 'user_123' }, data: expect.objectContaining({ cancelAtPeriodEnd: true, canceledAt: expect.any(Date), currentPeriodEnd: expect.any(Date), }), }); }); it('should support local / mock mode cancel when stripeSubscriptionId is missing', async () => { const mockDbSub = { userId: 'user_123', stripeSubscriptionId: null, }; vi.mocked(prisma.subscription.findUnique).mockResolvedValue(mockDbSub as any); vi.mocked(prisma.subscription.update).mockResolvedValue({} as any); const result = await cancelSubscription('user_123'); expect(result).toEqual({ success: true }); expect(stripe.subscriptions.update).not.toHaveBeenCalled(); expect(prisma.subscription.update).toHaveBeenCalledWith({ where: { userId: 'user_123' }, data: expect.objectContaining({ cancelAtPeriodEnd: true, canceledAt: expect.any(Date), }), }); }); it('should return error when Stripe API call fails', async () => { const mockDbSub = { userId: 'user_123', stripeSubscriptionId: 'sub_stripe_123', }; vi.mocked(prisma.subscription.findUnique).mockResolvedValue(mockDbSub as any); vi.mocked(stripe.subscriptions.update).mockRejectedValue(new Error('Stripe connection error')); const result = await cancelSubscription('user_123'); expect(result).toEqual({ success: false, error: 'Stripe connection error' }); expect(prisma.subscription.update).not.toHaveBeenCalled(); }); it('should return error when database write fails', async () => { const mockDbSub = { userId: 'user_123', stripeSubscriptionId: 'sub_stripe_123', }; const mockStripeSub = { id: 'sub_stripe_123', canceled_at: 1700100000, current_period_end: 1702678400, }; vi.mocked(prisma.subscription.findUnique).mockResolvedValue(mockDbSub as any); vi.mocked(stripe.subscriptions.update).mockResolvedValue(mockStripeSub as any); vi.mocked(prisma.subscription.update).mockRejectedValue(new Error('Prisma database error')); const result = await cancelSubscription('user_123'); expect(result).toEqual({ success: false, error: 'Prisma database error' }); }); });