test: 完善自动化测试体系(7个模块,73个任务)

系统性地修复、补充和强化项目的自动化测试能力:

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>
This commit is contained in:
yangjianbo
2026-02-08 12:05:39 +08:00
parent 53e1c8b268
commit bb5a5dd65e
41 changed files with 5101 additions and 182 deletions

View File

@@ -0,0 +1,324 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createRouter, createMemoryHistory } from 'vue-router'
import { setActivePinia, createPinia } from 'pinia'
import { defineComponent, h } from 'vue'
// Mock 导航加载状态
vi.mock('@/composables/useNavigationLoading', () => {
const mockStart = vi.fn()
const mockEnd = vi.fn()
return {
useNavigationLoadingState: () => ({
startNavigation: mockStart,
endNavigation: mockEnd,
isLoading: { value: false },
}),
useNavigationLoading: () => ({
startNavigation: mockStart,
endNavigation: mockEnd,
isLoading: { value: false },
}),
}
})
// Mock 路由预加载
vi.mock('@/composables/useRoutePrefetch', () => ({
useRoutePrefetch: () => ({
triggerPrefetch: vi.fn(),
cancelPendingPrefetch: vi.fn(),
resetPrefetchState: vi.fn(),
}),
}))
// Mock API 相关模块
vi.mock('@/api', () => ({
authAPI: {
getCurrentUser: vi.fn().mockResolvedValue({ data: {} }),
logout: vi.fn(),
},
isTotp2FARequired: () => false,
}))
vi.mock('@/api/admin/system', () => ({
checkUpdates: vi.fn(),
}))
vi.mock('@/api/auth', () => ({
getPublicSettings: vi.fn(),
}))
const DummyComponent = defineComponent({
render() {
return h('div', 'dummy')
},
})
/**
* 创建带守卫逻辑的测试路由
* 模拟 router/index.ts 中的 beforeEach 守卫逻辑
*/
function createTestRouter() {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/login', component: DummyComponent, meta: { requiresAuth: false, title: 'Login' } },
{
path: '/register',
component: DummyComponent,
meta: { requiresAuth: false, title: 'Register' },
},
{ path: '/home', component: DummyComponent, meta: { requiresAuth: false, title: 'Home' } },
{ path: '/dashboard', component: DummyComponent, meta: { title: 'Dashboard' } },
{ path: '/keys', component: DummyComponent, meta: { title: 'API Keys' } },
{ path: '/subscriptions', component: DummyComponent, meta: { title: 'Subscriptions' } },
{ path: '/redeem', component: DummyComponent, meta: { title: 'Redeem' } },
{
path: '/admin/dashboard',
component: DummyComponent,
meta: { requiresAdmin: true, title: 'Admin Dashboard' },
},
{
path: '/admin/users',
component: DummyComponent,
meta: { requiresAdmin: true, title: 'Admin Users' },
},
{
path: '/admin/groups',
component: DummyComponent,
meta: { requiresAdmin: true, title: 'Admin Groups' },
},
{
path: '/admin/subscriptions',
component: DummyComponent,
meta: { requiresAdmin: true, title: 'Admin Subscriptions' },
},
{
path: '/admin/redeem',
component: DummyComponent,
meta: { requiresAdmin: true, title: 'Admin Redeem' },
},
],
})
return router
}
// 用于测试的 auth 状态
interface MockAuthState {
isAuthenticated: boolean
isAdmin: boolean
isSimpleMode: boolean
}
/**
* 将 router/index.ts 中 beforeEach 守卫的核心逻辑提取为可测试的函数
*/
function simulateGuard(
toPath: string,
toMeta: Record<string, any>,
authState: MockAuthState
): string | null {
const requiresAuth = toMeta.requiresAuth !== false
const requiresAdmin = toMeta.requiresAdmin === true
// 不需要认证的路由
if (!requiresAuth) {
if (
authState.isAuthenticated &&
(toPath === '/login' || toPath === '/register')
) {
return authState.isAdmin ? '/admin/dashboard' : '/dashboard'
}
return null // 允许通过
}
// 需要认证但未登录
if (!authState.isAuthenticated) {
return '/login'
}
// 需要管理员但不是管理员
if (requiresAdmin && !authState.isAdmin) {
return '/dashboard'
}
// 简易模式限制
if (authState.isSimpleMode) {
const restrictedPaths = [
'/admin/groups',
'/admin/subscriptions',
'/admin/redeem',
'/subscriptions',
'/redeem',
]
if (restrictedPaths.some((path) => toPath.startsWith(path))) {
return authState.isAdmin ? '/admin/dashboard' : '/dashboard'
}
}
return null // 允许通过
}
describe('路由守卫逻辑', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
// --- 未认证用户 ---
describe('未认证用户', () => {
const authState: MockAuthState = {
isAuthenticated: false,
isAdmin: false,
isSimpleMode: false,
}
it('访问需要认证的页面重定向到 /login', () => {
const redirect = simulateGuard('/dashboard', {}, authState)
expect(redirect).toBe('/login')
})
it('访问管理页面重定向到 /login', () => {
const redirect = simulateGuard('/admin/dashboard', { requiresAdmin: true }, authState)
expect(redirect).toBe('/login')
})
it('访问公开页面允许通过', () => {
const redirect = simulateGuard('/login', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
})
it('访问 /home 公开页面允许通过', () => {
const redirect = simulateGuard('/home', { requiresAuth: false }, authState)
expect(redirect).toBeNull()
})
})
// --- 已认证普通用户 ---
describe('已认证普通用户', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: false,
isSimpleMode: false,
}
it('访问 /login 重定向到 /dashboard', () => {
const redirect = simulateGuard('/login', { requiresAuth: false }, authState)
expect(redirect).toBe('/dashboard')
})
it('访问 /register 重定向到 /dashboard', () => {
const redirect = simulateGuard('/register', { requiresAuth: false }, authState)
expect(redirect).toBe('/dashboard')
})
it('访问 /dashboard 允许通过', () => {
const redirect = simulateGuard('/dashboard', {}, authState)
expect(redirect).toBeNull()
})
it('访问管理页面被拒绝,重定向到 /dashboard', () => {
const redirect = simulateGuard('/admin/dashboard', { requiresAdmin: true }, authState)
expect(redirect).toBe('/dashboard')
})
it('访问 /admin/users 被拒绝', () => {
const redirect = simulateGuard('/admin/users', { requiresAdmin: true }, authState)
expect(redirect).toBe('/dashboard')
})
})
// --- 已认证管理员 ---
describe('已认证管理员', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: true,
isSimpleMode: false,
}
it('访问 /login 重定向到 /admin/dashboard', () => {
const redirect = simulateGuard('/login', { requiresAuth: false }, authState)
expect(redirect).toBe('/admin/dashboard')
})
it('访问管理页面允许通过', () => {
const redirect = simulateGuard('/admin/dashboard', { requiresAdmin: true }, authState)
expect(redirect).toBeNull()
})
it('访问用户页面允许通过', () => {
const redirect = simulateGuard('/dashboard', {}, authState)
expect(redirect).toBeNull()
})
})
// --- 简易模式 ---
describe('简易模式受限路由', () => {
it('普通用户简易模式访问 /subscriptions 重定向到 /dashboard', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: false,
isSimpleMode: true,
}
const redirect = simulateGuard('/subscriptions', {}, authState)
expect(redirect).toBe('/dashboard')
})
it('普通用户简易模式访问 /redeem 重定向到 /dashboard', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: false,
isSimpleMode: true,
}
const redirect = simulateGuard('/redeem', {}, authState)
expect(redirect).toBe('/dashboard')
})
it('管理员简易模式访问 /admin/groups 重定向到 /admin/dashboard', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: true,
isSimpleMode: true,
}
const redirect = simulateGuard('/admin/groups', { requiresAdmin: true }, authState)
expect(redirect).toBe('/admin/dashboard')
})
it('管理员简易模式访问 /admin/subscriptions 重定向', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: true,
isSimpleMode: true,
}
const redirect = simulateGuard(
'/admin/subscriptions',
{ requiresAdmin: true },
authState
)
expect(redirect).toBe('/admin/dashboard')
})
it('简易模式下非受限页面正常访问', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: false,
isSimpleMode: true,
}
const redirect = simulateGuard('/dashboard', {}, authState)
expect(redirect).toBeNull()
})
it('简易模式下 /keys 正常访问', () => {
const authState: MockAuthState = {
isAuthenticated: true,
isAdmin: false,
isSimpleMode: true,
}
const redirect = simulateGuard('/keys', {}, authState)
expect(redirect).toBeNull()
})
})
})