2026-08-12 11:12:23 +08:00
|
|
|
|
import assert from "node:assert/strict";
|
2026-10-08 14:30:36 +08:00
|
|
|
|
import { readFile } from "node:fs/promises";
|
2026-08-12 11:12:23 +08:00
|
|
|
|
import test from "node:test";
|
|
|
|
|
|
import { isCollectionScheduleDue } from "../lib/collection-service.ts";
|
|
|
|
|
|
import { sortWithNullsLast } from "../lib/sort-utils.ts";
|
|
|
|
|
|
|
|
|
|
|
|
test("runs the Beijing collection schedule from 09:00", () => {
|
|
|
|
|
|
const scheduledDate = "2026-08-12";
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
isCollectionScheduleDue(
|
|
|
|
|
|
scheduledDate,
|
|
|
|
|
|
Date.parse("2026-08-12T00:59:59Z"),
|
|
|
|
|
|
),
|
|
|
|
|
|
false,
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
isCollectionScheduleDue(
|
|
|
|
|
|
scheduledDate,
|
|
|
|
|
|
Date.parse("2026-08-12T01:00:00Z"),
|
|
|
|
|
|
),
|
|
|
|
|
|
true,
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
isCollectionScheduleDue(
|
|
|
|
|
|
scheduledDate,
|
|
|
|
|
|
Date.parse("2026-08-13T00:00:00Z"),
|
|
|
|
|
|
),
|
|
|
|
|
|
true,
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
isCollectionScheduleDue(
|
|
|
|
|
|
scheduledDate,
|
|
|
|
|
|
Date.parse("2026-08-11T16:00:00Z"),
|
|
|
|
|
|
),
|
|
|
|
|
|
false,
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test("sorts both directions while keeping missing values last", () => {
|
|
|
|
|
|
const values = [
|
|
|
|
|
|
{ id: "missing-a", value: null },
|
|
|
|
|
|
{ id: "middle", value: 20 },
|
|
|
|
|
|
{ id: "high", value: 50 },
|
|
|
|
|
|
{ id: "missing-b", value: null },
|
|
|
|
|
|
{ id: "low", value: 10 },
|
|
|
|
|
|
];
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
|
sortWithNullsLast(values, (item) => item.value, "asc").map(
|
|
|
|
|
|
(item) => item.id,
|
|
|
|
|
|
),
|
|
|
|
|
|
["low", "middle", "high", "missing-a", "missing-b"],
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
|
sortWithNullsLast(values, (item) => item.value, "desc").map(
|
|
|
|
|
|
(item) => item.id,
|
|
|
|
|
|
),
|
|
|
|
|
|
["high", "middle", "low", "missing-a", "missing-b"],
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2026-10-08 14:30:36 +08:00
|
|
|
|
|
|
|
|
|
|
test("a failed collection keeps historically successful rows on success", async () => {
|
|
|
|
|
|
const source = await readFile(
|
|
|
|
|
|
new URL("../lib/collection-service.ts", import.meta.url),
|
|
|
|
|
|
"utf8",
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 失败写入必须按 latest_likes 分流:无数据 → failed;
|
|
|
|
|
|
// 已有历史数据 → 保持 success,仅描述里说明沿用上次数据。
|
|
|
|
|
|
assert.match(
|
|
|
|
|
|
source,
|
|
|
|
|
|
/collection_status = CASE\s+WHEN latest_likes IS NULL THEN 'failed'\s+ELSE 'success'\s+END/,
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.match(
|
|
|
|
|
|
source,
|
|
|
|
|
|
/ELSE \?\s+END,\s+collection_updated_at = CASE\s+WHEN latest_likes IS NULL THEN CURRENT_TIMESTAMP\s+ELSE collection_updated_at/,
|
|
|
|
|
|
);
|
|
|
|
|
|
assert.match(source, /`\$\{message\},沿用上次采集数据`/);
|
|
|
|
|
|
// run 行照实记录失败,保留可观测性
|
|
|
|
|
|
assert.match(source, /UPDATE collection_runs\s+SET status = 'failed'/);
|
|
|
|
|
|
});
|