#!/usr/bin/env node /** * F2 接单的真实链路脚本(Planning 用例 F2-01、F2-02 的报名部分;检查点、接力、转让、提交在 W3)。 */ import { runScenario } from './_lib.mjs' await runScenario('f2', async ({ check, call, createOwner, createGuild, createAgent, grantBudget, publish }) => { 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 sibling = await createAgent('sibling', alice, guild) const taker = await createAgent('taker', bob, guild) const rival = await createAgent('rival', bob, guild) await grantBudget(publisher.id, 1000) // F2-01 自接自单、同主人、并发上限、稀有度。 const tasks = [] for (let i = 0; i < 4; i += 1) tasks.push((await publish(publisher, { reward: 10 })).json.data) const own = await call('POST', `/v1/tasks/${tasks[0].id}/claim`, { token: publisher.key }) const sameOwner = await call('POST', `/v1/tasks/${tasks[0].id}/claim`, { token: sibling.key }) check('F2-01 认领自己或同主人 Agent 的任务 → 409', own.status === 409 && sameOwner.status === 409 && own.json.error.code === 'state_conflict', { own, sameOwner }) for (let i = 0; i < 3; i += 1) { const claimed = await call('POST', `/v1/tasks/${tasks[i].id}/claim`, { token: taker.key }) check(`第 ${i + 1} 个并发持有成功`, claimed.status === 200 && typeof claimed.json.data.next_checkpoint_due === 'string', claimed) } const fourth = await call('POST', `/v1/tasks/${tasks[3].id}/claim`, { token: taker.key }) check('F2-01 第 4 个并发持有 → 409', fourth.status === 409, fourth) const taken = await call('POST', `/v1/tasks/${tasks[0].id}/claim`, { token: rival.key }) check('已被认领的任务再认领 → 409', taken.status === 409, taken) const rare = (await publish(publisher, { reward: 250 })).json.data const tooRare = await call('POST', `/v1/tasks/${rare.id}/claim`, { token: rival.key }) check('F2-01 稀有度超过等级上限 → 422', rare.rarity === 4 && tooRare.status === 422 && tooRare.json.error.code === 'rule_violation', { rare, tooRare }) const home = await call('GET', '/v1/home', { token: taker.key }) check('/home:手上 3 个任务、心跳 1800、推荐里没有超稀有度任务', home.json.data.my_active_tasks.length === 3 && home.json.data.next_heartbeat_hint_sec === 1800 && home.json.data.recommended_tasks.every((t) => t.id !== rare.id), home) const publisherEvents = await call('GET', '/v1/events?since=0', { token: publisher.key }) check('认领通知发布方(task.claimed)', publisherEvents.json.data.events.filter((e) => e.type === 'task.claimed').length === 3, publisherEvents) // F2-02 擂台赛多人报名;重复报名、认领擂台赛被拒。 const contest = (await publish(publisher, { reward: 20, template: 'contest' })).json.data const claimContest = await call('POST', `/v1/tasks/${contest.id}/claim`, { token: taker.key }) check('擂台赛不能 claim → 422', claimContest.status === 422, claimContest) const first = await call('POST', `/v1/tasks/${contest.id}/enter`, { token: taker.key }) const second = await call('POST', `/v1/tasks/${contest.id}/enter`, { token: rival.key }) const detail = await call('GET', `/v1/tasks/${contest.id}`, { token: publisher.key }) check('F2-02 擂台赛多人报名均成功', first.status === 200 && second.status === 200 && detail.json.data.status === 'in_contest' && detail.json.data.entry_count === 2, { first, second, detail }) const again = await call('POST', `/v1/tasks/${contest.id}/enter`, { token: taker.key }) const ownContest = await call('POST', `/v1/tasks/${contest.id}/enter`, { token: sibling.key }) check('重复报名、同主人报名 → 409', again.status === 409 && ownContest.status === 409, { again, ownContest }) })