mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 06:52:13 +08:00
系统性地修复、补充和强化项目的自动化测试能力: 1. 测试基础设施修复 - 修复 stubConcurrencyCache 缺失方法和构造函数参数不匹配 - 创建 testutil 共享包(stubs.go, fixtures.go, httptest.go) - 为所有 Stub 添加编译期接口断言 2. 中间件测试补充 - 新增 JWT 认证中间件测试(有效/过期/篡改/缺失 Token) - 补充 rate_limiter 和 recovery 中间件测试场景 3. 网关核心路径测试 - 新增账户选择、等待队列、流式响应、并发控制、计费、Claude Code 检测测试 - 覆盖负载均衡、粘性会话、SSE 转发、槽位管理等关键逻辑 4. 前端测试体系(11个新测试文件,163个测试用例) - Pinia stores: auth, app, subscriptions - API client: 请求拦截器、响应拦截器、401 刷新 - Router guards: 认证重定向、管理员权限、简易模式限制 - Composables: useForm, useTableLoader, useClipboard - Components: LoginForm, ApiKeyCreate, Dashboard 5. CI/CD 流水线重构 - 重构 backend-ci.yml 为统一的 ci.yml - 前后端 4 个并行 Job + Postgres/Redis services - Race 检测、覆盖率收集与门禁、Docker 构建验证 6. E2E 自动化测试 - e2e-test.sh 自动化脚本(Docker 启动→健康检查→测试→清理) - 用户注册→登录→API Key→网关调用完整链路测试 - Mock 模式和 API Key 脱敏支持 7. 修复预存问题 - tlsfingerprint dialer_test.go 缺失 build tag 导致集成测试编译冲突 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
4.8 KiB
TypeScript
174 lines
4.8 KiB
TypeScript
/**
|
|
* Dashboard 数据加载逻辑测试
|
|
* 通过封装组件测试仪表板核心数据加载流程
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { mount, flushPromises } from '@vue/test-utils'
|
|
import { setActivePinia, createPinia } from 'pinia'
|
|
import { defineComponent, ref, onMounted, nextTick } from 'vue'
|
|
|
|
// Mock API
|
|
const mockGetDashboardStats = vi.fn()
|
|
const mockRefreshUser = vi.fn()
|
|
|
|
vi.mock('@/api', () => ({
|
|
authAPI: {
|
|
getCurrentUser: vi.fn().mockResolvedValue({
|
|
data: { id: 1, username: 'test', email: 'test@example.com', role: 'user', balance: 100, concurrency: 5, status: 'active', allowed_groups: null, created_at: '', updated_at: '' },
|
|
}),
|
|
logout: vi.fn(),
|
|
refreshToken: vi.fn(),
|
|
},
|
|
isTotp2FARequired: () => false,
|
|
}))
|
|
|
|
vi.mock('@/api/usage', () => ({
|
|
usageAPI: {
|
|
getDashboardStats: (...args: any[]) => mockGetDashboardStats(...args),
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/api/admin/system', () => ({
|
|
checkUpdates: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/api/auth', () => ({
|
|
getPublicSettings: vi.fn().mockResolvedValue({}),
|
|
}))
|
|
|
|
interface DashboardStats {
|
|
balance: number
|
|
api_key_count: number
|
|
active_api_key_count: number
|
|
today_requests: number
|
|
today_cost: number
|
|
today_tokens: number
|
|
total_tokens: number
|
|
}
|
|
|
|
/**
|
|
* 简化的 Dashboard 测试组件
|
|
*/
|
|
const DashboardTestComponent = defineComponent({
|
|
setup() {
|
|
const stats = ref<DashboardStats | null>(null)
|
|
const loading = ref(false)
|
|
const error = ref('')
|
|
|
|
const loadStats = async () => {
|
|
loading.value = true
|
|
error.value = ''
|
|
try {
|
|
stats.value = await mockGetDashboardStats()
|
|
} catch (e: any) {
|
|
error.value = e.message || '加载失败'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(loadStats)
|
|
|
|
return { stats, loading, error, loadStats }
|
|
},
|
|
template: `
|
|
<div>
|
|
<div v-if="loading" class="loading">加载中...</div>
|
|
<div v-if="error" class="error">{{ error }}</div>
|
|
<div v-if="stats" class="stats">
|
|
<span class="balance">{{ stats.balance }}</span>
|
|
<span class="api-keys">{{ stats.api_key_count }}</span>
|
|
<span class="today-requests">{{ stats.today_requests }}</span>
|
|
<span class="today-cost">{{ stats.today_cost }}</span>
|
|
</div>
|
|
<button class="refresh" @click="loadStats">刷新</button>
|
|
</div>
|
|
`,
|
|
})
|
|
|
|
describe('Dashboard 数据加载', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
const fakeStats: DashboardStats = {
|
|
balance: 100.5,
|
|
api_key_count: 3,
|
|
active_api_key_count: 2,
|
|
today_requests: 150,
|
|
today_cost: 2.5,
|
|
today_tokens: 50000,
|
|
total_tokens: 1000000,
|
|
}
|
|
|
|
it('挂载后自动加载数据', async () => {
|
|
mockGetDashboardStats.mockResolvedValue(fakeStats)
|
|
|
|
const wrapper = mount(DashboardTestComponent)
|
|
await flushPromises()
|
|
|
|
expect(mockGetDashboardStats).toHaveBeenCalledTimes(1)
|
|
expect(wrapper.find('.balance').text()).toBe('100.5')
|
|
expect(wrapper.find('.api-keys').text()).toBe('3')
|
|
expect(wrapper.find('.today-requests').text()).toBe('150')
|
|
expect(wrapper.find('.today-cost').text()).toBe('2.5')
|
|
})
|
|
|
|
it('加载中显示 loading 状态', async () => {
|
|
let resolveStats: (v: any) => void
|
|
mockGetDashboardStats.mockImplementation(
|
|
() => new Promise((resolve) => { resolveStats = resolve })
|
|
)
|
|
|
|
const wrapper = mount(DashboardTestComponent)
|
|
await nextTick()
|
|
|
|
expect(wrapper.find('.loading').exists()).toBe(true)
|
|
|
|
resolveStats!(fakeStats)
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.loading').exists()).toBe(false)
|
|
expect(wrapper.find('.stats').exists()).toBe(true)
|
|
})
|
|
|
|
it('加载失败时显示错误信息', async () => {
|
|
mockGetDashboardStats.mockRejectedValue(new Error('Network error'))
|
|
|
|
const wrapper = mount(DashboardTestComponent)
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.error').text()).toBe('Network error')
|
|
expect(wrapper.find('.stats').exists()).toBe(false)
|
|
})
|
|
|
|
it('点击刷新按钮重新加载数据', async () => {
|
|
mockGetDashboardStats.mockResolvedValue(fakeStats)
|
|
|
|
const wrapper = mount(DashboardTestComponent)
|
|
await flushPromises()
|
|
|
|
expect(mockGetDashboardStats).toHaveBeenCalledTimes(1)
|
|
|
|
// 更新数据
|
|
const updatedStats = { ...fakeStats, today_requests: 200 }
|
|
mockGetDashboardStats.mockResolvedValue(updatedStats)
|
|
|
|
await wrapper.find('.refresh').trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(mockGetDashboardStats).toHaveBeenCalledTimes(2)
|
|
expect(wrapper.find('.today-requests').text()).toBe('200')
|
|
})
|
|
|
|
it('数据为空时不显示统计信息', async () => {
|
|
mockGetDashboardStats.mockResolvedValue(null)
|
|
|
|
const wrapper = mount(DashboardTestComponent)
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.stats').exists()).toBe(false)
|
|
})
|
|
})
|