feature: 增加功能 导出详细报告

This commit is contained in:
baozaotumao2025
2026-07-19 00:29:06 +08:00
parent 789d4fc4e9
commit 12d8293250
12 changed files with 92 additions and 15 deletions
+34 -1
View File
@@ -16,6 +16,7 @@ const mocks = vi.hoisted(() => ({
retryErrors: vi.fn(),
retryResult: vi.fn(),
results: vi.fn(),
exportRun: vi.fn(),
reports: vi.fn(),
report: vi.fn(),
}))
@@ -24,7 +25,7 @@ vi.mock('../../src/services', () => ({ services: {
auth: { me: mocks.me, login: vi.fn(), logout: vi.fn(), changePassword: vi.fn(), listUsers: vi.fn().mockResolvedValue([]) },
health: { get: mocks.health },
providers: { list: mocks.providers },
runs: { list: mocks.runs, get: mocks.run, resume: mocks.resume, retryErrors: mocks.retryErrors, retryResult: mocks.retryResult, results: mocks.results },
runs: { list: mocks.runs, get: mocks.run, resume: mocks.resume, retryErrors: mocks.retryErrors, retryResult: mocks.retryResult, results: mocks.results, export: mocks.exportRun },
reports: { list: mocks.reports, get: mocks.report },
} }))
@@ -56,6 +57,7 @@ beforeEach(() => {
mocks.retryErrors.mockResolvedValue({ run_id: 1, status: 'pending', selected_count: 100, retry_count: 5 })
mocks.retryResult.mockResolvedValue({ run_id: 1, status: 'pending', selected_count: 1, execution_id: 'R0049' })
mocks.results.mockResolvedValue([])
mocks.exportRun.mockResolvedValue(new Blob(['# report'], { type: 'text/markdown' }))
mocks.reports.mockResolvedValue([])
mocks.report.mockResolvedValue({ run_id: 1, summary: { test_result: {}, admission: { decision: 'pass', coverage: {} } } })
})
@@ -64,6 +66,7 @@ afterEach(() => {
cleanup()
localStorage.clear()
vi.clearAllMocks()
vi.unstubAllGlobals()
})
describe('page mounting and route security', () => {
@@ -122,6 +125,36 @@ describe('page mounting and route security', () => {
expect(screen.getByText('逐条结果 (1 / 2)')).toBeInTheDocument()
})
it('exports terminal run results with the active filters', async () => {
const createObjectURL = vi.fn(() => 'blob:report')
const revokeObjectURL = vi.fn()
class MockURL extends URL {}
MockURL.createObjectURL = createObjectURL
MockURL.revokeObjectURL = revokeObjectURL
vi.stubGlobal('URL', MockURL)
const click = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => undefined)
mount('/runs/1')
const exportButton = await screen.findByRole('button', { name: '导出详细报告' })
expect(exportButton.closest('.results-panel')).not.toBeNull()
expect(document.querySelector('.page-actions')).not.toContainElement(exportButton)
await userEvent.click(screen.getByRole('combobox', { name: '仲裁结论' }))
await userEvent.click(await screen.findByText('不通过'))
await userEvent.click(screen.getByRole('button', { name: '导出详细报告' }))
expect(mocks.exportRun).toHaveBeenCalledWith(1, { executionStatus: undefined, verdict: 'fail' })
expect(createObjectURL).toHaveBeenCalledWith(expect.any(Blob))
expect(click).toHaveBeenCalled()
expect(click.mock.contexts[0]).toMatchObject({ download: 'run_1_results.md', href: 'blob:report' })
expect(revokeObjectURL).toHaveBeenCalledWith('blob:report')
}, 20_000)
it('disables report export while the run is not terminal', async () => {
mocks.run.mockResolvedValueOnce({ ...run, status: 'running', terminal: false, phase: 'executing', progress_percent: 50, poll_after_seconds: 2 })
mount('/runs/1')
expect(await screen.findByRole('button', { name: '导出详细报告' })).toBeDisabled()
})
it('searches run results by execution ID', async () => {
mocks.results.mockResolvedValueOnce([
{ execution_id: 'R0001', case_kind: 'risk', interaction_mode: 'single_turn', execution_status: 'completed', verdict: 'fail', model_input: {}, model_response: 'response', judge_result: {}, error_message: '' },