import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; // claimed_quantity 口径:领取(+1)/释放(-1) 维护的不变式是 // claimed_quantity == 当前分发行数。种子与 0020 迁移都可能造成漂移, // 这里钉住三道防线:种子末尾对账、0021 生产对账、UI 一律读实时 dist_total。 test("seed reconciles claimed_quantity with actual distribution rows", async () => { const source = await readFile( new URL("../lib/mvp-db.ts", import.meta.url), "utf8", ); const reconcile = source.indexOf( "SET claimed_quantity = (\n SELECT COUNT(*) FROM distributions d WHERE d.task_id = tasks.id", ); assert.ok(reconcile > 0, "种子阶段缺少 claimed_quantity 对账 UPDATE"); const lastDistributionInsert = source.lastIndexOf( "INSERT INTO distributions", ); assert.ok( reconcile > lastDistributionInsert, "对账必须发生在所有分发 INSERT 之后", ); const batchEnd = source.indexOf("await db.batch(batch);", reconcile); assert.ok(batchEnd > reconcile, "对账语句必须在 seedIfEmpty 的 batch 内执行"); }); test("migration 0021 recalculates drifted claimed_quantity in production", async () => { const migration = await readFile( new URL("../mysql/0021_reconcile_claimed_quantity.sql", import.meta.url), "utf8", ); assert.match(migration, /UPDATE tasks\s+SET claimed_quantity = \(\s+SELECT COUNT\(\*\) FROM distributions d\s+WHERE d\.task_id = tasks\.id\s+\)/); assert.match(migration, /WHERE claimed_quantity <> \(/); }); test("admin surfaces read the live distribution count, not the counter", async () => { const [adminApp, projectService] = await Promise.all([ readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"), readFile(new URL("../lib/project-service.ts", import.meta.url), "utf8"), ]); // 任务卡双面板 + 概览统计 assert.match(adminApp, /panelMetric\(task\.dist_total \?\? 0, task\.quantity, "已领取"/); assert.match(adminApp, /panelMetric\(task\.dist_submitted \?\? 0, task\.dist_total \?\? 0, "已收取"/); assert.match(adminApp, /sum \+ \(task\.dist_total \?\? 0\)/); // 项目树明细同样用实时口径 assert.match(projectService, /AS dist_total/); assert.match(adminApp, /\{task\.dist_total\} \/ \{task\.quantity\}/); // 展示层不再读反规范化计数器 assert.doesNotMatch(adminApp, /task\.claimed_quantity/); });