Files
koc-loop/tests/partner-wecom-claim.test.mjs
ABAPPLO e2a93e22cf feat: 任务模型物理合并,发布+回收并为单任务双阶段
- tasks 新增 recovery_enabled/recovery_due_at/recovery_instructions/recovery_example_images,回收挂自己名下(先发布后交截图),不再置 complete
- 存量 screenshot_collect 迁移并入父任务后删除(mysql/0020 含 DELETE,生产执行前务必备份)
- 新 action task_enable_recovery:ghost 面板一键开通回收,截止默认=发布截止
- 企微催办双阶段(待发布/截图待提交);超期自动释放仅发布阶段
- admin 删 groupTaskMasters 聚合改单卡双面板;portal 详情页回收截图区块+批次回收进度
- 测试 309 全绿 + Playwright E2E 20/20
2026-09-24 15:52:45 +08:00

114 lines
3.7 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
const routeSource = readFileSync(
new URL("../app/api/partner/route.ts", import.meta.url),
"utf8",
);
const portalSource = readFileSync(
new URL("../koc-portal/app/page.tsx", import.meta.url),
"utf8",
);
test("partner route accepts identityTicket for wecom-backed claim", () => {
assert.match(routeSource, /identityTicket\?: string/);
assert.match(routeSource, /verifyWecomIdentityTicket/);
assert.match(routeSource, /resolveWecomClaimant/);
});
test("claim keeps manual wechat/phone path untouched when no ticket", () => {
assert.match(
routeSource,
/请输入正确的微信号或手机号/,
"manual validation message preserved",
);
assert.match(
routeSource,
/VALUES \(\?, \?, \?, '外部KOC', 0, 0\)\s*\n\s*ON CONFLICT\(id\) DO UPDATE SET\s*\n\s*name = excluded\.name,\s*\n\s*wecom_name = excluded\.wecom_name`/,
"manual partner upsert statement preserved",
);
});
test("claim with ticket upserts wecom_external_user_id for reminders", () => {
assert.match(
routeSource,
/wecom_external_user_id = excluded\.wecom_external_user_id/,
);
});
test("internal identity claim resolves the group_owner partner by wecom_userid", () => {
assert.match(
routeSource,
/WHERE wecom_userid = \? AND partner_type = 'group_owner'/,
);
assert.match(routeSource, /未找到你的群主身份/);
assert.match(
routeSource,
/claimantName = ownerPartner\.name \|\| internalUserid/,
);
});
test("external wecom claim upserts partner_type group_owner", () => {
assert.match(
routeSource,
/wecom_external_user_id, partner_type\)\s*\n\s*VALUES \(\?, \?, \?, '外部KOC', 0, 0, \?, 'group_owner'\)/,
);
assert.match(
routeSource,
/partner_type = 'group_owner'`/,
);
});
test("claim records owner_partner_id after validating a group_owner partner", () => {
assert.match(routeSource, /owner\?: string/);
assert.match(
routeSource,
/SELECT id FROM partners WHERE id = \? AND partner_type = 'group_owner'/,
);
assert.match(
routeSource,
/INSERT INTO claims\s*\n\s*\(\s*id, task_id, partner_id, owner_partner_id, claimant_name, claim_token, quantity\)/,
);
});
test("claim schedules task group creation and broadcast in background", () => {
assert.match(routeSource, /ensureTaskChatForMembers/);
assert.match(routeSource, /taskClaimBroadcastText/);
assert.match(routeSource, /taskChatIntroText/);
assert.match(routeSource, /runInBackground\(/);
assert.match(
routeSource,
/internalUserid \? \{ addUserids: \[internalUserid\] \} : \{\}/,
);
});
test("recover resolves internal identity via wecom_userid as well", () => {
assert.match(
routeSource,
/wecomIdentity\?\.userId[\s\S]{0,200}WHERE wecom_userid = \? AND partner_type = 'group_owner'/,
);
});
test("portal preserves owner param through the oauth round trip and sends it with claim", () => {
assert.match(portalSource, /owner: string;/);
assert.match(portalSource, /compact\.set\("o", params\.owner\)/);
assert.match(portalSource, /fallback\.set\("o", params\.owner\)/);
assert.match(portalSource, /parsed\.get\("o"\) \?\? ""/);
assert.match(portalSource, /params\.get\("owner"\) \?\? ""/);
assert.match(
portalSource,
/ownerPartnerId \? \{ owner: ownerPartnerId \} : \{\}/,
);
assert.match(portalSource, /userType === "internal"/);
});
test("recover builds dynamic partner candidates including wecom identity", () => {
assert.match(
routeSource,
/wecomPartnerId \? \[wecomPartnerId\] : \[partnerId, legacyPartnerId\]/,
);
assert.match(routeSource, /\.bind\(task\.id, \.\.\.partnerCandidates\)/);
});