XAPI / frontend /src /stores /__tests__ /subscriptions.spec.ts
cjovs's picture
Deploy Sub2API HF Space with embedded Postgres/Redis backup runtime
8059bf0 verified
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useSubscriptionStore } from '@/stores/subscriptions'
// Mock subscriptions API
const mockGetActiveSubscriptions = vi.fn()
vi.mock('@/api/subscriptions', () => ({
default: {
getActiveSubscriptions: (...args: any[]) => mockGetActiveSubscriptions(...args),
},
}))
const fakeSubscriptions = [
{
id: 1,
user_id: 1,
group_id: 1,
status: 'active' as const,
daily_usage_usd: 5,
weekly_usage_usd: 20,
monthly_usage_usd: 50,
daily_window_start: null,
weekly_window_start: null,
monthly_window_start: null,
created_at: '2024-01-01',
updated_at: '2024-01-01',
expires_at: '2025-01-01',
},
{
id: 2,
user_id: 1,
group_id: 2,
status: 'active' as const,
daily_usage_usd: 10,
weekly_usage_usd: 40,
monthly_usage_usd: 100,
daily_window_start: null,
weekly_window_start: null,
monthly_window_start: null,
created_at: '2024-02-01',
updated_at: '2024-02-01',
expires_at: '2025-02-01',
},
]
describe('useSubscriptionStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.useFakeTimers()
vi.clearAllMocks()
})
afterEach(() => {
vi.useRealTimers()
})
// --- fetchActiveSubscriptions ---
describe('fetchActiveSubscriptions', () => {
it('成功获取活跃订阅', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
const result = await store.fetchActiveSubscriptions()
expect(result).toEqual(fakeSubscriptions)
expect(store.activeSubscriptions).toEqual(fakeSubscriptions)
expect(store.loading).toBe(false)
})
it('缓存有效时返回缓存数据', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
// 第一次请求
await store.fetchActiveSubscriptions()
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(1)
// 第二次请求(60秒内)- 应返回缓存
const result = await store.fetchActiveSubscriptions()
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(1) // 没有新请求
expect(result).toEqual(fakeSubscriptions)
})
it('缓存过期后重新请求', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
await store.fetchActiveSubscriptions()
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(1)
// 推进 61 秒让缓存过期
vi.advanceTimersByTime(61_000)
const updatedSubs = [fakeSubscriptions[0]]
mockGetActiveSubscriptions.mockResolvedValue(updatedSubs)
const result = await store.fetchActiveSubscriptions()
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(2)
expect(result).toEqual(updatedSubs)
})
it('force=true 强制重新请求', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
await store.fetchActiveSubscriptions()
const updatedSubs = [fakeSubscriptions[0]]
mockGetActiveSubscriptions.mockResolvedValue(updatedSubs)
const result = await store.fetchActiveSubscriptions(true)
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(2)
expect(result).toEqual(updatedSubs)
})
it('并发请求共享同一个 Promise(去重)', async () => {
let resolvePromise: (v: any) => void
mockGetActiveSubscriptions.mockImplementation(
() => new Promise((resolve) => { resolvePromise = resolve })
)
const store = useSubscriptionStore()
// 并发发起两个请求
const p1 = store.fetchActiveSubscriptions()
const p2 = store.fetchActiveSubscriptions()
// 只调用了一次 API
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(1)
// 解决 Promise
resolvePromise!(fakeSubscriptions)
const [r1, r2] = await Promise.all([p1, p2])
expect(r1).toEqual(fakeSubscriptions)
expect(r2).toEqual(fakeSubscriptions)
})
it('API 错误时抛出异常', async () => {
mockGetActiveSubscriptions.mockRejectedValue(new Error('Network error'))
const store = useSubscriptionStore()
await expect(store.fetchActiveSubscriptions()).rejects.toThrow('Network error')
})
})
// --- hasActiveSubscriptions ---
describe('hasActiveSubscriptions', () => {
it('有订阅时返回 true', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
await store.fetchActiveSubscriptions()
expect(store.hasActiveSubscriptions).toBe(true)
})
it('无订阅时返回 false', () => {
const store = useSubscriptionStore()
expect(store.hasActiveSubscriptions).toBe(false)
})
it('清除后返回 false', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
await store.fetchActiveSubscriptions()
expect(store.hasActiveSubscriptions).toBe(true)
store.clear()
expect(store.hasActiveSubscriptions).toBe(false)
})
})
// --- invalidateCache ---
describe('invalidateCache', () => {
it('失效缓存后下次请求重新获取数据', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
await store.fetchActiveSubscriptions()
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(1)
store.invalidateCache()
await store.fetchActiveSubscriptions()
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(2)
})
})
// --- clear ---
describe('clear', () => {
it('清除所有订阅数据', async () => {
mockGetActiveSubscriptions.mockResolvedValue(fakeSubscriptions)
const store = useSubscriptionStore()
await store.fetchActiveSubscriptions()
expect(store.activeSubscriptions).toHaveLength(2)
store.clear()
expect(store.activeSubscriptions).toHaveLength(0)
expect(store.hasActiveSubscriptions).toBe(false)
})
})
// --- polling ---
describe('startPolling / stopPolling', () => {
it('startPolling 不会创建重复 interval', () => {
const store = useSubscriptionStore()
mockGetActiveSubscriptions.mockResolvedValue([])
store.startPolling()
store.startPolling() // 重复调用
// 推进5分钟只触发一次
vi.advanceTimersByTime(5 * 60 * 1000)
expect(mockGetActiveSubscriptions).toHaveBeenCalledTimes(1)
store.stopPolling()
})
it('stopPolling 停止定期刷新', () => {
const store = useSubscriptionStore()
mockGetActiveSubscriptions.mockResolvedValue([])
store.startPolling()
store.stopPolling()
vi.advanceTimersByTime(10 * 60 * 1000)
expect(mockGetActiveSubscriptions).not.toHaveBeenCalled()
})
})
})