test: cover ai platform frontend workflows
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|---|---|---|
|
||||
| 后端自动化 | 已通过 | 71 项 pytest;Ruff、迁移漂移与 Django system check 通过 |
|
||||
| 全新数据库迁移 | 已通过 | 测试配置下从空库应用全部迁移 |
|
||||
| 前端自动化 | 已通过 | 11 项 Vitest;ESLint、类型检查与生产构建通过 |
|
||||
| 前端自动化 | 已通过 | 15 项 Vitest;覆盖异步轮询、失败态、发布、运行详情和取消;ESLint、类型检查与生产构建通过 |
|
||||
| 真实模型三步运行 | 待执行 | 凭证仅允许管理员在本地录入,不进入仓库 |
|
||||
|
||||
真实模型三步运行通过前,本演示版只能描述为“实现完成、真实渠道验收待完成”。
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
## 当前质量基线
|
||||
|
||||
- 后端测试:71 项通过;Ruff、迁移检查、Django system check 和全新数据库迁移通过。
|
||||
- 前端测试:11 项通过;ESLint、TypeScript 类型检查和生产构建通过。
|
||||
- 前端测试:15 项通过;ESLint、TypeScript 类型检查和生产构建通过。
|
||||
- 前端生产入口包约 626 KB,存在 Vite 体积告警;后续按路由懒加载拆包。
|
||||
- 真实模型渠道的三步端到端脚本尚未执行,因为凭证必须由管理员在本地提供;在该步骤通过前,不声明演示里程碑验收完成。
|
||||
|
||||
|
||||
146
frontend/src/features/ai-platform/ai-platform-pages.test.tsx
Normal file
146
frontend/src/features/ai-platform/ai-platform-pages.test.tsx
Normal 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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user