concurrency.mjs 2.27 KB
#!/usr/bin/env node
/**
 * 并发与幂等的真实链路脚本(Planning 用例 CC-01、CC-02;CC-03 结算并发在 W4)。
 * CC-01 用 6 个 Agent(每个主人最多 3 个)同时发 20 个认领请求,断言恰好 1 个成功。
 */
import { randomUUID } from 'node:crypto'

import { runScenario } from './_lib.mjs'

await runScenario('cc', async ({ db, check, call, createOwner, createGuild, createAgent, publish }) => {
  const alice = await createOwner('alice')
  const bob = await createOwner('bob')
  const carol = await createOwner('carol')
  const guild = await createGuild('main', alice.user_id)
  const publisher = await createAgent('publisher', alice, guild)
  const takers = []
  for (const [owner, tag] of [[bob, 'bob'], [carol, 'carol']]) {
    for (let i = 1; i <= 3; i += 1) takers.push(await createAgent(`${tag}-${i}`, owner, guild))
  }

  // CC-02 同一 Idempotency-Key 并发发布 10 次 → 1 个任务、1 笔扣款。
  const key = randomUUID()
  const replays = await Promise.all(Array.from({ length: 10 }, () => publish(publisher, { reward: 30 }, key)))
  const taskIds = new Set(replays.map((r) => r.json.data && r.json.data.id))
  const wallet = (await call('GET', '/v1/wallet', { token: publisher.key })).json.data
  const holds = await db('billing_ledger').where({ reason: 'vk_escrow_hold', ref_id: [...taskIds][0] })
  check('CC-02 发布重放 10 次 → 1 个任务、1 笔扣款', replays.every((r) => r.status === 200) && taskIds.size === 1 && holds.length === 1 && wallet.available === 70 && wallet.held === 30, { statuses: replays.map((r) => r.status), task_ids: [...taskIds], wallet })

  // CC-01 20 个并发认领同一计件活 → 恰好 1 个成功。
  const taskId = [...taskIds][0]
  const claims = await Promise.all(Array.from({ length: 20 }, (_, i) => call('POST', `/v1/tasks/${taskId}/claim`, { token: takers[i % takers.length].key })))
  const won = claims.filter((r) => r.status === 200)
  const openHolds = await db('vk_task_holds').where({ task_id: taskId }).whereNull('ended_at')
  check('CC-01 20 个并发认领 → 恰好 1 个成功,其余 409', won.length === 1 && claims.filter((r) => r.status === 409).length === 19 && openHolds.length === 1 && openHolds[0].agent_id === won[0].json.data.holder_agent_id, { statuses: claims.map((r) => r.status) })
})