Files
koc-loop/tests/public-metrics-fallback.test.mjs
ABAPPLO c11afd5f99 feat: 采集失败KOC补交截图闭环 + 任务中心分发详情弹窗
公开数据补交闭环(F1):
- distributions 新增 public_metrics_screenshot_key(mysql/0022 迁移)
- KOC 门户:链接已回填但采集失败时展示补交数据截图入口(kind=public-metrics)
- 运营端:回收队列"核对补录"支持公开互动数据(赞/藏/评),按补交截图补录后采集状态置成功
- 企微催办:采集失败纳入待办(公开数据待补交)
- task-result-image 支持 kind=publish 输出发布截图

门户文案(F2):
- 自动采集数据明确"由系统每天自动采集,无需你填写",仅识别失败时才手动填写曝光/阅读

任务中心分发详情:
- 任务中心/数据回收的分发表行可点击,弹窗查看详情
- 详情按任务类型适配:发布阶段/数据回收(自动采集)/创作者数据(第7天)/截图回收四区块三段式布局
2026-10-09 17:02:46 +08:00

102 lines
5.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
// 公开数据采集失败兜底闭环(2026-10-09):
// 自动采集优先 → 采集失败时催 KOC 补交数据截图 → 运营按截图核对补录互动数据。
// 同时修正门户创作者数据区块的表述:公开数据系统自动采集,KOC 不需要每天手动填。
test("koc can submit a public-metrics screenshot when auto collection fails", async () => {
const [uploadRoute, imageRoute, partnerRoute, page, schema, migration] =
await Promise.all([
readFile(new URL("../app/api/partner-upload/route.ts", import.meta.url), "utf8"),
readFile(new URL("../app/api/partner-image/route.ts", import.meta.url), "utf8"),
readFile(new URL("../app/api/partner/route.ts", import.meta.url), "utf8"),
readFile(new URL("../koc-portal/app/page.tsx", import.meta.url), "utf8"),
readFile(new URL("../db/schema.ts", import.meta.url), "utf8"),
readFile(new URL("../mysql/0022_public_metrics_screenshot.sql", import.meta.url), "utf8"),
]);
assert.match(migration, /ALTER TABLE distributions ADD COLUMN public_metrics_screenshot_key TEXT/);
assert.match(schema, /publicMetricsScreenshotKey/);
// 上传通道:仅发布后可补交,落 public-metrics/ 目录并标记待核对
assert.match(uploadRoute, /"public-metrics"/);
assert.match(uploadRoute, /\(isCreatorCenter \|\| isPublicMetrics\) && !assignment\.publish_url/);
assert.match(uploadRoute, /public-metrics\/\$\{upload\.distributionId\}/);
assert.match(uploadRoute, /public_metrics_screenshot_key = \?/);
assert.match(uploadRoute, /KOC已补交数据截图,待运营核对/);
// 领取载荷:门户能感知采集状态、已采集到的公开数据与补交截图
assert.match(partnerRoute, /d\.collection_status,/);
assert.match(partnerRoute, /d\.latest_likes,/);
assert.match(partnerRoute, /d\.public_metrics_screenshot_key,/);
// 图片服务:KOC 可查看自己补交的数据截图(可变凭证不缓存)
assert.match(imageRoute, /imageKind === "public-metrics"/);
assert.match(imageRoute, /d\.public_metrics_screenshot_key/);
assert.match(imageRoute, /imageKind === "public-metrics"/);
// 门户:仅采集失败且已发布时展示补交入口
assert.match(page, /collection_status === "failed"/);
assert.match(page, /"public-metrics"/);
assert.match(page, /submitPublicMetricsShot/);
assert.match(page, /自动采集失败,请补交数据截图/);
assert.match(page, /运营核对后会补录数据/);
});
test("creator data section reads as one-time d7 step, not a daily manual form", async () => {
const [page, styles] = await Promise.all([
readFile(new URL("../koc-portal/app/page.tsx", import.meta.url), "utf8"),
readFile(new URL("../koc-portal/app/globals.css", import.meta.url), "utf8"),
]);
// 自动采集状态行:有数据时直接展示最近采到的赞藏评
assert.match(page, /auto-collect-line/);
assert.match(page, /公开数据系统自动采集中/);
assert.match(page, /由系统每天自动采集,无需你填写/);
// 一次性表述 + 仅识别失败才手动填
assert.match(page, /这一步只需做一次/);
assert.match(page, /仅当识别失败时才需要手动填写这两个数字/);
assert.match(page, /只有识别失败时才需要手动修改/);
assert.match(styles, /\.auto-collect-line/);
assert.match(styles, /\.metrics-fallback/);
});
test("admin reviews the fallback screenshot and records engagement metrics", async () => {
const [actionRoute, adminApp, creatorRoute] = await Promise.all([
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
readFile(new URL("../app/api/creator-screenshot/route.ts", import.meta.url), "utf8"),
]);
// manual_metrics:曝光/阅读可留空;补录互动数据并把采集状态置成功
assert.match(actionRoute, /publicLikes/);
assert.match(actionRoute, /latest_likes = CASE WHEN \? THEN \? ELSE latest_likes END/);
assert.match(actionRoute, /collection_status = CASE WHEN \? THEN 'success' ELSE collection_status END/);
assert.match(actionRoute, /运营已按补交截图核对补录/);
// 后台:采集失败行出现「核对补录」入口,状态列可直达补交截图
assert.match(adminApp, /核对补录/);
assert.match(adminApp, /KOC补交截图/);
assert.match(adminApp, /kind=public-metrics/);
assert.match(adminApp, /请至少填写创作者数据或公开互动数据中的一项/);
// 截图服务:kind=public-metrics 仅管理端可读
assert.match(creatorRoute, /"public-metrics"/);
assert.match(creatorRoute, /public_metrics_screenshot_key/);
});
test("due-soon reminders include failed collections as a metrics phase", async () => {
const [notifier, adminApp] = await Promise.all([
readFile(new URL("../lib/wecom-notifier-service.ts", import.meta.url), "utf8"),
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
]);
// 催办查询纳入采集失败(不限日期),文案区分「公开数据待补交」
assert.match(notifier, /WHEN d\.collection_status = 'failed' THEN 'metrics'/);
assert.match(notifier, /AND d\.collection_status = 'failed'\)/);
assert.match(notifier, /公开数据待补交/);
// 回收队列的「催办未完成」计数与催办口径对齐(含采集失败行)
assert.match(adminApp, /\(recoveryEnabled \? recoveryPending : 0\) \+/);
assert.match(adminApp, /\+\s*failedCollectionCount/);
});