test: cover ai platform frontend workflows

This commit is contained in:
hailin
2026-07-14 14:15:30 +08:00
parent f8b6705bec
commit 74427497c3
3 changed files with 148 additions and 2 deletions

View File

@@ -8,7 +8,7 @@
|---|---|---| |---|---|---|
| 后端自动化 | 已通过 | 71 项 pytest;Ruff、迁移漂移与 Django system check 通过 | | 后端自动化 | 已通过 | 71 项 pytest;Ruff、迁移漂移与 Django system check 通过 |
| 全新数据库迁移 | 已通过 | 测试配置下从空库应用全部迁移 | | 全新数据库迁移 | 已通过 | 测试配置下从空库应用全部迁移 |
| 前端自动化 | 已通过 | 11 项 Vitest;ESLint、类型检查与生产构建通过 | | 前端自动化 | 已通过 | 15 项 Vitest;覆盖异步轮询、失败态、发布、运行详情和取消;ESLint、类型检查与生产构建通过 |
| 真实模型三步运行 | 待执行 | 凭证仅允许管理员在本地录入,不进入仓库 | | 真实模型三步运行 | 待执行 | 凭证仅允许管理员在本地录入,不进入仓库 |
真实模型三步运行通过前,本演示版只能描述为“实现完成、真实渠道验收待完成”。 真实模型三步运行通过前,本演示版只能描述为“实现完成、真实渠道验收待完成”。

View File

@@ -34,7 +34,7 @@
## 当前质量基线 ## 当前质量基线
- 后端测试:71 项通过;Ruff、迁移检查、Django system check 和全新数据库迁移通过。 - 后端测试:71 项通过;Ruff、迁移检查、Django system check 和全新数据库迁移通过。
- 前端测试:11 项通过;ESLint、TypeScript 类型检查和生产构建通过。 - 前端测试:15 项通过;ESLint、TypeScript 类型检查和生产构建通过。
- 前端生产入口包约 626 KB,存在 Vite 体积告警;后续按路由懒加载拆包。 - 前端生产入口包约 626 KB,存在 Vite 体积告警;后续按路由懒加载拆包。
- 真实模型渠道的三步端到端脚本尚未执行,因为凭证必须由管理员在本地提供;在该步骤通过前,不声明演示里程碑验收完成。 - 真实模型渠道的三步端到端脚本尚未执行,因为凭证必须由管理员在本地提供;在该步骤通过前,不声明演示里程碑验收完成。

View File

@@ -0,0 +1,146 @@
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { AgentRunDetailPage } from '@/features/agents/AgentRunDetailPage'
import { ModelProviderPage } from '@/features/modelhub/ModelProviderPage'
import { SkillDetailPage } from '@/features/skills/SkillDetailPage'
const mocks = vi.hoisted(() => ({
listProviders: vi.fn(),
listDeployments: vi.fn(),
testDeployment: vi.fn(),
getModelTest: vi.fn(),
getSkill: vi.fn(),
publishSkill: vi.fn(),
getAgentRun: vi.fn(),
cancelAgentRun: vi.fn()
}))
vi.mock('@/features/modelhub/api', () => ({
listProviders: mocks.listProviders,
listDeployments: mocks.listDeployments,
testDeployment: mocks.testDeployment,
getModelTest: mocks.getModelTest,
createProvider: vi.fn(),
updateProvider: vi.fn(),
createDeployment: vi.fn(),
updateDeployment: vi.fn()
}))
vi.mock('@/features/skills/api', () => ({
getSkill: mocks.getSkill,
publishSkill: mocks.publishSkill,
ensureSkillDraft: vi.fn(),
updateSkill: vi.fn(),
updateSkillDraft: vi.fn(),
rollbackSkill: vi.fn()
}))
vi.mock('@/features/agents/api', () => ({
getAgentRun: mocks.getAgentRun,
cancelAgentRun: mocks.cancelAgentRun
}))
const provider = {
id: 1,
name: '企业渠道',
base_url: 'https://models.example.com/v1',
enabled: true,
default_timeout_seconds: 60,
credential_set: true,
credential_masked: '****1234',
created_at: '2026-07-14T00:00:00Z',
updated_at: '2026-07-14T00:00:00Z'
}
const deployment = {
id: 2,
provider_id: 1,
provider_name: '企业渠道',
display_name: '支持模型',
model_name: 'support-model',
capability: 'text',
default_temperature: 0.2,
max_output_tokens: 2048,
enabled: true
}
const queuedTest = {
public_id: 'test-uuid', deployment_id: 2, status: 'queued', model_snapshot: {}, output_text: '', input_tokens: null, output_tokens: null, duration_ms: 0, retry_count: 0, error_code: '', error_message: '', created_at: '2026-07-14T00:00:00Z', started_at: null, finished_at: null
}
const run = {
public_id: '11111111-1111-1111-1111-111111111111', agent_id: 1, agent_name: '技术支持分析 Agent', agent_version: 1, status: 'running', sandbox: true, current_step: 'structure', total_steps: 1, input_tokens: 3, output_tokens: 2, duration_ms: 120, error_code: '', error_message: '', created_at: '2026-07-14T00:00:00Z', started_at: '2026-07-14T00:00:00Z', finished_at: null, input_payload: { problem: '无法登录' }, final_output: null, config_snapshot: { agent: { version: 1 } }, cancellation_requested_at: null,
steps: [{ id: 10, code: 'structure', name: '问题结构化', order: 1, status: 'running', input_payload: { problem: '无法登录' }, output_payload: null, input_tokens: 3, output_tokens: 2, duration_ms: 120, error_code: '', error_message: '', attempts: [] }]
}
afterEach(() => {
cleanup()
vi.clearAllMocks()
})
beforeEach(() => {
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn()
}))
})
describe('AI 平台真实页面', () => {
it('展示脱敏渠道并轮询异步模型测试', async () => {
mocks.listProviders.mockResolvedValue([provider])
mocks.listDeployments.mockResolvedValue([deployment])
mocks.testDeployment.mockResolvedValue(queuedTest)
mocks.getModelTest.mockResolvedValue({ ...queuedTest, status: 'succeeded', output_text: '模型连接正常', input_tokens: 8, output_tokens: 4, duration_ms: 320, finished_at: '2026-07-14T00:00:01Z' })
render(<ModelProviderPage />)
expect(await screen.findByText('****1234')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '连通测试' }))
fireEvent.click(screen.getByRole('button', { name: '开始测试' }))
await waitFor(() => expect(mocks.getModelTest).toHaveBeenCalledWith('test-uuid'), { timeout: 2500 })
expect(await screen.findByText('模型连接正常')).toBeInTheDocument()
})
it('展示列表失败态', async () => {
mocks.listProviders.mockRejectedValue(new Error('后端不可用'))
mocks.listDeployments.mockResolvedValue([])
render(<ModelProviderPage />)
expect(await screen.findByText('无法加载模型配置')).toBeInTheDocument()
expect(screen.getByText('后端不可用')).toBeInTheDocument()
})
it('发布 Skill 草稿版本', async () => {
mocks.getSkill.mockResolvedValue({
id: 1, code: 'structure', name: '问题结构化', description: '', active_version: null, draft_version: 1, updated_at: '2026-07-14T00:00:00Z',
versions: [{ id: 11, version: 1, status: 'draft', prompt_template: '{{ input.problem }}', input_schema: { type: 'object' }, output_schema: { type: 'object' }, example_input: {}, created_at: '2026-07-14T00:00:00Z', updated_at: '2026-07-14T00:00:00Z', published_at: null }]
})
mocks.publishSkill.mockResolvedValue({ id: 11, version: 1, status: 'published' })
render(<SkillDetailPage skillId={1} />)
fireEvent.click(await screen.findByRole('button', { name: '发布 v1' }))
await waitFor(() => expect(mocks.publishSkill).toHaveBeenCalledWith(1))
})
it('展示运行执行脊柱并提交尽力取消', async () => {
mocks.getAgentRun.mockResolvedValue(run)
mocks.cancelAgentRun.mockResolvedValue({ ...run, status: 'cancelled', cancellation_requested_at: '2026-07-14T00:00:01Z' })
render(<AgentRunDetailPage publicId={run.public_id} />)
expect(await screen.findByText('执行脊柱')).toBeInTheDocument()
expect(screen.getByText('问题结构化')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '尽力取消' }))
await waitFor(() => expect(mocks.cancelAgentRun).toHaveBeenCalledWith(run.public_id))
expect(await screen.findByText('已取消')).toBeInTheDocument()
})
})