test(subscriptions): overview, coverage matrix, auto-fill and form compatibility
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
410d7fd636
commit
124d6428b1
2 files changed
+785
No files matched your search
@@ -0,0 +1,120 @@
|
||||
import { checkFillBudget, coverageRow, resolveAutoFill, subscriptionGroup } from "@umt/domain";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// W9-E 纯函数:总览分组顺序、矩阵一行的格状态、自动补全取值、补全预算。
|
||||
|
||||
describe("subscriptionGroup", () => {
|
||||
const base = {
|
||||
status: "active" as const,
|
||||
mediaKind: "series" as const,
|
||||
mode: "continuous" as const,
|
||||
totalsKnown: true,
|
||||
anyProgress: false,
|
||||
allSlotsComplete: false,
|
||||
};
|
||||
it("按 暂停 → 待定 → 已完成 → 追更中 → 待搜索 判定;cancelled 不进总览", () => {
|
||||
expect(subscriptionGroup({ ...base, status: "paused", totalsKnown: false }).group).toBe("paused");
|
||||
expect(subscriptionGroup({ ...base, status: "pending" }).group).toBe("pending");
|
||||
expect(subscriptionGroup({ ...base, totalsKnown: false, anyProgress: true }).group).toBe("pending");
|
||||
// 电影不看总集数
|
||||
expect(subscriptionGroup({ ...base, mediaKind: "movie", totalsKnown: false }).group).toBe("waiting");
|
||||
expect(subscriptionGroup({ ...base, status: "completed" })).toEqual({
|
||||
group: "completed",
|
||||
awaitingNewSeason: false,
|
||||
});
|
||||
expect(subscriptionGroup({ ...base, anyProgress: true, allSlotsComplete: true })).toEqual({
|
||||
group: "completed",
|
||||
awaitingNewSeason: true,
|
||||
});
|
||||
expect(subscriptionGroup({ ...base, mode: "once", allSlotsComplete: true }).awaitingNewSeason).toBe(false);
|
||||
expect(subscriptionGroup({ ...base, anyProgress: true }).group).toBe("following");
|
||||
expect(subscriptionGroup(base).group).toBe("waiting");
|
||||
expect(subscriptionGroup({ ...base, status: "cancelled" }).group).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("coverageRow", () => {
|
||||
const cols = [1, 2, 3, 4, 5, 6].map((e) => ({
|
||||
unitKey: `s1e${e}`,
|
||||
episode: e,
|
||||
airDate: e === 6 ? "2099-01-01" : null,
|
||||
totalKnown: true,
|
||||
}));
|
||||
it("已入库 > 在途(含整季通配)> 范围外/未播出 > 缺失;可升级与延后标记", () => {
|
||||
const r = coverageRow({
|
||||
columns: [...cols, { unitKey: "s2e1", episode: 1, totalKnown: false }],
|
||||
covered: new Map([
|
||||
["s1e1", { assetId: "a1", quality: { resolution: "2160p", source: "REMUX", label: "2160p REMUX" }, pri: 100 }],
|
||||
["s1e2", { assetId: "a1", pri: 97 }],
|
||||
]),
|
||||
inflight: [
|
||||
{ id: "p1", stage: "downloading", unitKeys: ["s1e3"] },
|
||||
{ id: "p2", stage: "reserved", unitKeys: ["s2*"] },
|
||||
],
|
||||
deferredKeys: ["s1e4"],
|
||||
range: { end: 4 },
|
||||
today: "2026-09-28",
|
||||
upgrade: { enabled: true, targetPri: 100 },
|
||||
});
|
||||
const by = Object.fromEntries(r.cells.map((c) => [c.unitKey, c]));
|
||||
expect(by.s1e1).toMatchObject({ state: "have", assetId: "a1", pri: 100, upgradable: false });
|
||||
expect(by.s1e1!.quality?.label).toBe("2160p REMUX");
|
||||
expect(by.s1e2).toMatchObject({ state: "have", upgradable: true });
|
||||
expect(by.s1e3).toMatchObject({ state: "inflight", planId: "p1", planStage: "downloading" });
|
||||
expect(by.s1e4).toMatchObject({ state: "missing", deferred: true, reason: null });
|
||||
expect(by.s1e5).toMatchObject({ state: "skipped", reason: "out_of_range" });
|
||||
expect(by.s1e6).toMatchObject({ state: "skipped", reason: "out_of_range" });
|
||||
expect(by.s2e1).toMatchObject({ state: "inflight", planId: "p2" });
|
||||
expect(r.summary).toEqual({ have: 2, inflight: 2, missing: 1, skipped: 2 });
|
||||
expect(r.minPri).toBe(97);
|
||||
});
|
||||
it("未播出跳过;总集数未知的缺失标 suspected", () => {
|
||||
const r = coverageRow({
|
||||
columns: [
|
||||
{ unitKey: "s1e1", episode: 1, airDate: "2099-01-01", totalKnown: true },
|
||||
{ unitKey: "s1e2", episode: 2, totalKnown: false },
|
||||
],
|
||||
covered: new Map(),
|
||||
inflight: [],
|
||||
today: "2026-09-28",
|
||||
});
|
||||
expect(r.cells.map((c) => [c.state, c.reason])).toEqual([
|
||||
["skipped", "not_aired"],
|
||||
["missing", "suspected"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("自动补全开关与补全预算", () => {
|
||||
it("订阅显式值优先;inherit 取规则链上最后一个设了 autoFill 的组;都没设为开", () => {
|
||||
expect(resolveAutoFill("off", [{ id: "g", autoFill: true }])).toMatchObject({
|
||||
enabled: false,
|
||||
source: "subscription",
|
||||
});
|
||||
expect(
|
||||
resolveAutoFill("inherit", [
|
||||
{ id: "a", autoFill: true },
|
||||
{ id: "b", autoFill: false },
|
||||
{ id: "c", autoFill: null },
|
||||
]),
|
||||
).toEqual({ enabled: false, mode: "inherit", source: "rule_group", ruleGroupId: "b" });
|
||||
expect(resolveAutoFill(undefined, [{ id: "a", autoFill: null }])).toEqual({
|
||||
enabled: true,
|
||||
mode: "inherit",
|
||||
source: "default",
|
||||
});
|
||||
});
|
||||
it("单元数与字节按日累计;未设维度不限;体积未知只看单元数", () => {
|
||||
expect(checkFillBudget(undefined, { unitsToday: 99, bytesToday: 1e12 }, { units: 5, bytes: 1e9 }).allow).toBe(true);
|
||||
const d = checkFillBudget(
|
||||
{ unitsPerDay: 3, bytesPerDay: 100 },
|
||||
{ unitsToday: 2, bytesToday: 50 },
|
||||
{ units: 2, bytes: 60 },
|
||||
);
|
||||
expect(d.allow).toBe(false);
|
||||
expect(d.violations.map((v) => v.kind)).toEqual(["units", "bytes"]);
|
||||
expect(
|
||||
checkFillBudget({ bytesPerDay: 100 }, { unitsToday: 0, bytesToday: 99 }, { units: 1, bytes: null }).allow,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user