test: add tests for prommetrics, common routes, and Sora admin page
- 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:
260
frontend/src/views/admin/__tests__/SoraAdminView.spec.ts
Normal file
260
frontend/src/views/admin/__tests__/SoraAdminView.spec.ts
Normal 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')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user