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,153 @@
|
||||
import { ManualAcquisitionRequest } from "@umt/contracts";
|
||||
import { createMockQb, createTempTree, makeTorrent } from "@umt/testkit";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
|
||||
import { submitDownload } from "../../src/modules/acquisition-organize/services/downloads.ts";
|
||||
import { planManualAcquisition } from "../../src/modules/acquisition-organize/services/manual-acquisition.ts";
|
||||
import { createHarness, fakeDisk, pgAvailable, seedBase, seedEntity, seedRelease, seedSlot } from "./harness.ts";
|
||||
|
||||
const hasPg = await pgAvailable();
|
||||
describe.skipIf(!hasPg)("独立手动下载", () => {
|
||||
it("无订阅下载,未知资源确认,同hash沿用原路径", async () => {
|
||||
const qb = await createMockQb({ addVisibleAfterPolls: 0 });
|
||||
const cache = await createTempTree({});
|
||||
const lib = await createTempTree({}, { sentinel: true });
|
||||
const torrents = new Map<string, Uint8Array>();
|
||||
const disk = fakeDisk(10_000_000);
|
||||
disk.setPct(20);
|
||||
const h = await createHarness("manual_acquisition", {
|
||||
diskUsage: disk.fn,
|
||||
torrentSource: async ({ release }) => ({ kind: "file", bytes: torrents.get(release.id)! }),
|
||||
});
|
||||
try {
|
||||
const base = await seedBase(h.db, { libraryPath: lib.root, cachePath: cache.root, qbUrl: qb.url });
|
||||
const entityId = await seedEntity(h.db, { kind: "movie", title: "Manual Movie", year: 2026 });
|
||||
const torrent = makeTorrent({ name: "Manual.Movie.2026.1080p.mkv", length: 300_000 });
|
||||
const relId = await seedRelease(h.db, {
|
||||
siteId: base.siteId,
|
||||
entityId,
|
||||
title: torrent.name,
|
||||
size: 300_000,
|
||||
hash: torrent.hash,
|
||||
});
|
||||
torrents.set(relId, torrent.bytes);
|
||||
const request = (id: string, opts = {}) => ManualAcquisitionRequest.parse({ sourceReleaseId: id, ...opts });
|
||||
const first = await planManualAcquisition(h.ctx, request(relId));
|
||||
expect(first).toMatchObject({ submitted: true, slotId: null });
|
||||
expect(await h.db.selectFrom("subscription").selectAll().execute()).toHaveLength(0);
|
||||
expect(await h.db.selectFrom("acquisition_slot").selectAll().execute()).toHaveLength(0);
|
||||
const sub = await submitDownload(h.d, first.plan!.id);
|
||||
expect(sub.status).toBe("submitted");
|
||||
const rel2 = await seedRelease(h.db, {
|
||||
siteId: base.siteId,
|
||||
entityId,
|
||||
title: "Other.Release.2160p.mkv",
|
||||
size: 300_000,
|
||||
hash: torrent.hash,
|
||||
});
|
||||
torrents.set(rel2, torrent.bytes);
|
||||
const second = await planManualAcquisition(h.ctx, request(rel2, { savePath: `${cache.root}/other` }));
|
||||
const reused = await submitDownload(h.d, second.plan!.id);
|
||||
expect(reused).toMatchObject({ reason: "already_exists", downloadTaskId: sub.downloadTaskId });
|
||||
const dt = await h.db
|
||||
.selectFrom("download_task")
|
||||
.selectAll()
|
||||
.where("id", "=", sub.downloadTaskId!)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(dt.save_path).toBe(cache.root);
|
||||
expect(dt.media_entity_id).toBe(entityId);
|
||||
const unknown = makeTorrent({ name: "Unknown.1080p.mkv", length: 300_000, pieceSeed: 42 });
|
||||
const uid = await seedRelease(h.db, {
|
||||
siteId: base.siteId,
|
||||
entityId,
|
||||
title: unknown.name,
|
||||
size: 300_000,
|
||||
hash: unknown.hash,
|
||||
});
|
||||
await h.db.updateTable("source_release").set({ media_entity_id: null }).where("id", "=", uid).execute();
|
||||
torrents.set(uid, unknown.bytes);
|
||||
expect(await planManualAcquisition(h.ctx, request(uid))).toMatchObject({
|
||||
submitted: false,
|
||||
requiresConfirmation: true,
|
||||
});
|
||||
const confirmed = await planManualAcquisition(h.ctx, request(uid, { allowUnrecognized: true }));
|
||||
const submitted = await submitDownload(h.d, confirmed.plan!.id);
|
||||
expect(submitted.status).toBe("submitted");
|
||||
expect(
|
||||
(
|
||||
await h.db
|
||||
.selectFrom("download_task")
|
||||
.select("media_entity_id")
|
||||
.where("id", "=", submitted.downloadTaskId!)
|
||||
.executeTakeFirstOrThrow()
|
||||
).media_entity_id,
|
||||
).toBeNull();
|
||||
const autoEntity = await seedEntity(h.db, { kind: "movie", title: "Auto Movie", year: 2026 });
|
||||
const slotId = await seedSlot(h.db, { entityId: autoEntity });
|
||||
const autoTorrent = makeTorrent({
|
||||
name: "Auto.Movie.2026.1080p.BluRay.x264-GRP.mkv",
|
||||
length: 300_000,
|
||||
pieceSeed: 100,
|
||||
});
|
||||
const autoRelease = await seedRelease(h.db, {
|
||||
siteId: base.siteId,
|
||||
entityId: autoEntity,
|
||||
title: autoTorrent.name,
|
||||
size: 300_000,
|
||||
hash: autoTorrent.hash,
|
||||
});
|
||||
torrents.set(autoRelease, autoTorrent.bytes);
|
||||
const auto = await evaluateAcquisition(h.d, { sourceReleaseId: autoRelease });
|
||||
expect(auto.plans).toHaveLength(1);
|
||||
const autoTask = await submitDownload(h.d, auto.plans[0]!);
|
||||
const oldAsset = await h.db
|
||||
.insertInto("media_asset")
|
||||
.values({ media_entity_id: autoEntity, kind: "video", size_bytes: 300_000, status: "active" })
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
await h.db
|
||||
.updateTable("download_task")
|
||||
.set({ status: "imported" })
|
||||
.where("id", "=", autoTask.downloadTaskId!)
|
||||
.execute();
|
||||
const unchanged = await planManualAcquisition(h.ctx, request(autoRelease));
|
||||
expect(unchanged.plan!.id).toBe(auto.plans[0]);
|
||||
const wash = await planManualAcquisition(
|
||||
h.ctx,
|
||||
request(autoRelease, { importMode: "replace", replacementAssetIds: [oldAsset.id] }),
|
||||
);
|
||||
const washed = await submitDownload(h.d, wash.plan!.id);
|
||||
expect(washed.downloadTaskId).toBe(autoTask.downloadTaskId);
|
||||
const linked = await h.db
|
||||
.selectFrom("download_task")
|
||||
.selectAll()
|
||||
.where("id", "=", autoTask.downloadTaskId!)
|
||||
.executeTakeFirstOrThrow();
|
||||
expect(linked).toMatchObject({
|
||||
acquisition_plan_id: auto.plans[0],
|
||||
import_plan_id: wash.plan!.id,
|
||||
status: "completed",
|
||||
});
|
||||
expect(
|
||||
(
|
||||
await h.db
|
||||
.selectFrom("acquisition_plan")
|
||||
.select("slot_id")
|
||||
.where("id", "=", auto.plans[0]!)
|
||||
.executeTakeFirstOrThrow()
|
||||
).slot_id,
|
||||
).toBe(slotId);
|
||||
expect(h.engine.enqueued).toContainEqual(
|
||||
expect.objectContaining({
|
||||
task: "verifyAndImport",
|
||||
input: expect.objectContaining({ downloadTaskId: autoTask.downloadTaskId }),
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await h.close();
|
||||
await qb.close();
|
||||
await cache.cleanup();
|
||||
await lib.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user