Files
UHD_Media_Tool/apps/web/app/components/episode-mapping/decision.test.ts
T

154 lines
6.2 KiB
TypeScript

import type { Decision, EpisodeMappingPreview } from "@umt/contracts";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { agentSimulationText, EpisodeDecisionBody, episodeDecisionView, simulationText } from "./decision.tsx";
const ENTITY = "7d3f7f5e-8a8c-4b8e-9f0a-1b2c3d4e5f60";
const decision = (over: Partial<Decision> = {}): Decision =>
({
id: "11111111-1111-4111-8111-111111111111",
kind: "episode_mapping",
status: "open",
severity: "warning",
title: "集数待确认:药屋少女的呢喃 第 1 季 · Lilith-Raws",
summary: "来源 S01E41:集数超出作品季表,也换算不出对应的季集(共 2 条资源)",
subjectType: "media_entity",
subjectId: ENTITY,
dedupeKey: `episode_mapping:${ENTITY}:1:lilith-raws`,
evidence: {
mediaEntityId: ENTITY,
season: 1,
releaseGroup: "Lilith-Raws",
count: 2,
releases: [
{
sourceReleaseId: "a",
title: "[Lilith-Raws] X - 40",
source: { season: 1, episodes: { start: 40, end: 40 } },
},
{
sourceReleaseId: "b",
title: "[Lilith-Raws] X - 41",
source: { season: 1, episodes: { start: 41, end: 41 } },
},
],
suggestion: {
expression: "EP-30",
targetSeason: 2,
samples: [
{
source: { start: 40, end: 40 },
target: { season: 2, episode: 10 },
publishedAt: "x",
airDate: "2025-03-14",
},
],
},
},
options: [
{ key: "confirm_suggestion", label: "确认建议规则", description: "按 EP-30 换算到第 2 季" },
{ key: "ignore_rule", label: "忽略并建忽略规则" },
],
...over,
}) as Decision;
describe("待处理「集数待确认」", () => {
it("从证据取范围、来源季集、建议规则与可用操作", () => {
const v = episodeDecisionView(decision());
expect(v).toMatchObject({ entityId: ENTITY, season: 1, releaseGroup: "Lilith-Raws", count: 2 });
expect(v.releases.map((r) => r.source)).toEqual(["S01E40", "S01E41"]);
expect(v.suggestion?.expression).toBe("EP-30");
expect(v.confirm?.key).toBe("confirm_suggestion");
expect(v.ignoreRule?.key).toBe("ignore_rule");
const none = episodeDecisionView(decision({ options: [], evidence: { mediaEntityId: ENTITY, suggestion: null } }));
expect(none).toMatchObject({ confirm: null, ignoreRule: null, suggestion: null, season: null });
});
it("渲染来源季集、建议规则样本与模拟结果", () => {
const html = renderToStaticMarkup(
createElement(EpisodeDecisionBody, { view: episodeDecisionView(decision()), simulation: "套用后 2 条都对得上" }),
);
expect(html).toContain("第 1 季 · Lilith-Raws");
expect(html).toContain("S01E40");
expect(html).toContain("EP-30");
expect(html).toContain("第 2 季");
expect(html).toContain("S02E10");
expect(html).toContain("套用后 2 条都对得上");
});
it("没有建议时提示到作品页手动建", () => {
const html = renderToStaticMarkup(
createElement(EpisodeDecisionBody, {
view: episodeDecisionView(decision({ evidence: { mediaEntityId: ENTITY, suggestion: null } })),
simulation: null,
}),
);
expect(html).toContain("到作品页手动建");
});
it("模拟结果一句话", () => {
const p = (vals: string[]) =>
({ items: vals.map((validation) => ({ validation })) }) as unknown as EpisodeMappingPreview;
expect(simulationText(p(["pass", "pass"]))).toBe("套用后 2 条都对得上");
expect(simulationText(p(["pass", "fail"]))).toBe("套用后 1 条对得上,1 条仍对不上");
expect(simulationText(p([]))).toBeNull();
});
const agentSuggestion = {
rule: { season: 1, releaseGroup: "Lilith-Raws", kind: "expression", expression: "EP-30", targetSeason: 2 },
reasons: ["40、41 的发布时间对上第 2 季第 10、11 集的播出日期"],
simulation: { samples: 2, resolved: 2, passed: 2, missing: 0, regressions: 0, changed: 2, scanned: 5, ok: true },
samples: [
{
title: "[Lilith-Raws] X - 40",
source: { season: 1, episodes: { start: 40, end: 40 } },
after: { method: "rule", canonical: { season: 2, episodes: { start: 10, end: 10 } }, special: null },
},
],
runId: null,
at: "2026-10-03T00:00:00Z",
};
const withAgent = () =>
decision({
evidence: { ...decision().evidence, agentSuggestion },
options: [
{
key: "confirm_agent_suggestion",
label: "确认智能体建议",
description: "按 EP-30 换算到第 2 季,建规则前按当前资源再模拟一次,通过才写入并重算",
},
...decision().options,
],
});
it("智能体建议:取规则、理由、模拟结果与「确认智能体建议」", () => {
const v = episodeDecisionView(withAgent());
expect(v.agentSuggestion?.rule.expression).toBe("EP-30");
expect(v.agentSuggestion?.reasons).toHaveLength(1);
expect(v.confirmAgent?.key).toBe("confirm_agent_suggestion");
expect(v.confirm?.key).toBe("confirm_suggestion");
// 没有建议的事项不出这一块
expect(episodeDecisionView(decision())).toMatchObject({ agentSuggestion: null, confirmAgent: null });
});
it("渲染智能体建议:规则、作用域、理由、样本套用后的季集与模拟结果", () => {
const html = renderToStaticMarkup(
createElement(EpisodeDecisionBody, { view: episodeDecisionView(withAgent()), simulation: null }),
);
expect(html).toContain("智能体建议");
expect(html).toContain("偏移表达式");
expect(html).toContain("EP-30 → 第 2 季");
expect(html).toContain("对上第 2 季第 10、11 集的播出日期");
expect(html).toContain("S01E40 → S02E10");
expect(html).toContain("模拟:这 2 条都对得上,不影响已对上的资源");
});
it("智能体建议的模拟结果一句话", () => {
const s = agentSuggestion.simulation;
expect(agentSimulationText({ ...s, resolved: 1, regressions: 2 })).toBe(
"模拟:2 条里 1 条对得上,会影响 2 条已对上的资源",
);
});
});