Merge branch 'qb1'
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
commit
8e47d8715b
36 files changed
+2053
-89
No files matched your search
@@ -188,6 +188,66 @@ describe.skipIf(!hasPg)("W2-A 管理 API(真实 PG)", () => {
|
||||
noLeak("qb-secret-pass");
|
||||
});
|
||||
|
||||
it("downloader_instance 认证方式三选一:API Key(不登录)/ 无认证(内网白名单)/ 账密;切换校验与凭证清理", async () => {
|
||||
const k52 = await createMockQb({ version: "5.2.4", apiKey: "qbt_live_key" });
|
||||
const open = await createMockQb({ version: "5.2.4", bypassAuth: true });
|
||||
try {
|
||||
expect(
|
||||
(await admin("POST", "/downloader-instances", { name: "k0", baseUrl: k52.url, authKind: "api_key" })).status,
|
||||
).toBe(422);
|
||||
expect(
|
||||
(await admin("POST", "/downloader-instances", { name: "p0", baseUrl: k52.url, authKind: "password" })).status,
|
||||
).toBe(422);
|
||||
const k = (
|
||||
await json(
|
||||
await admin("POST", "/downloader-instances", {
|
||||
name: "k52",
|
||||
baseUrl: k52.url,
|
||||
authKind: "api_key",
|
||||
credential: "qbt_live_key",
|
||||
}),
|
||||
)
|
||||
).data;
|
||||
expect(k.settings.authKind).toBe("api_key");
|
||||
expect(JSON.stringify(k)).not.toContain("qbt_live_key");
|
||||
expect((await json(await admin("POST", `/downloader-instances/${k.id}/test`))).data.ok).toBe(true);
|
||||
expect(k52.callsTo("auth/login")).toHaveLength(0);
|
||||
await admin("PATCH", `/downloader-instances/${k.id}`, { credential: "qbt_wrong" });
|
||||
const bad = (await json(await admin("POST", `/downloader-instances/${k.id}/test`))).data;
|
||||
expect(bad.ok).toBe(false);
|
||||
expect(bad.message).toContain("API Key");
|
||||
|
||||
const n = (
|
||||
await json(
|
||||
await admin("POST", "/downloader-instances", { name: "open", baseUrl: open.url, authKind: "none" }),
|
||||
)
|
||||
).data;
|
||||
expect(n.secretRef).toBeNull();
|
||||
expect((await json(await admin("POST", `/downloader-instances/${n.id}/test`))).data.ok).toBe(true);
|
||||
expect(open.callsTo("auth/login")).toHaveLength(0);
|
||||
|
||||
// 切到无认证:凭证删除;无认证连需要认证的实例 → ok=false
|
||||
const sw = (await json(await admin("PATCH", `/downloader-instances/${k.id}`, { authKind: "none" }))).data;
|
||||
expect(sw.secretRef).toBeNull();
|
||||
await expect(h.ctx.secrets.resolve(k.secretRef)).rejects.toThrow(/不存在/);
|
||||
expect((await json(await admin("POST", `/downloader-instances/${k.id}/test`))).data.ok).toBe(false);
|
||||
// 再切回 API Key 必须带 Key
|
||||
expect((await admin("PATCH", `/downloader-instances/${k.id}`, { authKind: "api_key" })).status).toBe(422);
|
||||
const back = (
|
||||
await json(
|
||||
await admin("PATCH", `/downloader-instances/${k.id}`, { authKind: "api_key", credential: "qbt_live_key" }),
|
||||
)
|
||||
).data;
|
||||
expect(back.secretRef).toBeTruthy();
|
||||
expect((await json(await admin("POST", `/downloader-instances/${k.id}/test`))).data.ok).toBe(true);
|
||||
for (const id of [k.id, n.id]) await admin("DELETE", `/downloader-instances/${id}`);
|
||||
noLeak("qbt_live_key");
|
||||
} finally {
|
||||
await k52.close();
|
||||
await open.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("media_server_instance:无 key 时 test 报未配置;有 key 时 ping 成功", async () => {
|
||||
const a = (await json(await admin("POST", "/media-server-instances", { name: "emby-nokey", baseUrl: emby.url })))
|
||||
.data;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { sql } from "@umt/db";
|
||||
import { normalizeTitle } from "@umt/domain";
|
||||
import { normalizeTitle, parseReleasePath } from "@umt/domain";
|
||||
import { createMockTmdb, type MockTmdb, TMDB_FIXTURES, type TmdbFixture } from "@umt/testkit";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { down, up } from "../../../../packages/db/migrations/0047_identity_pool.ts";
|
||||
import { resolveRelease } from "../../src/modules/sources-identity/service/resolve.ts";
|
||||
import { identifyParsed, resolveRelease } from "../../src/modules/sources-identity/service/resolve.ts";
|
||||
import { createHarness, type Harness, json, pgAvailable, seedSite } from "./harness.ts";
|
||||
|
||||
// DEC-1 惰性识别(对照 MP:RSS / 站点资源只在匹配订阅或用户搜索时识别,识别失败不告警)与待识别池治理。
|
||||
@@ -37,6 +37,8 @@ describe.skipIf(!pg)("DEC-1 惰性识别 + 待识别池", () => {
|
||||
twin(112),
|
||||
twin(211, "Nightfall", "夜幕", "1995-03-01"),
|
||||
twin(212, "Nightfall", "夜幕", "1995-03-01"),
|
||||
twin(311, "Harborlight", "港灯", "2003-05-01"),
|
||||
twin(312, "Harborlight", "港灯", "2003-05-01"),
|
||||
],
|
||||
});
|
||||
h = await createHarness("lazyid", { tmdbUrl: tmdb.url });
|
||||
@@ -271,6 +273,44 @@ describe.skipIf(!pg)("DEC-1 惰性识别 + 待识别池", () => {
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(p).toMatchObject({ status: "archived", next_review_at: null });
|
||||
});
|
||||
|
||||
it("QB-1 接管任务按文件名识别(trigger=takeover):不需要本地别名也查 TMDB,auto 建档;歧义只返回候选,不进池不开待处理", async () => {
|
||||
const calls = tmdbCalls();
|
||||
const parsed = parseReleasePath(
|
||||
"Dune.Part.Two.2024.2160p.UHD.BluRay.x265-GRP/Dune.Part.Two.2024.2160p.UHD.BluRay.x265-GRP.mkv",
|
||||
);
|
||||
const r = await identifyParsed(h.rt, parsed, { trigger: "takeover" });
|
||||
expect(tmdbCalls()).toBeGreaterThan(calls);
|
||||
expect(r.outcome).toBe("auto");
|
||||
expect(r.mediaEntityId).toBeTruthy();
|
||||
const ent = await h.db
|
||||
.selectFrom("media_entity")
|
||||
.selectAll()
|
||||
.where("id", "=", r.mediaEntityId!)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(ent.year).toBe(2024);
|
||||
// 同名两部 → 歧义:只给候选
|
||||
const pool = await h.db
|
||||
.selectFrom("identity_candidate")
|
||||
.select((eb) => eb.fn.countAll().as("n"))
|
||||
.executeTakeFirstOrThrow();
|
||||
const amb = await identifyParsed(h.rt, parseReleasePath("Harborlight.2003.1080p.BluRay.mkv"), {
|
||||
trigger: "takeover",
|
||||
});
|
||||
expect(amb.outcome).toBe("ambiguous");
|
||||
expect(amb.candidates.map((c) => c.externalId).sort()).toEqual(["311", "312"]);
|
||||
expect(amb.mediaEntityId).toBeNull();
|
||||
const pool2 = await h.db
|
||||
.selectFrom("identity_candidate")
|
||||
.select((eb) => eb.fn.countAll().as("n"))
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(pool2.n).toBe(pool.n);
|
||||
// 默认 observed 触发:不相关资源不查 TMDB(惰性识别不变)
|
||||
const before = tmdbCalls();
|
||||
const lazy = await identifyParsed(h.rt, parseReleasePath("Zqxv.Kxlmn.2011.720p.mkv"), { trigger: "observed" });
|
||||
expect(lazy.outcome).toBe("no_match");
|
||||
expect(tmdbCalls()).toBe(before);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!pg)("迁移 0047:存量 identity_unresolved 清理", () => {
|
||||
|
||||
Reference in new issue
Block a user