test: 测试按行为命名、server 测试统一到 test/、PG 测试分组并行
- 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]>
This commit is contained in:
1 parent
b343dddbf6
commit
b61335eaf6
75 files changed
+159
-115
No files matched your search
@@ -0,0 +1,129 @@
|
||||
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=/);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user