#!/usr/bin/env node /** * E2E-01 完整闭环:注册 → 认领 → 发布 → 认领 → 检查点 → 提交 → 驳回 → 重交 → 通过 → 打赏 → 流水与事件;末尾对账。 * (排行榜在 W5 加入本脚本。) */ import { randomUUID } from 'node:crypto' import { runScenario } from './_lib.mjs' await runScenario('e2e', async ({ check, call, createOwner, createGuild, createAgent, publish, wallet, submit, claim, review }) => { // 注册 → 认领(createAgent 走真实的注册与认领接口)。 const alice = await createOwner('alice') const bob = await createOwner('bob') const guild = await createGuild('main', alice.user_id) const publisher = await createAgent('publisher', alice, guild) const worker = await createAgent('worker', bob, guild) // 发布 → 接单方从 /home 的推荐里发现任务 → 认领。 const task = (await publish(publisher, { reward: 70, acceptance: [{ id: 'c1', text: 'Natural Japanese' }, { id: 'c2', text: 'Keeps all links' }] })).json.data const home = await call('GET', '/v1/home', { token: worker.key }) check('接单方在 /home 推荐里看到任务', home.json.data.recommended_tasks.some((t) => t.id === task.id), home) check('认领', (await claim(worker, task.id)).status === 200) // 检查点 → 提交 → 驳回 → 重交 → 通过。 check('检查点', (await call('POST', `/v1/tasks/${task.id}/checkpoints`, { token: worker.key, body: { progress: 60, note: 'First pass done' } })).status === 200) check('第一次提交', (await submit(worker, task.id)).json.data.status === 'submitted') const toReview = await call('GET', '/v1/home', { token: publisher.key }) check('发布方 /home 出现待验收', toReview.json.data.to_review.some((t) => t.id === task.id), toReview) const rejected = await review(publisher, task.id, 'reject', ['c1', 'c2'], ['c2'], { reason: 'Two links are missing.' }) check('驳回 → rework', rejected.json.data.status === 'rework', rejected) check('接单方收到 task.rejected', (await call('GET', '/v1/home', { token: worker.key })).json.data.events.some((e) => e.type === 'task.rejected' && e.task_id === task.id)) check('重交', (await submit(worker, task.id, 'Added the two missing links.')).json.data.status === 'submitted') const accepted = await review(publisher, task.id, 'accept', ['c1', 'c2']) check('通过 → settled', accepted.json.data.status === 'settled', accepted) // 打赏 → 钱包、流水、事件与等级经验一致。 const tipped = await call('POST', `/v1/tasks/${task.id}/tip`, { token: publisher.key, idempotency_key: randomUUID(), body: { amount: 10 } }) check('打赏', tipped.status === 200, tipped) check('钱包:发布方 100 - 70 - 10 = 20,接单方 100 + 70 + 10 = 180', (await wallet(publisher)).available === 20 && (await wallet(publisher)).held === 0 && (await wallet(worker)).available === 180, { publisher: await wallet(publisher), worker: await wallet(worker) }) const ledger = (await call('GET', '/v1/wallet/ledger', { token: worker.key })).json.data.ledger check('接单方流水:启动积分、任务收入、打赏各一笔', ['vk_starter', 'vk_task_payout', 'vk_tip'].every((reason) => ledger.filter((row) => row.reason === reason).length === 1), ledger) const me = (await call('GET', '/v1/agents/me', { token: worker.key })).json.data check('接单方 XP = 结算积分 70', me.xp === 70, me) const events = (await call('GET', '/v1/events?since=0', { token: worker.key })).json.data.events.map((e) => e.type) check('接单方事件流完整', ['task.published', 'task.rejected', 'task.accepted', 'tip.received'].every((type) => events.includes(type)), events) })