fix(trpc): bind ranked top candidate and cover low-confidence fetch
This commit is contained in:
parent
188cf79270
commit
714f51a1a6
|
|
@ -212,6 +212,30 @@ describe("danmakuService.fetch", () => {
|
|||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("多候选低置信:不自动绑,提示手选", async () => {
|
||||
vi.mocked(mediaItemDao.getByIdForUser).mockResolvedValue(mediaItem());
|
||||
vi.mocked(danmakuDocsDao.get).mockResolvedValue(null);
|
||||
vi.mocked(hasOpenCredentials).mockReturnValue(true);
|
||||
vi.mocked(danmakuCacheDao.getValid).mockResolvedValue(null);
|
||||
// 两条同分(score 1.0),lead 0 < 0.15 → highConfidence false
|
||||
vi.mocked(openMatch).mockResolvedValue({
|
||||
ok: true,
|
||||
errorMessage: null,
|
||||
isMatched: true,
|
||||
candidates: [
|
||||
{ episodeId: 11, animeId: 1, animeTitle: "T", episodeTitle: "E", imageUrl: null },
|
||||
{ episodeId: 22, animeId: 2, animeTitle: "T2", episodeTitle: "E2", imageUrl: null },
|
||||
],
|
||||
});
|
||||
const r = await danmakuService.fetch("u1", "m1");
|
||||
expect(r.source).toBe("none");
|
||||
expect(r.xml).toBe("");
|
||||
expect(r.message).toContain("手选");
|
||||
expect(mediaItemDao.setDanmakuMatch).not.toHaveBeenCalled();
|
||||
expect(openCommentXml).not.toHaveBeenCalled();
|
||||
expect(danmakuCacheDao.upsert).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("danmakuService.selectMatch", () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { openMatch, hasOpenCredentials } from "@app/danmaku";
|
||||
import { mediaItemDao } from "@app/dao";
|
||||
import type { MediaItem } from "@app/models";
|
||||
import {
|
||||
AUTO_BIND_THRESHOLD,
|
||||
buildSearchQuery,
|
||||
|
|
@ -6,6 +9,30 @@ import {
|
|||
pickDandanEpisode,
|
||||
scoreHit,
|
||||
} from "./scrape.service.js";
|
||||
import { scrapeService } from "./scrape.service.js";
|
||||
|
||||
vi.mock("@app/dao", () => ({
|
||||
mediaItemDao: {
|
||||
getByIdForUser: vi.fn(),
|
||||
updateScrape: vi.fn(),
|
||||
listByPathPrefix: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@app/danmaku", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@app/danmaku")>();
|
||||
return {
|
||||
...actual,
|
||||
openMatch: vi.fn(),
|
||||
openCommentXml: vi.fn(),
|
||||
hasOpenCredentials: vi.fn(),
|
||||
openFetchJson: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./danmaku.service.js", () => ({
|
||||
remoteFileHash: vi.fn().mockResolvedValue("hash"),
|
||||
}));
|
||||
|
||||
describe("parseEpisodeFromFilename", () => {
|
||||
it("S01E02", () => {
|
||||
|
|
@ -89,3 +116,57 @@ describe("scoreHit", () => {
|
|||
expect(s).toBeLessThan(AUTO_BIND_THRESHOLD);
|
||||
});
|
||||
});
|
||||
|
||||
function mediaItem(over: Partial<MediaItem> = {}): MediaItem {
|
||||
return {
|
||||
id: "m1",
|
||||
userId: "u1",
|
||||
mountId: null,
|
||||
path: "/anime/Show - 01.mkv",
|
||||
rawName: "Show - 01.mkv",
|
||||
title: "Show",
|
||||
size: 1024,
|
||||
mime: null,
|
||||
bangumiId: null,
|
||||
epNumber: 1,
|
||||
scrapeStatus: "pending",
|
||||
scrapedAt: null,
|
||||
posterUrl: null,
|
||||
dandanplayEpisodeId: null,
|
||||
matchedHash: null,
|
||||
danmakuMatchSource: "none",
|
||||
scannedAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
describe("scrapeService.matchDandanplay 绑定一致性", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(hasOpenCredentials).mockReturnValue(true);
|
||||
vi.mocked(openMatch).mockResolvedValue({
|
||||
ok: true,
|
||||
errorMessage: null,
|
||||
isMatched: true,
|
||||
candidates: [
|
||||
// score 0:无 animeTitle/episodeTitle → rankCandidates 会排到末尾
|
||||
{ episodeId: 11, animeId: 1, animeTitle: null, episodeTitle: null, imageUrl: null },
|
||||
// score 1.0 → ranked 第一名
|
||||
{ episodeId: 22, animeId: 2, animeTitle: "T", episodeTitle: "E", imageUrl: null },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("绑定 ranked.candidates[0],且与返回 candidates[0] 一致", async () => {
|
||||
vi.mocked(mediaItemDao.getByIdForUser).mockResolvedValue(mediaItem());
|
||||
const r = await scrapeService.matchDandanplay("u1", "m1");
|
||||
expect(r.matched).toBe(true);
|
||||
expect(r.candidates[0]?.episodeId).toBe(22);
|
||||
expect(mediaItemDao.updateScrape).toHaveBeenCalledWith(
|
||||
"m1",
|
||||
"u1",
|
||||
expect.objectContaining({ dandanplayEpisodeId: 22 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ export const scrapeService = {
|
|||
};
|
||||
}
|
||||
const ranked = rankCandidates(outcome.candidates);
|
||||
const best = outcome.candidates[0];
|
||||
const best = ranked.candidates[0];
|
||||
if (best) {
|
||||
await mediaItemDao.updateScrape(mediaItemId, userId, {
|
||||
dandanplayEpisodeId: best.episodeId,
|
||||
|
|
|
|||
Loading…
Reference in New Issue