- server 的 src/modules/*/__tests__ 挪到 test/<模块>/;e2e 去掉 eN- 前缀, w5a2 / v2 / search2 / *-dh1 / *-md1 / *-md2、web 的 agui3 / search2、domain 的 id2 / id3 改为按行为命名 - vitest 分三组:unit、pg(真实 PG + 假引擎,文件并行,最多 6 个)、serial(真实引擎 / worker / e2e,串行); pg 组用例超时放宽到 60s(迁移类用例并行时排队等 schema 锁)。server 测试 615s → 约 350s - CLAUDE.md 补测试约定;待办补 harness 去重与 continuous-series 偶发超时(疑似 Slot 评估竞争) - ADR-004 补充:维持 Deno,记录实际只用到 --allow-run 白名单 Co-Authored-By: Claude Opus 5.5 <[email protected]>
130 lines
5.5 KiB
TypeScript
130 lines
5.5 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import type { MediaServerInventory, WorkInventory } from "@umt/contracts";
|
|
import { describe, expect, it } from "vitest";
|
|
import { parseWorkRef, type ReleaseFilterState, toApiFilters } from "./release-results.tsx";
|
|
import { resolvePick } from "./site-picker-dialog.tsx";
|
|
import { inventoryBadge, mediaServerSummary, releaseSearchPath, slotLine, workHref, workRef } from "./work-card.tsx";
|
|
|
|
// SEARCH-2:作品 → 详情 / 搜资源的地址,站点选择解析,资源查询参数,订阅入口只在作品详情页。
|
|
|
|
const inv = (p: Partial<WorkInventory>): WorkInventory => ({
|
|
state: "not_collected",
|
|
subscribed: false,
|
|
versionCount: 0,
|
|
downloadingCount: 0,
|
|
slotTotal: 0,
|
|
slotSatisfied: 0,
|
|
missing: [],
|
|
...p,
|
|
});
|
|
const ID = "11111111-1111-4111-8111-111111111111";
|
|
|
|
describe("作品地址", () => {
|
|
it("本地作品进媒体库详情;仅 TMDB 的进 TMDB 详情", () => {
|
|
expect(workHref({ localEntityId: ID, tmdb: { entityType: "movie", externalId: "1" } })).toBe(
|
|
`/console/library/${ID}`,
|
|
);
|
|
expect(workHref({ localEntityId: null, tmdb: { entityType: "tv", externalId: "1399" } })).toBe(
|
|
"/console/media/tmdb/tv/1399",
|
|
);
|
|
});
|
|
|
|
it("搜资源引用与第二阶段地址可以往返", () => {
|
|
const ref = workRef({ localEntityId: null, tmdb: { entityType: "movie", externalId: "157336" } });
|
|
expect(ref).toBe("tmdb:movie:157336");
|
|
expect(parseWorkRef(ref!)).toEqual({ tmdb: "movie:157336" });
|
|
expect(parseWorkRef(`local:${ID}`)).toEqual({ entityId: ID });
|
|
expect(parseWorkRef("q:Dune 2024")).toEqual({ q: "Dune 2024" });
|
|
expect(parseWorkRef("nope")).toBeNull();
|
|
const url = new URL(releaseSearchPath({ title: "星际穿越", ref: ref! }, ["a", "b"]), "http://x");
|
|
expect(url.pathname).toBe("/console/search");
|
|
expect(Object.fromEntries(url.searchParams)).toEqual({ q: "星际穿越", work: ref, t: "星际穿越", in: "a,b" });
|
|
});
|
|
});
|
|
|
|
describe("站点选择", () => {
|
|
const sites = [
|
|
{ id: "a", enabled: true, searchDefault: true },
|
|
{ id: "b", enabled: true, searchDefault: false },
|
|
{ id: "c", enabled: false, searchDefault: true },
|
|
];
|
|
it("默认 = 启用且默认参与;全部 = 全部启用;指定 = 选中里仍启用的", () => {
|
|
expect(resolvePick({ mode: "default", siteIds: [] }, sites)).toEqual(["a"]);
|
|
expect(resolvePick({ mode: "all", siteIds: [] }, sites)).toEqual(["a", "b"]);
|
|
expect(resolvePick({ mode: "custom", siteIds: ["b", "c"] }, sites)).toEqual(["b"]);
|
|
});
|
|
|
|
it("资源查询:选中的站点进 sites,站点分面只进 siteFilter;按作品搜不带 q", () => {
|
|
const f: ReleaseFilterState = {
|
|
sites: "b",
|
|
resolution: "",
|
|
source: "",
|
|
minGb: "",
|
|
maxGb: "",
|
|
free: "",
|
|
hr: "",
|
|
sort: "",
|
|
order: "",
|
|
};
|
|
expect(toApiFilters({ tmdb: "movie:1" }, f, "a,b")).toMatchObject({
|
|
tmdb: "movie:1",
|
|
sites: "a,b",
|
|
siteFilter: "b",
|
|
});
|
|
expect(toApiFilters({ tmdb: "movie:1" }, { ...f, sites: "" }, "")).not.toHaveProperty("q");
|
|
// 快速筛选进 qf(同维度逗号分隔,值编码)
|
|
expect(toApiFilters({ q: "x" }, f, "", { resolution: ["2160p", "1080p"], promotion: ["50%"], hdr: [] }).qf).toBe(
|
|
"promotion:50%25;resolution:2160p,1080p",
|
|
);
|
|
expect(toApiFilters({ q: "x" }, f, "", {}).qf).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("库内状态文案", () => {
|
|
it("未收录 / 已建档 / 已收录 / 正在下载;收录版本满足与缺失", () => {
|
|
expect(inventoryBadge(inv({})).label).toBe("不在媒体库");
|
|
expect(inventoryBadge(inv({ state: "tracked" })).label).toBe("在媒体库 · 未下载");
|
|
expect(inventoryBadge(inv({ state: "owned", versionCount: 2 })).label).toBe("已入库 · 2 个版本");
|
|
expect(inventoryBadge(inv({ state: "downloading", downloadingCount: 1 })).label).toBe("正在下载 1 个");
|
|
expect(
|
|
slotLine(inv({ state: "owned", slotTotal: 2, slotSatisfied: 1, missing: [{ slotId: ID, name: "2160p" }] })),
|
|
).toBe("收录版本满足 1/2 · 缺「2160p」");
|
|
expect(slotLine(inv({}))).toBeNull();
|
|
});
|
|
|
|
it("Emby:任一实例有 = 已有;全部没有 = 没有;有失败 = 未知(不等于缺失)", () => {
|
|
const ms = (...st: ("present" | "not_found" | "unknown")[]): MediaServerInventory => ({
|
|
mediaEntityId: null,
|
|
partial: false,
|
|
instances: st.map((status, i) => ({
|
|
instanceId: `${ID.slice(0, -1)}${i}`,
|
|
name: `E${i}`,
|
|
status,
|
|
reason: null,
|
|
items: [],
|
|
observedAt: "2026-09-29T00:00:00.000Z",
|
|
})),
|
|
});
|
|
expect(mediaServerSummary(ms())).toBeNull();
|
|
expect(mediaServerSummary(ms("present"))?.label).toBe("Emby 已有");
|
|
expect(mediaServerSummary(ms("not_found", "not_found"))?.label).toBe("Emby 没有");
|
|
expect(mediaServerSummary(ms("not_found", "unknown"))?.label).toBe("Emby 未知");
|
|
});
|
|
});
|
|
|
|
describe("订阅入口位置", () => {
|
|
const src = (rel: string) => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), "utf8");
|
|
it("搜索结果与 ⌘K 面板不能直接订阅", () => {
|
|
for (const f of ["./command-palette.tsx", "./search-sections.tsx", "./work-card.tsx"]) {
|
|
const s = src(f);
|
|
expect(s, f).not.toMatch(/useCreateSubscription|SubscriptionFormDialog|useSubscribeTmdb/);
|
|
}
|
|
});
|
|
it("作品详情页(本地 / 仅 TMDB)有订阅入口", () => {
|
|
for (const f of ["../../routes/console/library.$id.tsx", "../../routes/console/media.tmdb.$type.$id.tsx"]) {
|
|
expect(src(f), f).toMatch(/<SubscriptionFormDialog[\s\S]*initialTarget=/);
|
|
}
|
|
});
|
|
});
|