Files
koc-loop/tests/task-report.test.mjs

165 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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 test from "node:test";
import { buildTaskReportCsv } from "../lib/task-report-csv.ts";
import { getTaskReportData } from "../lib/mvp-db.ts";
const reportRow = {
id: "task-A",
name: "夏日种草计划",
task_type: "content_publish",
brand: "轻食品牌",
platform: "小红书",
status: "active",
due_at: "2026-08-20",
created_at: "2026-08-01 10:00:00",
quantity: 20,
project_name: "八月投放",
claimed_count: 10,
published_count: 8,
completed_count: 5,
exposure_total: 12000,
views_total: 9000,
d2_likes: 100,
d2_comments: 20,
d2_collects: 30,
d5_likes: 150,
d5_comments: 25,
d5_collects: 40,
d7_likes: 180,
d7_comments: 30,
d7_collects: 50,
latest_likes: 200,
latest_comments: 40,
latest_collects: 60,
latest_shares: 10,
};
test("buildTaskReportCsv writes BOM, headers and mapped rows", () => {
const csv = buildTaskReportCsv([reportRow]);
assert.ok(csv.startsWith(""), "starts with UTF-8 BOM");
const lines = csv.slice(1).split("\r\n");
assert.equal(lines.length, 2);
assert.equal(
lines[0],
[
"任务ID",
"任务名称",
"任务类型",
"品牌",
"平台",
"所属项目",
"任务状态",
"计划数量",
"领取数",
"发布数",
"完成数",
"完成率",
"截止日期",
"创建时间",
"曝光量合计",
"阅读量合计",
"D2点赞",
"D2评论",
"D2收藏",
"D5点赞",
"D5评论",
"D5收藏",
"D7点赞",
"D7评论",
"D7收藏",
"最新点赞",
"最新评论",
"最新收藏",
"最新转发",
].join(","),
);
assert.equal(
lines[1],
[
"task-A",
"夏日种草计划",
"内容发布",
"轻食品牌",
"小红书",
"八月投放",
"进行中",
20,
10,
8,
5,
"50%",
"2026-08-20",
"2026-08-01 10:00:00",
12000,
9000,
100,
20,
30,
150,
25,
40,
180,
30,
50,
200,
40,
60,
10,
].join(","),
);
});
test("buildTaskReportCsv escapes commas and quotes, blanks null metrics", () => {
const csv = buildTaskReportCsv([
{
...reportRow,
name: '含,逗号"引号"任务',
project_name: null,
claimed_count: 0,
exposure_total: null,
views_total: null,
d2_likes: null,
d2_comments: null,
d2_collects: null,
d5_likes: null,
d5_comments: null,
d5_collects: null,
d7_likes: null,
d7_comments: null,
d7_collects: null,
latest_likes: null,
latest_comments: null,
latest_collects: null,
latest_shares: null,
},
]);
const rowLine = csv.slice(1).split("\r\n")[1];
assert.ok(rowLine.includes('"含,逗号""引号""任务"'));
const cells = rowLine.split(",");
assert.equal(cells.at(-1), "", "null latest_shares becomes empty");
assert.equal(cells.at(-18), "", "no claims means empty completion rate");
assert.equal(cells.at(-16), "2026-08-01 10:00:00");
});
test("getTaskReportData aggregates per task via distributions join", async () => {
let capturedSql = "";
const db = {
prepare(sql) {
capturedSql = sql;
return {
async all() {
return { results: [reportRow] };
},
};
},
};
const rows = await getTaskReportData(db);
assert.deepEqual(rows, [reportRow]);
assert.match(capturedSql, /FROM tasks t/);
assert.match(capturedSql, /LEFT JOIN distributions d ON d\.task_id = t\.id/);
assert.match(capturedSql, /LEFT JOIN projects p ON p\.id = t\.project_id/);
assert.match(capturedSql, /GROUP BY t\.id/);
assert.match(capturedSql, /publish_url/);
assert.match(capturedSql, /latest_shares/);
});