import { renderHook, act, waitFor } from '@testing-library/react-native' import { useMembership } from './use-membership' import { subscription, useSession } from '@/lib/auth' jest.mock('@/lib/auth', () => ({ subscription: { plans: jest.fn(), list: jest.fn(), create: jest.fn(), upgrade: jest.fn(), restore: jest.fn(), cancel: jest.fn(), credit: { topup: jest.fn(), }, }, useSession: jest.fn(), })) describe('useMembership', () => { const mockSession = { user: { id: 'user-123' }, } beforeEach(() => { jest.clearAllMocks() ;(useSession as jest.Mock).mockReturnValue({ data: mockSession }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: [], error: null }) }) describe('获取 Stripe 定价表数据', () => { it('应该成功获取定价表数据', async () => { const mockPricingData = { pricing_table_items: [ { amount: '1000', recurring: { interval: 'month' }, metadata: { grant_token: '1000' }, is_highlight: true, }, ], } ;(subscription.plans as jest.Mock).mockResolvedValue({ data: mockPricingData, error: null, }) const { result } = renderHook(() => useMembership()) await waitFor(() => { expect(result.current.stripePricingData).toEqual(mockPricingData) }) expect(subscription.plans).toHaveBeenCalledWith({ query: { id: '', key: '', }, }) }) it('应该处理定价表获取错误', async () => { ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: { message: 'Failed to fetch pricing' }, }) const { result } = renderHook(() => useMembership()) await waitFor(() => { expect(subscription.plans).toHaveBeenCalled() }) expect(result.current.stripePricingData).toBeNull() }) }) describe('获取用户订阅列表', () => { it('应该成功获取订阅列表', async () => { const mockSubscriptions = [ { type: 'licenced', status: 'active', cancelAtPeriodEnd: false, stripeSubscriptionId: 'sub-123', }, ] ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: mockSubscriptions, error: null, }) const { result } = renderHook(() => useMembership()) await waitFor(() => { expect(subscription.list).toHaveBeenCalledWith({ query: { referenceId: 'user-123' }, }) }) }) it('应该在没有用户会话时不获取订阅', () => { ;(useSession as jest.Mock).mockReturnValue({ data: null }) ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) renderHook(() => useMembership()) expect(subscription.list).not.toHaveBeenCalled() }) }) describe('创建订阅', () => { it('应该调用 subscription.create', async () => { const mockUrl = 'https://checkout.stripe.com/test' ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.create as jest.Mock).mockResolvedValue({ data: { url: mockUrl }, error: null, }) const { result } = renderHook(() => useMembership()) await act(async () => { await result.current.createSubscription({ priceId: 'price-123', productId: 'prod-123', }) }) expect(subscription.create).toHaveBeenCalledWith({ priceId: 'price-123', productId: 'prod-123', successUrl: expect.stringContaining('/membership'), cancelUrl: expect.stringContaining('/membership?canceled=true'), }) }) it('应该处理创建订阅错误', async () => { ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.create as jest.Mock).mockResolvedValue({ data: null, error: { message: 'Failed to create subscription' }, }) const { result } = renderHook(() => useMembership()) await act(async () => { await result.current.createSubscription({ priceId: 'price-123', productId: 'prod-123', }) }) expect(subscription.create).toHaveBeenCalled() }) }) describe('升级订阅', () => { it('应该成功升级订阅', async () => { const mockPricingData = { pricing_table_items: [ { amount: '1000', product_name: 'basic', recurring: { interval: 'month' }, metadata: { grant_token: '1000' } }, ], } const mockSubscriptions = [ { type: 'licenced', status: 'active', cancelAtPeriodEnd: false, stripeSubscriptionId: 'sub-123', }, ] ;(subscription.plans as jest.Mock).mockResolvedValue({ data: mockPricingData, error: null }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: mockSubscriptions, error: null }) ;(subscription.upgrade as jest.Mock).mockResolvedValue({ data: { success: true }, error: null, }) const { result } = renderHook(() => useMembership()) await waitFor(() => { expect(result.current.activeAuthSubscription).toBeDefined() }) await act(async () => { await result.current.upgradeSubscription({ credits: 1000 }) }) expect(subscription.upgrade).toHaveBeenCalled() }) it('应该处理未找到活跃订阅的情况', async () => { ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: [], error: null }) const { result } = renderHook(() => useMembership()) await act(async () => { await result.current.upgradeSubscription({ credits: 1000 }) }) expect(subscription.upgrade).not.toHaveBeenCalled() }) }) describe('恢复订阅', () => { it('应该成功恢复订阅', async () => { const mockSubscriptions = [ { type: 'licenced', status: 'active', cancelAtPeriodEnd: true, stripeSubscriptionId: 'sub-123', referenceId: 'user-123', }, ] ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: mockSubscriptions, error: null }) ;(subscription.restore as jest.Mock).mockResolvedValue({ data: { success: true }, error: null, }) const { result } = renderHook(() => useMembership()) await waitFor(() => { expect(result.current.activeAuthSubscription).toBeDefined() }) await act(async () => { await result.current.restoreSubscription() }) expect(subscription.restore).toHaveBeenCalledWith({ subscriptionId: 'sub-123', referenceId: 'user-123', }) }) it('应该处理未找到可恢复订阅的情况', async () => { ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: [], error: null }) const { result } = renderHook(() => useMembership()) await act(async () => { await result.current.restoreSubscription() }) expect(subscription.restore).not.toHaveBeenCalled() }) }) describe('充值积分', () => { it('应该成功充值积分', async () => { const mockUrl = 'https://checkout.stripe.com/topup' const mockSubscriptions = [ { type: 'metered', priceId: 'price-metered-123', creditBalance: { remainingTokenBalance: 100 }, }, ] ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: mockSubscriptions, error: null }) ;(subscription.credit.topup as jest.Mock).mockResolvedValue({ data: { url: mockUrl }, error: null, }) const { result } = renderHook(() => useMembership()) await waitFor(() => { expect(result.current.creditBalance).toBe(100) }) await act(async () => { await result.current.rechargeToken(1000) }) expect(subscription.credit.topup).toHaveBeenCalledWith({ amount: 1000, priceId: 'price-metered-123', successUrl: expect.stringContaining('/membership'), cancelUrl: expect.stringContaining('/membership?canceled=true'), }) }) it('应该处理无效充值金额', async () => { ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) const { result } = renderHook(() => useMembership()) await act(async () => { await result.current.rechargeToken(0) }) expect(subscription.credit.topup).not.toHaveBeenCalled() }) it('应该处理定价数据不可用的情况', async () => { ;(subscription.plans as jest.Mock).mockResolvedValue({ data: null, error: null }) ;(subscription.list as jest.Mock).mockResolvedValue({ data: [], error: null }) const { result } = renderHook(() => useMembership()) await act(async () => { await result.current.rechargeToken(1000) }) expect(subscription.credit.topup).not.toHaveBeenCalled() }) }) })