import assert from "node:assert/strict"; import test from "node:test"; import { buildKocDirectMessage, oneClickPushTask, OneClickPushError, } from "../lib/task-one-click-push-service.ts"; import { clearWecomAccessTokenCacheForTests, resolveWecomConfig, } from "../lib/wecom-client.ts"; const baseBindings = { WECOM_CORP_ID: "corp-test", WECOM_AGENT_ID: "1000001", WECOM_SECRET: "secret-test", }; const taskRow = { id: "task-1", name: "测试任务", brand: "品牌A", quantity: 3, due_at: "2026-09-05", task_type: "content_publish", platform: "小红书", content_format: "image_text", share_token: "tok-123", project_id: "", }; const boundPartners = [ { id: "p-1", name: "合作方甲", wecom_external_user_id: "wmOK1" }, { id: "p-2", name: "合作方乙", wecom_external_user_id: "wmOK2" }, { id: "p-3", name: "合作方丙", wecom_external_user_id: "wmFAIL" }, ]; function fakePushDb({ task = taskRow, partners = boundPartners, todayLog = null, } = {}) { const executed = []; const makeStatement = (sql, params = []) => ({ sql, params, bind(...args) { return makeStatement(sql, args); }, async run() { executed.push({ sql, params }); return { meta: { changes: 1 } }; }, async first() { executed.push({ sql, params }); if (/FROM tasks WHERE id = \?/.test(sql)) return task; if (/FROM wecom_push_logs/.test(sql) && /DATE\(created_at\) = CURDATE\(\)/.test(sql)) { return todayLog; } return null; }, async all() { executed.push({ sql, params }); if (/FROM partners/.test(sql) && /wecom_external_user_id/.test(sql)) { return { results: partners }; } return { results: [] }; }, }); return { executed, prepare(sql) { return makeStatement(sql); }, }; } function fakeWecomFetch(calls, { invalidUsers = [] } = {}) { return async (input, init = {}) => { const url = new URL(String(input)); calls.push({ url, init }); if (url.pathname.endsWith("/cgi-bin/gettoken")) { return Response.json({ errcode: 0, errmsg: "ok", access_token: "token-test", expires_in: 7200, }); } if (url.pathname.endsWith("/cgi-bin/message/send")) { const body = JSON.parse(String(init.body)); if (invalidUsers.includes(body.touser)) { return Response.json({ errcode: 0, errmsg: "ok", invaliduser: body.touser, }); } return Response.json({ errcode: 0, errmsg: "ok" }); } return new Response("not found", { status: 404 }); }; } test.beforeEach(() => { clearWecomAccessTokenCacheForTests(); }); test("buildKocDirectMessage targets KOC with claim url and OAuth hint", () => { const message = buildKocDirectMessage(taskRow, "https://portal.example.com/"); assert.match(message, /【新任务】测试任务/); assert.match(message, /先到先得/); assert.match(message, /免输身份/); assert.match(message, /https:\/\/portal\.example\.com\/\?task=tok-123/); }); test("oneClickPushTask sends only to selected partners and logs the push", async () => { process.env.KOC_PORTAL_URL = "https://portal.example.com"; try { const db = fakePushDb(); const calls = []; const outcome = await oneClickPushTask( db, { taskId: "task-1", sendMainGroup: false, sendKoc: true, createGroupTemplate: false, selectedKocPartnerIds: ["p-1", "p-3"], }, resolveWecomConfig(baseBindings), fakeWecomFetch(calls, { invalidUsers: ["wmFAIL"] }), ); const appCalls = calls.filter((call) => call.url.pathname.endsWith("/cgi-bin/message/send"), ); assert.deepEqual( appCalls.map((call) => JSON.parse(String(call.init.body)).touser), ["wmOK1", "wmFAIL"], ); for (const call of appCalls) { assert.match(JSON.parse(String(call.init.body)).text.content, /【新任务】测试任务/); } assert.equal(outcome.kocSent, 1); assert.equal(outcome.kocFailed, 1); assert.equal(outcome.kocResults[1].partnerName, "合作方丙"); assert.match(outcome.kocResults[1].error, /invaliduser/); assert.equal(outcome.linkUrl, "https://portal.example.com/?task=tok-123"); const insert = db.executed.find((item) => /INSERT INTO wecom_push_logs/.test(item.sql)); assert.ok(insert, "push log should be written"); assert.equal(insert.params[1], "task-1"); assert.equal(insert.params[3], "task_open"); assert.equal(insert.params[6], 1); const kocUserids = JSON.parse(insert.params[5]); assert.equal(kocUserids.length, 2); assert.equal(kocUserids[0].externalUserId, "wmOK1"); } finally { delete process.env.KOC_PORTAL_URL; } }); test("oneClickPushTask blocks duplicate same-day push unless forced", async () => { process.env.KOC_PORTAL_URL = "https://portal.example.com"; try { const db = fakePushDb({ todayLog: { id: "wpl-old", task_id: "task-1", push_kind: "task_open" }, }); const calls = []; await assert.rejects( () => oneClickPushTask( db, { taskId: "task-1", sendMainGroup: false, sendKoc: true }, resolveWecomConfig(baseBindings), fakeWecomFetch(calls), ), (error) => error instanceof OneClickPushError && /仍要推送/.test(error.message) && error.status === 409, ); assert.equal(calls.length, 0, "no wecom api calls on duplicate block"); const outcome = await oneClickPushTask( db, { taskId: "task-1", sendMainGroup: false, sendKoc: true, selectedKocPartnerIds: ["p-2"], force: true, }, resolveWecomConfig(baseBindings), fakeWecomFetch(calls), ); assert.equal(outcome.kocSent, 1); } finally { delete process.env.KOC_PORTAL_URL; } }); test("oneClickPushTask without project skips project targets and still reaches KOC", async () => { process.env.KOC_PORTAL_URL = "https://portal.example.com"; try { const db = fakePushDb({ task: { ...taskRow, project_id: "" } }); const calls = []; const outcome = await oneClickPushTask( db, { taskId: "task-1", sendMainGroup: true, sendKoc: true }, resolveWecomConfig(baseBindings), fakeWecomFetch(calls), ); assert.ok( outcome.skippedReasons.some((reason) => /未挂项目/.test(reason)), "should explain project skip", ); assert.equal(outcome.projectOutcome, null); assert.equal(outcome.kocSent, 3); assert.equal( calls.filter((call) => call.url.pathname.includes("/cgi-bin/externalcontact/")).length, 0, "no group template calls expected", ); } finally { delete process.env.KOC_PORTAL_URL; } }); test("oneClickPushTask requires share token and at least one target", async () => { await assert.rejects( () => oneClickPushTask( fakePushDb({ task: { ...taskRow, share_token: "" } }), { taskId: "task-1", sendMainGroup: false, sendKoc: true }, resolveWecomConfig(baseBindings), fakeWecomFetch([]), ), (error) => error instanceof OneClickPushError && /领取链接/.test(error.message), ); await assert.rejects( () => oneClickPushTask( fakePushDb(), { taskId: "task-1", sendMainGroup: false, sendKoc: false, createGroupTemplate: false }, resolveWecomConfig(baseBindings), fakeWecomFetch([]), ), (error) => error instanceof OneClickPushError && /至少选择一个推送目标/.test(error.message), ); });