test: add tests for prommetrics, common routes, and Sora admin page
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled

- Add prommetrics package tests (12 tests covering all metric functions)
- Add routes/common_test.go with health check, readiness, liveness tests
- Add SoraAdminView.spec.ts with 11 component tests
This commit is contained in:
User
2026-04-16 13:04:03 +08:00
parent c4007afe6b
commit c9992af876
3 changed files with 605 additions and 0 deletions

View File

@@ -0,0 +1,260 @@
import { describe, expect, it, vi, beforeEach } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import { defineComponent } from 'vue'
import SoraAdminView from '../SoraAdminView.vue'
import type { SoraSystemStats, SoraUserStats, SoraGenerationAdmin } from '@/api/admin/sora'
// 使用 vi.hoisted 确保 mock 函数在 mock 工厂函数执行前定义
const {
mockGetSystemStats,
mockListUserStats,
mockListGenerations,
mockClearUserStorage
} = vi.hoisted(() => ({
mockGetSystemStats: vi.fn(),
mockListUserStats: vi.fn(),
mockListGenerations: vi.fn(),
mockClearUserStorage: vi.fn()
}))
vi.mock('@/api/admin/sora', () => ({
default: {
getSystemStats: mockGetSystemStats,
listUserStats: mockListUserStats,
listGenerations: mockListGenerations,
clearUserStorage: mockClearUserStorage
}
}))
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
useI18n: () => ({
t: (key: string) => key
})
}
})
// Stub child components
const AppLayoutStub = defineComponent({
name: 'AppLayout',
template: '<div class="app-layout"><slot /></div>'
})
const LoadingSpinnerStub = defineComponent({
name: 'LoadingSpinner',
template: '<div class="loading-spinner" />'
})
const IconStub = defineComponent({
name: 'Icon',
props: ['name', 'size'],
template: '<span class="icon" />'
})
const BaseButtonStub = defineComponent({
name: 'BaseButton',
props: ['variant', 'size'],
emits: ['click'],
template: '<button @click="$emit(\'click\')"><slot /></button>'
})
function createMockSystemStats(overrides: Partial<SoraSystemStats> = {}): SoraSystemStats {
return {
total_users: 10,
total_generations: 100,
total_storage_bytes: 5 * 1024 * 1024 * 1024,
active_generations: 3,
by_status: { completed: 80, failed: 15, pending: 5 },
by_model: { sora2: 60, sora1: 40 },
...overrides
}
}
function createMockUserStats(): SoraUserStats[] {
return [
{
user_id: 1,
username: 'user1',
email: 'user1@example.com',
quota_bytes: 10 * 1024 * 1024 * 1024,
used_bytes: 2 * 1024 * 1024 * 1024,
available_bytes: 8 * 1024 * 1024 * 1024,
quota_source: 'user',
generations_count: 20,
active_count: 1,
total_file_size_bytes: 1 * 1024 * 1024 * 1024
}
]
}
function createMockGenerations(): SoraGenerationAdmin[] {
return [
{
id: 1,
user_id: 1,
username: 'user1',
email: 'user1@example.com',
model: 'sora2',
prompt: 'A beautiful sunset',
media_type: 'video',
status: 'completed',
storage_type: 's3',
media_url: 'https://example.com/video.mp4',
file_size_bytes: 10 * 1024 * 1024,
error_message: '',
created_at: '2024-01-01T10:00:00Z',
completed_at: '2024-01-01T10:05:00Z'
}
]
}
describe('SoraAdminView', () => {
beforeEach(() => {
vi.clearAllMocks()
mockGetSystemStats.mockResolvedValue(createMockSystemStats())
mockListUserStats.mockResolvedValue({
items: createMockUserStats(),
total: 1,
page: 1,
page_size: 20,
pages: 1
})
mockListGenerations.mockResolvedValue({
items: createMockGenerations(),
total: 1,
page: 1,
page_size: 20,
pages: 1
})
})
function mountComponent() {
return mount(SoraAdminView, {
global: {
stubs: {
AppLayout: AppLayoutStub,
LoadingSpinner: LoadingSpinnerStub,
Icon: IconStub
}
}
})
}
describe('初始加载', () => {
it('加载时显示 loading spinner', () => {
const wrapper = mountComponent()
expect(wrapper.find('.loading-spinner').exists()).toBe(true)
})
it('加载完成后显示页面内容', async () => {
const wrapper = mountComponent()
await flushPromises()
expect(wrapper.find('.loading-spinner').exists()).toBe(false)
expect(wrapper.find('.app-layout').exists()).toBe(true)
})
it('加载完成后调用所有 API', async () => {
mountComponent()
await flushPromises()
expect(mockGetSystemStats).toHaveBeenCalled()
expect(mockListUserStats).toHaveBeenCalled()
expect(mockListGenerations).toHaveBeenCalled()
})
})
describe('概览标签页', () => {
it('默认显示概览标签页', async () => {
const wrapper = mountComponent()
await flushPromises()
// 概览标签页应显示统计卡片
const cards = wrapper.findAll('.card')
expect(cards.length).toBeGreaterThan(0)
})
it('显示系统统计数据', async () => {
const wrapper = mountComponent()
await flushPromises()
expect(wrapper.text()).toContain('10') // total_users
expect(wrapper.text()).toContain('100') // total_generations
expect(wrapper.text()).toContain('3') // active_generations
})
it('显示按状态分布', async () => {
const wrapper = mountComponent()
await flushPromises()
expect(wrapper.text()).toContain('completed')
expect(wrapper.text()).toContain('80')
})
it('显示按模型分布', async () => {
const wrapper = mountComponent()
await flushPromises()
expect(wrapper.text()).toContain('sora2')
expect(wrapper.text()).toContain('60')
})
})
describe('用户统计标签页', () => {
it('点击用户统计标签切换到用户列表', async () => {
const wrapper = mountComponent()
await flushPromises()
const tabs = wrapper.findAll('button')
const userTab = tabs.find(b => b.text().includes('admin.sora.userStats'))
await userTab?.trigger('click')
await flushPromises()
expect(wrapper.find('table').exists()).toBe(true)
expect(wrapper.text()).toContain('user1')
expect(wrapper.text()).toContain('user1@example.com')
})
})
describe('生成记录标签页', () => {
it('点击生成记录标签切换到记录列表', async () => {
const wrapper = mountComponent()
await flushPromises()
const tabs = wrapper.findAll('button')
const genTab = tabs.find(b => b.text().includes('admin.sora.generations'))
await genTab?.trigger('click')
await flushPromises()
expect(wrapper.find('table').exists()).toBe(true)
expect(wrapper.text()).toContain('user1')
expect(wrapper.text()).toContain('sora2')
expect(wrapper.text()).toContain('completed')
})
})
describe('API 错误处理', () => {
it('API 错误时不会崩溃', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
mockGetSystemStats.mockRejectedValue(new Error('Network error'))
const wrapper = mountComponent()
await flushPromises()
expect(wrapper.exists()).toBe(true)
consoleSpy.mockRestore()
})
})
describe('格式化函数', () => {
it('正确格式化字节数', async () => {
const wrapper = mountComponent()
await flushPromises()
// 5 GB 应该显示为 "5 GB"
expect(wrapper.text()).toContain('5 GB')
})
})
})