feat: add distribution release and recovery controls
This commit is contained in:
58
tests/collection-schedule.test.mjs
Normal file
58
tests/collection-schedule.test.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
import assert from "node:assert/strict";
|
||||
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"],
|
||||
);
|
||||
});
|
||||
58
tests/distribution-release.test.mjs
Normal file
58
tests/distribution-release.test.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import {
|
||||
DistributionReleaseError,
|
||||
distributionReleaseBlockReason,
|
||||
} from "../lib/distribution-release-service.ts";
|
||||
|
||||
test("allows only unfinished assignments to return to the claim pool", () => {
|
||||
assert.equal(
|
||||
distributionReleaseBlockReason({
|
||||
publishUrl: null,
|
||||
resultSubmittedAt: null,
|
||||
taskType: "content_publish",
|
||||
}),
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
distributionReleaseBlockReason({
|
||||
publishUrl: "https://www.xiaohongshu.com/discovery/item/example",
|
||||
resultSubmittedAt: null,
|
||||
taskType: "content_publish",
|
||||
}),
|
||||
"已回填发布链接的笔记不能释放",
|
||||
);
|
||||
assert.equal(
|
||||
distributionReleaseBlockReason({
|
||||
publishUrl: null,
|
||||
resultSubmittedAt: "2026-08-12 09:00:00",
|
||||
taskType: "screenshot_collect",
|
||||
}),
|
||||
"已提交结果截图的任务不能释放",
|
||||
);
|
||||
assert.equal(new DistributionReleaseError("blocked", 409).status, 409);
|
||||
});
|
||||
|
||||
test("releases a claim atomically and restores all counters", async () => {
|
||||
const [service, database, actionRoute, adminApp] = await Promise.all([
|
||||
readFile(new URL("../lib/distribution-release-service.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../lib/database.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/admin-app.tsx", import.meta.url), "utf8"),
|
||||
]);
|
||||
|
||||
assert.match(database, /async transaction<T>/);
|
||||
assert.match(service, /FOR UPDATE/);
|
||||
assert.match(service, /DELETE FROM distributions WHERE id = \?/);
|
||||
assert.match(service, /UPDATE contents SET status = 'available'/);
|
||||
assert.match(service, /claimed_quantity = GREATEST\(claimed_quantity - 1, 0\)/);
|
||||
assert.match(service, /claimed_total = GREATEST\(claimed_total - 1, 0\)/);
|
||||
assert.match(service, /UPDATE claims/);
|
||||
assert.match(service, /UPDATE delegation_bundles/);
|
||||
assert.match(actionRoute, /body\.action === "release_distribution"/);
|
||||
assert.match(actionRoute, /isManagerRequest/);
|
||||
assert.match(adminApp, /确认释放这篇笔记/);
|
||||
assert.match(adminApp, /已释放,可重新领取/);
|
||||
assert.match(adminApp, /role="alertdialog"/);
|
||||
});
|
||||
@@ -75,6 +75,8 @@ test("organizes distribution and recovery by task and imports the verified Feish
|
||||
assert.match(adminApp, /选择一个数据回收任务/);
|
||||
assert.match(adminApp, /读取表格/);
|
||||
assert.match(adminApp, /当前仅展示/);
|
||||
assert.match(adminApp, /release_distribution/);
|
||||
assert.match(adminApp, /确认释放这篇笔记/);
|
||||
assert.equal(snapshot.sheetId, "954953");
|
||||
assert.equal(snapshot.rows.length, 61);
|
||||
assert.equal(
|
||||
@@ -229,6 +231,11 @@ test("supports task collection schedules and latest public metrics", async () =>
|
||||
}
|
||||
assert.match(adminApp, /选择开始日期与采集日/);
|
||||
assert.match(adminApp, /第1天到第7天/);
|
||||
assert.match(adminApp, /SortableRecoveryHeader/);
|
||||
assert.match(adminApp, /sortRecoveryDistributions/);
|
||||
assert.match(adminApp, /aria-sort=/);
|
||||
assert.match(adminApp, /direction === "asc" \? "↑" : "↓"/);
|
||||
assert.match(adminApp, /sortWithNullsLast/);
|
||||
assert.match(adminApp, /内容 \/ 发布账号/);
|
||||
assert.match(adminApp, /recovery-title-link/);
|
||||
assert.match(adminApp, /打开小红书笔记/);
|
||||
@@ -252,10 +259,11 @@ test("supports task collection schedules and latest public metrics", async () =>
|
||||
assert.match(collectionService, /runDueScheduledCollections/);
|
||||
assert.match(collectionService, /retryFailedCollections/);
|
||||
assert.match(collectionService, /exposure IS NOT NULL AND views IS NOT NULL/);
|
||||
assert.match(collectionService, /等待第\$\{scheduleDay\}天 10:00自动采集/);
|
||||
assert.match(collectionService, /等待第\$\{scheduleDay\}天 09:00自动采集/);
|
||||
assert.match(collectionService, /isCollectionScheduleDue/);
|
||||
assert.doesNotMatch(collectionService, /latestDueSchedule/);
|
||||
assert.match(collectionService, /自动追采/);
|
||||
assert.match(scheduler, /0 10 \* \* \*/);
|
||||
assert.match(scheduler, /0 9 \* \* \*/);
|
||||
assert.match(scheduler, /backfillAccountProfiles/);
|
||||
assert.match(scheduler, /Asia\/Shanghai/);
|
||||
assert.match(bootstrapRoute, /backfillAccountProfiles/);
|
||||
|
||||
Reference in New Issue
Block a user