fix: bug
This commit is contained in:
404
hooks/use-membership.test.ts
Normal file
404
hooks/use-membership.test.ts
Normal file
@@ -0,0 +1,404 @@
|
||||
import { renderHook, act, waitFor } from '@testing-library/react-native'
|
||||
import { useMembership } from './use-membership'
|
||||
import { subscription, useSession } from '@/lib/auth'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { Linking, Alert } from 'react-native'
|
||||
|
||||
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(),
|
||||
}))
|
||||
|
||||
jest.mock('expo-router', () => ({
|
||||
useRouter: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock('react-native', () => ({
|
||||
Linking: {
|
||||
openURL: jest.fn(),
|
||||
},
|
||||
Alert: {
|
||||
alert: jest.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const mockUseQuery = jest.fn()
|
||||
const mockUseMutation = jest.fn()
|
||||
|
||||
jest.mock('@tanstack/react-query', () => ({
|
||||
useQuery: (...args: any[]) => mockUseQuery(args[0]),
|
||||
useMutation: (...args: any[]) => mockUseMutation(args[0]),
|
||||
}))
|
||||
|
||||
describe('useMembership', () => {
|
||||
const mockRouter = { push: jest.fn() }
|
||||
const mockSession = {
|
||||
user: { id: 'user-123' },
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
;(useRouter as jest.Mock).mockReturnValue(mockRouter)
|
||||
;(useSession as jest.Mock).mockReturnValue({ data: mockSession })
|
||||
|
||||
mockUseQuery.mockReturnValue({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
})
|
||||
|
||||
mockUseMutation.mockReturnValue({
|
||||
mutate: jest.fn(),
|
||||
})
|
||||
})
|
||||
|
||||
describe('获取 Stripe 定价表数据', () => {
|
||||
it('应该成功获取定价表数据', async () => {
|
||||
const mockPricingData = {
|
||||
pricing_table_items: [
|
||||
{
|
||||
amount: '1000',
|
||||
recurring: { interval: 'month' },
|
||||
metadata: { grant_token: '1000' },
|
||||
is_highlight: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
mockUseQuery.mockReturnValue({
|
||||
data: mockPricingData,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
expect(result.current.stripePricingData).toEqual(mockPricingData)
|
||||
})
|
||||
})
|
||||
|
||||
describe('获取用户订阅列表', () => {
|
||||
it('应该成功获取订阅列表', async () => {
|
||||
const mockSubscriptions = [
|
||||
{
|
||||
type: 'licenced',
|
||||
status: 'active',
|
||||
cancelAtPeriodEnd: false,
|
||||
stripeSubscriptionId: 'sub-123',
|
||||
},
|
||||
]
|
||||
|
||||
let queryCallCount = 0
|
||||
mockUseQuery.mockImplementation((config) => {
|
||||
queryCallCount++
|
||||
if (queryCallCount === 2) {
|
||||
return {
|
||||
data: mockSubscriptions,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
expect(result.current.isLoadingSubscriptions).toBe(false)
|
||||
})
|
||||
|
||||
it('应该在没有用户会话时不获取订阅', () => {
|
||||
;(useSession as jest.Mock).mockReturnValue({ data: null })
|
||||
|
||||
mockUseQuery.mockImplementation((config) => {
|
||||
if (config.enabled === false) {
|
||||
return {
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
renderHook(() => useMembership())
|
||||
|
||||
expect(mockUseQuery).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('创建订阅', () => {
|
||||
it('应该成功创建订阅并打开支付链接', async () => {
|
||||
const mockUrl = 'https://checkout.stripe.com/test'
|
||||
;(subscription.create as jest.Mock).mockResolvedValue({
|
||||
data: { url: mockUrl },
|
||||
error: null,
|
||||
})
|
||||
|
||||
const mutateFn = jest.fn(async (variables) => {
|
||||
const { data, error } = await subscription.create({
|
||||
priceId: variables.priceId,
|
||||
productId: variables.productId,
|
||||
successUrl: expect.stringContaining('/membership'),
|
||||
cancelUrl: expect.stringContaining('/membership?canceled=true'),
|
||||
})
|
||||
if (error) throw new Error(error.message)
|
||||
if (data?.url) await Linking.openURL(data.url)
|
||||
return data
|
||||
})
|
||||
|
||||
mockUseMutation.mockReturnValue({
|
||||
mutate: mutateFn,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.createSubscription({
|
||||
priceId: 'price-123',
|
||||
productId: 'prod-123',
|
||||
})
|
||||
})
|
||||
|
||||
expect(mutateFn).toHaveBeenCalled()
|
||||
expect(Linking.openURL).toHaveBeenCalledWith(mockUrl)
|
||||
})
|
||||
|
||||
it('应该处理创建订阅错误', async () => {
|
||||
;(subscription.create as jest.Mock).mockResolvedValue({
|
||||
data: null,
|
||||
error: { message: 'Failed to create subscription' },
|
||||
})
|
||||
|
||||
const mutateFn = jest.fn(async (variables) => {
|
||||
try {
|
||||
const { data, error } = await subscription.create(variables)
|
||||
if (error) throw new Error(error.message)
|
||||
return data
|
||||
} catch (error) {
|
||||
Alert.alert('错误', '订阅创建失败')
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
mockUseMutation.mockReturnValue({
|
||||
mutate: mutateFn,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.createSubscription({
|
||||
priceId: 'price-123',
|
||||
productId: 'prod-123',
|
||||
})
|
||||
} catch (e) {
|
||||
// Expected error
|
||||
}
|
||||
})
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith('错误', '订阅创建失败')
|
||||
})
|
||||
})
|
||||
|
||||
describe('升级订阅', () => {
|
||||
it('应该成功升级订阅', async () => {
|
||||
const mockSubscriptions = [
|
||||
{
|
||||
type: 'licenced',
|
||||
status: 'active',
|
||||
cancelAtPeriodEnd: false,
|
||||
stripeSubscriptionId: 'sub-123',
|
||||
},
|
||||
]
|
||||
|
||||
let queryCallCount = 0
|
||||
mockUseQuery.mockImplementation(() => {
|
||||
queryCallCount++
|
||||
if (queryCallCount === 2) {
|
||||
return {
|
||||
data: mockSubscriptions,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
;(subscription.upgrade as jest.Mock).mockResolvedValue({
|
||||
data: { success: true },
|
||||
error: null,
|
||||
})
|
||||
|
||||
const mutateFn = jest.fn()
|
||||
mockUseMutation.mockReturnValue({
|
||||
mutate: mutateFn,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
await act(async () => {
|
||||
result.current.upgradeSubscription({ credits: 1000 })
|
||||
})
|
||||
|
||||
expect(mutateFn).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('恢复订阅', () => {
|
||||
it('应该成功恢复订阅', async () => {
|
||||
const mockSubscriptions = [
|
||||
{
|
||||
type: 'licenced',
|
||||
status: 'active',
|
||||
cancelAtPeriodEnd: true,
|
||||
stripeSubscriptionId: 'sub-123',
|
||||
referenceId: 'user-123',
|
||||
},
|
||||
]
|
||||
|
||||
let queryCallCount = 0
|
||||
mockUseQuery.mockImplementation(() => {
|
||||
queryCallCount++
|
||||
if (queryCallCount === 2) {
|
||||
return {
|
||||
data: mockSubscriptions,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
;(subscription.restore as jest.Mock).mockResolvedValue({
|
||||
data: { success: true },
|
||||
error: null,
|
||||
})
|
||||
|
||||
const mutateFn = jest.fn()
|
||||
mockUseMutation.mockReturnValue({
|
||||
mutate: mutateFn,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
await act(async () => {
|
||||
result.current.restoreSubscription()
|
||||
})
|
||||
|
||||
expect(mutateFn).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('充值积分', () => {
|
||||
it('应该成功充值积分并打开支付链接', async () => {
|
||||
const mockUrl = 'https://checkout.stripe.com/topup'
|
||||
const mockSubscriptions = [
|
||||
{
|
||||
type: 'metered',
|
||||
priceId: 'price-metered-123',
|
||||
},
|
||||
]
|
||||
|
||||
let queryCallCount = 0
|
||||
mockUseQuery.mockImplementation(() => {
|
||||
queryCallCount++
|
||||
if (queryCallCount === 2) {
|
||||
return {
|
||||
data: mockSubscriptions,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
;(subscription.credit.topup as jest.Mock).mockResolvedValue({
|
||||
data: { url: mockUrl },
|
||||
error: null,
|
||||
})
|
||||
|
||||
const mutateFn = jest.fn(async (amount) => {
|
||||
const { data, error } = await subscription.credit.topup({
|
||||
amount,
|
||||
priceId: 'price-metered-123',
|
||||
successUrl: expect.stringContaining('/membership'),
|
||||
cancelUrl: expect.stringContaining('/membership?canceled=true'),
|
||||
})
|
||||
if (error) throw new Error(error.message)
|
||||
if (data?.url) await Linking.openURL(data.url)
|
||||
return data
|
||||
})
|
||||
|
||||
mockUseMutation.mockReturnValue({
|
||||
mutate: mutateFn,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
await act(async () => {
|
||||
await result.current.rechargeToken(1000)
|
||||
})
|
||||
|
||||
expect(mutateFn).toHaveBeenCalledWith(1000)
|
||||
expect(Linking.openURL).toHaveBeenCalledWith(mockUrl)
|
||||
})
|
||||
|
||||
it('应该处理无效充值金额', async () => {
|
||||
const mutateFn = jest.fn(async (amount) => {
|
||||
try {
|
||||
if (!amount || amount <= 0) throw new Error('充值金额无效')
|
||||
} catch (error) {
|
||||
Alert.alert('错误', '充值失败')
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
mockUseMutation.mockReturnValue({
|
||||
mutate: mutateFn,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useMembership())
|
||||
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.rechargeToken(0)
|
||||
} catch (e) {
|
||||
// Expected error
|
||||
}
|
||||
})
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith('错误', '充值失败')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user