165 lines
3.8 KiB
JavaScript
165 lines
3.8 KiB
JavaScript
|
|
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/);
|
|||
|
|
});
|