HH-846 / HH-799: add task recovery and audit (#27)

This commit is contained in:
2026-08-31 13:19:54 +08:00
parent 0fbfb335c4
commit b117cc738f
20 changed files with 1545 additions and 61 deletions
+65
View File
@@ -171,3 +171,68 @@ test('keeps draft review responsive and restores focus after dialog close and su
await expect(page.getByRole('dialog')).toBeHidden()
await expect(trigger).toBeFocused()
})
test('keeps task recovery and audit traceable without blind retry at 599px, 900px and 1280px', async ({ page }) => {
let verified = false
let resumed = false
let auditPage = 0
const baseTask = {
id: 'task-a', account_id: 'account-a', draft_id: 'draft-a', confirmation_id: 'confirmation-a', state: 'needs_confirmation',
hold_reason: 'task_result_uncertain', browser_env_alias: 'env-a', network_exit_id: 'exit-a', runtime_instance_id: 'runtime-a', binding_version: 4,
confirmation: { id: 'confirmation-a', version: 1, account_version: 2, draft_version: 1, browser_env_alias: 'env-a', network_exit_id: 'exit-a', runtime_instance_id: 'runtime-a', binding_version: 4 },
attempts: [{ id: 'attempt-a', started_at: '2026-08-30T00:00:00Z', outcome: 'uncertain', evidence: { mock_outcome: 'uncertain' } }],
}
const listTask = { ...baseTask, confirmation: undefined, attempts: undefined, allowed_action: undefined, updated_at: '2026-08-30T00:01:00Z' }
await page.route(/\/api\/phase-a\/tasks(?:\/.*)?(?:\?.*)?$/, async route => {
const request = route.request()
const url = new URL(request.url())
if (request.method() === 'POST' && url.pathname.endsWith('/verify')) {
expect(request.postDataJSON()).toEqual({ result: 'not_executed' })
verified = true
return route.fulfill({ status: 204 })
}
if (request.method() === 'POST' && url.pathname.endsWith('/resume')) {
resumed = true
return route.fulfill({ status: 204 })
}
if (url.pathname.endsWith('/task-a')) return route.fulfill({ json: { ...baseTask, state: resumed ? 'queued' : baseTask.state, allowed_action: resumed ? '' : verified ? 'resume' : 'verify', verification_result: verified ? 'not_executed' : undefined } })
return route.fulfill({ json: [listTask] })
})
await page.route('**/api/phase-a/attempts/attempt-a', route => route.fulfill({ json: { ...baseTask.attempts[0], task_id: 'task-a', browser_env_alias: 'env-a', network_exit_id: 'exit-a', binding_version: 4 } }))
await page.route('**/api/browsers/env-a', route => route.fulfill({ json: { alias: 'env-a', name: '店铺环境', account_id: 'account-a', image_version: '148', binding_version: 4, runtime_instance_id: 'runtime-a', network_exit: { id: 'exit-a', health_status: 'healthy' } } }))
await page.route('**/api/network-exits/exit-a', route => route.fulfill({ json: { id: 'exit-a', protocol: 'socks5', host: 'proxy.example', port: 1080, health_status: 'healthy', version: 2 } }))
await page.route('**/api/phase-a/audit*', route => {
const currentPage = Number(new URL(route.request().url()).searchParams.get('page') || 1)
auditPage = Math.max(auditPage, currentPage)
return route.fulfill({ json: { data: [{
id: 7, event_type: 'task_verified', account_id: 'account-a', task_id: 'task-a', attempt_id: 'attempt-a', browser_env_alias: 'env-a', network_exit_id: 'exit-a', binding_version: 4, reason_code: 'manual_verification_recorded', created_at: '2026-08-30T00:01:00Z',
}], total: 26, page: currentPage, page_size: 25 } })
})
for (const path of ['/#/tasks', '/#/tasks/task-a', '/#/attempts/attempt-a', '/#/browsers/env-a', '/#/network-exits/exit-a', '/#/audit?task_id=task-a']) {
for (const width of [599, 900, 1280]) {
await page.setViewportSize({ width, height: 900 })
await page.goto(path)
await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width)
}
}
verified = false
await page.setViewportSize({ width: 599, height: 900 })
await page.goto('/#/tasks/task-a')
await expect(page.getByRole('button', { name: /恢复排队/ })).toHaveCount(0)
await page.getByRole('button', { name: '记录人工核验' }).click()
await expect(page.getByRole('button', { name: '按核验结论恢复排队' })).toBeVisible()
await page.getByRole('button', { name: '按核验结论恢复排队' }).click()
await expect.poll(() => resumed).toBe(true)
await expect(page.getByText('已排队')).toBeVisible()
await page.getByRole('link', { name: '查看关联审计' }).click()
await expect(page).toHaveURL(/#\/audit\?task_id=task-a$/)
await expect(page.getByRole('link', { name: 'task-a' }).first()).toBeVisible()
await page.getByRole('button', { name: '下一页' }).click()
await expect.poll(() => auditPage).toBe(2)
await page.getByRole('link', { name: 'attempt-a' }).first().click()
await expect(page).toHaveURL(/#\/attempts\/attempt-a$/)
await expect(page.getByRole('link', { name: '返回任务' })).toHaveAttribute('href', '#/tasks/task-a')
})