import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; // 2026-10-08 第二轮交互走查修复: // 1) 完成态判据统一为「曝光量+阅读量齐即 complete」(原 d7_likes 条件全库无赋值方,永不触发) // 2) 回收队列头部统计改为「已发布 N / 总数」口径,未回填行不再计入 // 3) 行内更新/补填链接从原生 prompt() 改为弹窗表单,失败保留已输入内容 // 4) 顶栏「获取KOC领取链接」从导航跳转改为直接弹出各任务领取链接列表 test("creator metrics completion follows the exposure+views criterion", async () => { const sources = await Promise.all([ readFile(new URL("../app/api/partner/route.ts", import.meta.url), "utf8"), readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"), ]); for (const source of sources) { assert.match( source, /status = CASE WHEN exposure IS NOT NULL AND views IS NOT NULL THEN 'complete' ELSE status END/, ); } // 死条件必须清除:d7_likes 只被置 NULL 从不赋值,不能作为完成判据 for (const source of sources) { assert.doesNotMatch( source, /CASE WHEN d7_likes IS NOT NULL THEN 'complete'/, ); } }); test("manual metrics refreshes the task status afterwards", async () => { const source = await readFile( new URL("../app/api/action/route.ts", import.meta.url), "utf8", ); assert.match(source, /import \{ refreshTaskStatus \} from "\.\.\/\.\.\/\.\.\/lib\/task-service";/); const manual = source.slice( source.indexOf('body.action === "manual_metrics"'), source.indexOf('body.action === "bind_wecom_external_id"'), ); assert.ok(manual.includes("manual_metrics"), "必须截取到 manual_metrics 分支"); assert.match(manual, /SELECT task_id FROM distributions WHERE id = \?/); assert.match(manual, /refreshTaskStatus\(ownerTask\.task_id, db\)/); }); test("recovery queue header counts only published rows", async () => { const source = await readFile( new URL("../app/admin-app.tsx", import.meta.url), "utf8", ); assert.match( source, /const publishedCount = taskDistributions\.filter\(/, ); assert.match( source, /已发布 \{publishedCount\} \/ \{taskDistributions\.length\} 篇/, ); }); test("publish url editing uses a modal instead of window.prompt", async () => { const source = await readFile( new URL("../app/admin-app.tsx", import.meta.url), "utf8", ); assert.match(source, /const \[publishUrlModal, setPublishUrlModal\]/); assert.match(source, /onSubmit=\{submitPublishUrl\}/); assert.match(source, /支持长链、短链或完整分享文案/); // 提交失败保留弹窗内容(runAction 失败返回 false) assert.match(source, /if \(updated\) setPublishUrlModal\(null\);/); // 更新链接流程本身不再用 prompt(剪贴板复制失败兜底的 prompt 保留) const updateFn = source.slice( source.indexOf("const updateDistributionPublishUrl"), source.indexOf("const submitPublishUrl"), ); assert.ok(updateFn.includes("updateDistributionPublishUrl"), "必须截取到函数体"); assert.doesNotMatch(updateFn, /prompt/); }); test("claim links button opens a modal listing every task link", async () => { const source = await readFile( new URL("../app/admin-app.tsx", import.meta.url), "utf8", ); assert.match(source, /const \[claimLinksOpen, setClaimLinksOpen\] = useState\(false\);/); // 顶栏按钮不再是导航跳转 assert.match(source, /onClick=\{\(\) => setClaimLinksOpen\(true\)\}/); assert.doesNotMatch( source, /onClick=\{\(\) => setActiveNav\("tasks"\)\}\s*\n\s*获取KOC领取链接/, ); // 弹窗里每个任务给出复制按钮,无链接的任务明示未生成 assert.match(source, /claim-links-list/); assert.match(source, /KOC领取链接已复制/); assert.match(source, /尚未生成领取链接/); // 弹窗内点击(含复制按钮)不能冒泡到 backdrop 导致误关闭 const modal = source.slice( source.indexOf('className="modal-card compact claim-links-modal"'), source.indexOf('