expo-popcore-app/hooks/use-membership.test.ts

317 lines
9.4 KiB
TypeScript

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()
})
})
})