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:
rootandClaude Opus 5.5 committed 2026-10-02 16:54:04 +08:00
1 parent b343dddbf6
commit b61335eaf6
75 files changed
+159 -115

No files matched your search

+8
View File
@@ -27,6 +27,14 @@ deno task release # 测试环境发布,见下
- 界面文案守 `docs/design/glossary.md` 的术语与标点(`deno task check:copy`);状态色只表示状态(`StatusBadge` 带圆点,别拿来显示数量)。 - 界面文案守 `docs/design/glossary.md` 的术语与标点(`deno task check:copy`);状态色只表示状态(`StatusBadge` 带圆点,别拿来显示数量)。
- 主工作区不留常驻 dev 进程(`--watch` 会改写 `deno.lock`);构建前确认 `deno.lock` 没被改。 - 主工作区不留常驻 dev 进程(`--watch` 会改写 `deno.lock`);构建前确认 `deno.lock` 没被改。
## 测试
- 位置:server 的测试统一放 `apps/server/test/<模块>/`(不再用 `src/**/__tests__`);web 与 adapters 测试和源码放同目录 `*.test.ts`;其它包放 `test/`。
- 命名按测的行为(`crash-recovery.test.ts`、`two-stage-search.test.ts`),不要用任务编号(`e4-`、`w5a2`、`-md1`)。
- server 分三组(`apps/server/vitest.config.ts`):`unit` 不碰 PG;`pg` 真实 PG + 假引擎,文件并行;`serial` 起真实引擎 / worker / 子进程的集成与 e2e,串行。新文件缺省进 `pg`,起真实 worker 的必须加进 `SERIAL`。
- 每个 PG 测试文件用自己的 schema;等待异步结果用轮询到条件,不写固定 sleep。
- 真机测试用 `*.real.test.ts`,靠环境变量开启,缺省跳过。
## 文档放哪 ## 文档放哪
- 正在推进的计划与待办:`docs/plan/README.md`;做完的计划、验收、发布记录直接删除(git 历史可查),不留归档。 - 正在推进的计划与待办:`docs/plan/README.md`;做完的计划、验收、发布记录直接删除(git 历史可查),不留归档。
@@ -1,10 +1,10 @@
import { sql } from "@umt/db"; import { sql } from "@umt/db";
import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit"; import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { createSchedulesModule } from "../../schedules/index.ts"; import { createSchedulesModule } from "../../src/modules/schedules/index.ts";
import { evaluateAcquisition } from "../services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
import { submitDownload, syncDownloads } from "../services/downloads.ts"; import { submitDownload, syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { executeRecord, planImport } from "../services/organize.ts"; import { executeRecord, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { import {
createHarness, createHarness,
fakeDisk, fakeDisk,
@@ -2,11 +2,11 @@ import { OpenAPIHono } from "@hono/zod-openapi";
import type { TmdbClient } from "@umt/adapters"; import type { TmdbClient } from "@umt/adapters";
import { DEFAULT_CLASSIFICATION_YAML } from "@umt/domain"; import { DEFAULT_CLASSIFICATION_YAML } from "@umt/domain";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { AuthEnv } from "../../../http/auth.ts"; import type { AuthEnv } from "../../src/http/auth.ts";
import { createSettingsReader } from "../../auth-settings/provided.ts"; import { createSettingsReader } from "../../src/modules/auth-settings/provided.ts";
import { registerSettingsRoutes } from "../../auth-settings/settings-routes.ts"; import { registerSettingsRoutes } from "../../src/modules/auth-settings/settings-routes.ts";
import { SourcesRuntime } from "../../sources-identity/runtime.ts"; import { SourcesRuntime } from "../../src/modules/sources-identity/runtime.ts";
import { classifyEntity } from "../services/classification.ts"; import { classifyEntity } from "../../src/modules/acquisition-organize/services/classification.ts";
import { createHarness, type Harness, pgAvailable, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedEntity } from "./harness.ts";
const hasPg = await pgAvailable(); const hasPg = await pgAvailable();
@@ -294,7 +294,7 @@ describe.skipIf(!hasPg)("DL-3 下载计划并入下载面板(真实 PG)", () =>
}); });
it("审批被拒 / 过期:草案跟着结束,不再算进行中", async () => { it("审批被拒 / 过期:草案跟着结束,不再算进行中", async () => {
const { closeDraftPlan } = await import("../../agent/interaction.ts"); const { closeDraftPlan } = await import("../../src/modules/agent/interaction.ts");
await closeDraftPlan(h.db, { plan_type: "acquisition_plan", plan_id: ids.draft }, "rejected"); await closeDraftPlan(h.db, { plan_type: "acquisition_plan", plan_id: ids.draft }, "rejected");
await closeDraftPlan(h.db, { plan_type: "acquisition_plan", plan_id: ids.agentDraft }, "expired"); await closeDraftPlan(h.db, { plan_type: "acquisition_plan", plan_id: ids.agentDraft }, "expired");
// 不是草案的不动 // 不是草案的不动
@@ -1,6 +1,6 @@
import { createMockQb, type MockQb } from "@umt/testkit"; import { createMockQb, type MockQb } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { syncDownloads } from "../services/downloads.ts"; import { syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { createHarness, type Harness, pgAvailable, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedEntity } from "./harness.ts";
// DL-2 下载面板:进行中 / 已完成分组与计数、每个种子的实时速度 / 剩余时间 / 连接数、下载器总速度、 // DL-2 下载面板:进行中 / 已完成分组与计数、每个种子的实时速度 / 剩余时间 / 连接数、下载器总速度、
@@ -2,8 +2,8 @@ import type { TaskRunView } from "@umt/tasks";
import { createTempTree, type TempTree } from "@umt/testkit"; import { createTempTree, type TempTree } from "@umt/testkit";
import { sql } from "kysely"; import { sql } from "kysely";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { enqueueSweep, enqueueTracked } from "../services/common.ts"; import { enqueueSweep, enqueueTracked } from "../../src/modules/acquisition-organize/services/common.ts";
import { type QueueScrapeInput, queueScrape, runScrapeRecord } from "../services/scrape.ts"; import { type QueueScrapeInput, queueScrape, runScrapeRecord } from "../../src/modules/acquisition-organize/services/scrape.ts";
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
const hasPg = await pgAvailable(); const hasPg = await pgAvailable();
@@ -7,17 +7,17 @@ import { type DB, migrateToLatest } from "@umt/db";
import type { TaskEngine } from "@umt/tasks"; import type { TaskEngine } from "@umt/tasks";
import { Kysely, PostgresDialect, sql } from "kysely"; import { Kysely, PostgresDialect, sql } from "kysely";
import pg from "pg"; import pg from "pg";
import { ConfigSchema } from "../../../config.ts"; import { ConfigSchema } from "../../src/config.ts";
import { createOutboxPort } from "../../../context.ts"; import { createOutboxPort } from "../../src/context.ts";
import { createApp } from "../../../http/app.ts"; import { createApp } from "../../src/http/app.ts";
import { hashApiKey } from "../../../http/auth.ts"; import { hashApiKey } from "../../src/http/auth.ts";
import { EventBus } from "../../../http/sse.ts"; import { EventBus } from "../../src/http/sse.ts";
import { createLogger } from "../../../logger.ts"; import { createLogger } from "../../src/logger.ts";
import { createCapabilityRegistry } from "../../agent/capability-registry.ts"; import { createCapabilityRegistry } from "../../src/modules/agent/capability-registry.ts";
import { createSourcesIdentityModule } from "../../sources-identity/index.ts"; import { createSourcesIdentityModule } from "../../src/modules/sources-identity/index.ts";
import type { ServerContext } from "../../types.ts"; import type { ServerContext } from "../../src/modules/types.ts";
import { type AoOverrides, createAcquisitionOrganizeModule } from "../index.ts"; import { type AoOverrides, createAcquisitionOrganizeModule } from "../../src/modules/acquisition-organize/index.ts";
import type { AoDeps } from "../services/common.ts"; import type { AoDeps } from "../../src/modules/acquisition-organize/services/common.ts";
// 真实 PG(独立 schema)+ mock qB + 临时目录 的测试装配;引擎用记录入队的假实现,测试直接驱动服务函数。 // 真实 PG(独立 schema)+ mock qB + 临时目录 的测试装配;引擎用记录入队的假实现,测试直接驱动服务函数。
@@ -1,7 +1,7 @@
import type { DB } from "@umt/db"; import type { DB } from "@umt/db";
import type { Kysely } from "kysely"; import type { Kysely } from "kysely";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { backfillVersionSpecs } from "../services/version-backfill.ts"; import { backfillVersionSpecs } from "../../src/modules/acquisition-organize/services/version-backfill.ts";
import { createHarness, type Harness, pgAvailable, seedEntity, seedRelease, seedSlot } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedEntity, seedRelease, seedSlot } from "./harness.ts";
// LIBV-1:入库版本 API(每集多个版本、季汇总、单目标比对)与规格回填(dry-run / apply)。 // LIBV-1:入库版本 API(每集多个版本、季汇总、单目标比对)与规格回填(dry-run / apply)。
@@ -1,9 +1,9 @@
import { ManualAcquisitionRequest } from "@umt/contracts"; import { ManualAcquisitionRequest } from "@umt/contracts";
import { createMockQb, createTempTree, makeTorrent } from "@umt/testkit"; import { createMockQb, createTempTree, makeTorrent } from "@umt/testkit";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { evaluateAcquisition } from "../services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
import { submitDownload } from "../services/downloads.ts"; import { submitDownload } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { planManualAcquisition } from "../services/manual-acquisition.ts"; import { planManualAcquisition } from "../../src/modules/acquisition-organize/services/manual-acquisition.ts";
import { createHarness, fakeDisk, pgAvailable, seedBase, seedEntity, seedRelease, seedSlot } from "./harness.ts"; import { createHarness, fakeDisk, pgAvailable, seedBase, seedEntity, seedRelease, seedSlot } from "./harness.ts";
const hasPg = await pgAvailable(); const hasPg = await pgAvailable();
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
import { DEFAULT_NAMING_TEMPLATES } from "@umt/contracts"; import { DEFAULT_NAMING_TEMPLATES } from "@umt/contracts";
import { LEGACY_V1_NAMING, MP_DEFAULT_NAMING, namingCanonicalJson } from "@umt/domain"; import { LEGACY_V1_NAMING, MP_DEFAULT_NAMING, namingCanonicalJson } from "@umt/domain";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { ensureNamingVersion, namingByVersion } from "../services/naming.ts"; import { ensureNamingVersion, namingByVersion } from "../../src/modules/acquisition-organize/services/naming.ts";
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
// W9-C 命名模板:变量表、预览(样本/错误/路径绑定)、三级模板读写与保存前校验、模板版本登记。 // W9-C 命名模板:变量表、预览(样本/错误/路径绑定)、三级模板读写与保存前校验、模板版本登记。
@@ -4,7 +4,7 @@ import { readdir, readFile, rm, utimes, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { createTempTree, type TempTree } from "@umt/testkit"; import { createTempTree, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { executeRecord, type FrozenPlan, planImport } from "../services/organize.ts"; import { executeRecord, type FrozenPlan, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
// W4-E4 执行侧:replace_with_verify 接线(写临时→校验→原子替换,旧文件进回收目录)、写前清 partial、 // W4-E4 执行侧:replace_with_verify 接线(写临时→校验→原子替换,旧文件进回收目录)、写前清 partial、
@@ -4,8 +4,8 @@ import { join } from "node:path";
import { sql } from "@umt/db"; import { sql } from "@umt/db";
import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit"; import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { evaluateAcquisition } from "../services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
import { submitDownload, syncDownloads } from "../services/downloads.ts"; import { submitDownload, syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { import {
createHarness, createHarness,
EXEC_KEY, EXEC_KEY,
@@ -12,9 +12,9 @@ import {
type TempTree, type TempTree,
} from "@umt/testkit"; } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { evaluateAcquisition } from "../services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
import { submitDownload, syncDownloads } from "../services/downloads.ts"; import { submitDownload, syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { executeRecord, type FrozenPlan, planImport } from "../services/organize.ts"; import { executeRecord, type FrozenPlan, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { import {
createHarness, createHarness,
EXEC_KEY, EXEC_KEY,
@@ -4,9 +4,9 @@ import type { TmdbClient } from "@umt/adapters";
import { DEFAULT_SETTINGS, type OrganizeSourceConfig } from "@umt/contracts"; import { DEFAULT_SETTINGS, type OrganizeSourceConfig } from "@umt/contracts";
import { createMockQb, createTempTree, type MockQb, type TempTree } from "@umt/testkit"; import { createMockQb, createTempTree, type MockQb, type TempTree } from "@umt/testkit";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { SourcesRuntime } from "../../sources-identity/runtime.ts"; import { SourcesRuntime } from "../../src/modules/sources-identity/runtime.ts";
import { syncDownloads } from "../services/downloads.ts"; import { syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { executeRecord, planImport } from "../services/organize.ts"; import { executeRecord, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
const hasPg = await pgAvailable(); const hasPg = await pgAvailable();
@@ -5,7 +5,7 @@ import { dirname } from "node:path";
import { DEFAULT_SETTINGS } from "@umt/contracts"; import { DEFAULT_SETTINGS } from "@umt/contracts";
import { createMockQb, createTempTree, type MockQb, type TempTree } from "@umt/testkit"; import { createMockQb, createTempTree, type MockQb, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { executeRecord, planImport } from "../services/organize.ts"; import { executeRecord, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
const hasPg = await pgAvailable(); const hasPg = await pgAvailable();
@@ -1,7 +1,7 @@
import { makeTorrent } from "@umt/testkit"; import { makeTorrent } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { createAcquisitionOrganizeModule } from "../index.ts"; import { createAcquisitionOrganizeModule } from "../../src/modules/acquisition-organize/index.ts";
import { evaluateAcquisition } from "../services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
import { createHarness, fakeDisk, type Harness, pgAvailable, seedBase, seedEntity, seedRelease } from "./harness.ts"; import { createHarness, fakeDisk, type Harness, pgAvailable, seedBase, seedEntity, seedRelease } from "./harness.ts";
// 规则 API:订阅/Slot/策略版本(发布=持续授权、回滚)/偏好标签抑制;新作品收录走已发布策略;outbox 路由。 // 规则 API:订阅/Slot/策略版本(发布=持续授权、回滚)/偏好标签抑制;新作品收录走已发布策略;outbox 路由。
@@ -2,8 +2,8 @@ import { chmod, readdir, readFile, stat, writeFile } from "node:fs/promises";
import { join, relative } from "node:path"; import { join, relative } from "node:path";
import { createTempTree, type TempTree } from "@umt/testkit"; import { createTempTree, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { executeRecord, planImport } from "../services/organize.ts"; import { executeRecord, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { entityScrapeLocations, runScrapeRecord, SCRAPE_TASK, scrapeEntity } from "../services/scrape.ts"; import { entityScrapeLocations, runScrapeRecord, SCRAPE_TASK, scrapeEntity } from "../../src/modules/acquisition-organize/services/scrape.ts";
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
// 刮削(SCR-1 / SCR-2):入库成功后自动对作品目录排 scrapeMedia → 写 NFO 与图片(临时库根);只补缺失 / 全部覆盖; // 刮削(SCR-1 / SCR-2):入库成功后自动对作品目录排 scrapeMedia → 写 NFO 与图片(临时库根);只补缺失 / 全部覆盖;
@@ -1,10 +1,10 @@
import { createQbClient } from "@umt/adapters"; import { createQbClient } from "@umt/adapters";
import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit"; import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { evaluateAcquisition } from "../services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
import { submitDownload, syncDownloads } from "../services/downloads.ts"; import { submitDownload, syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { executeRecord, planImport } from "../services/organize.ts"; import { executeRecord, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { applyEpisodeTotal, subscriptionProgress, sweepDueSlots } from "../services/subscriptions.ts"; import { applyEpisodeTotal, subscriptionProgress, sweepDueSlots } from "../../src/modules/acquisition-organize/services/subscriptions.ts";
import { createHarness, fakeDisk, type Harness, pgAvailable, seedBase, seedEntity, seedRelease } from "./harness.ts"; import { createHarness, fakeDisk, type Harness, pgAvailable, seedBase, seedEntity, seedRelease } from "./harness.ts";
// W5-C 订阅增强集成:Slot×单元多在途 + 忙时延后重评 + 移除释放;季包按缺集选文件(mock qB files/filePrio); // W5-C 订阅增强集成:Slot×单元多在途 + 忙时延后重评 + 移除释放;季包按缺集选文件(mock qB files/filePrio);
@@ -2,10 +2,10 @@ import { existsSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { createMockQb, createTempTree, type MockQb, type TempTree } from "@umt/testkit"; import { createMockQb, createTempTree, type MockQb, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { addAliasIfAbsent } from "../../sources-identity/service/entities.ts"; import { addAliasIfAbsent } from "../../src/modules/sources-identity/service/entities.ts";
import { syncDownloads } from "../services/downloads.ts"; import { syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { executeRecord, planImport } from "../services/organize.ts"; import { executeRecord, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { organizeDecisionHook, pickMainVideo, takeoverParsed } from "../services/takeover.ts"; import { organizeDecisionHook, pickMainVideo, takeoverParsed } from "../../src/modules/acquisition-organize/services/takeover.ts";
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts"; import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
// QB-1:接管任务(用户手动加进 qB、贴管理标签,没有站点资源)完成后按 种子名 + 主视频文件名 识别 → 直接整理; // QB-1:接管任务(用户手动加进 qB、贴管理标签,没有站点资源)完成后按 种子名 + 主视频文件名 识别 → 直接整理;
@@ -4,10 +4,10 @@ import { join } from "node:path";
import { sql } from "@umt/db"; import { sql } from "@umt/db";
import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit"; import { createMockQb, createTempTree, type MockQb, makeTorrent, type TempTree } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { evaluateAcquisition } from "../services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
import { cleanupDownloadCache, submitDownload, syncDownloads } from "../services/downloads.ts"; import { cleanupDownloadCache, submitDownload, syncDownloads } from "../../src/modules/acquisition-organize/services/downloads.ts";
import { executeRecord, planImport } from "../services/organize.ts"; import { executeRecord, planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
import { reconcileLibrary } from "../services/reconcile.ts"; import { reconcileLibrary } from "../../src/modules/acquisition-organize/services/reconcile.ts";
import { import {
createHarness, createHarness,
fakeDisk, fakeDisk,
@@ -3,8 +3,8 @@ import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { MOCK_ROW_H, MOCK_ROW_Y0, MOCK_VIEWPORT } from "@umt/testkit"; import { MOCK_ROW_H, MOCK_ROW_Y0, MOCK_VIEWPORT } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { registerBrowserCapabilities } from "../capabilities.ts"; import { registerBrowserCapabilities } from "../../src/modules/browser/capabilities.ts";
import { createBrowserModule } from "../index.ts"; import { createBrowserModule } from "../../src/modules/browser/index.ts";
import { import {
ACCESS_KEY, ACCESS_KEY,
type BrowserHarness, type BrowserHarness,
@@ -206,7 +206,7 @@ describe.skipIf(!up)("浏览器 HTTP 端点", () => {
it("服务重启:按数据库恢复会话,旧引用失效;上游回收的标签页标 lost 并自动重开", async () => { it("服务重启:按数据库恢复会话,旧引用失效;上游回收的标签页标 lost 并自动重开", async () => {
// 新模块实例 = 进程重启(适配器内存为空) // 新模块实例 = 进程重启(适配器内存为空)
const fresh = createBrowserModule({ stagingDir: h.stagingDir }); const fresh = createBrowserModule({ stagingDir: h.stagingDir });
const reg = (await import("../../agent/capability-registry.ts")).createCapabilityRegistry({ const reg = (await import("../../src/modules/agent/capability-registry.ts")).createCapabilityRegistry({
logger: h.ctx.logger, logger: h.ctx.logger,
db: h.db, db: h.db,
settings: h.ctx.settings, settings: h.ctx.settings,
@@ -1,8 +1,8 @@
import { siteCloudflareState } from "@umt/contracts"; import { siteCloudflareState } from "@umt/contracts";
import { MOCK_CAMOFOX_IDENTITY, type MockCamofoxSite } from "@umt/testkit"; import { MOCK_CAMOFOX_IDENTITY, type MockCamofoxSite } from "@umt/testkit";
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { browserServiceFor } from "../index.ts"; import { browserServiceFor } from "../../src/modules/browser/index.ts";
import { type BrowserService, registerChallengeVision } from "../service.ts"; import { type BrowserService, registerChallengeVision } from "../../src/modules/browser/service.ts";
import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts"; import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts";
// CF-2 浏览器一侧(对 testkit mock camofox 的 Cloudflare 模式): // CF-2 浏览器一侧(对 testkit mock camofox 的 Cloudflare 模式):
@@ -1,5 +1,5 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { readAccountCookie } from "../../sources-identity/service/credentials.ts"; import { readAccountCookie } from "../../src/modules/sources-identity/service/credentials.ts";
import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts"; import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts";
// BR-2 凭据统一(对 testkit mock camofox + mock NexusPHP 登录态判定): // BR-2 凭据统一(对 testkit mock camofox + mock NexusPHP 登录态判定):
@@ -1,8 +1,8 @@
import { MOCK_CAMOFOX_IDENTITY } from "@umt/testkit"; import { MOCK_CAMOFOX_IDENTITY } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { sourcesIdentityModule } from "../../sources-identity/index.ts"; import { sourcesIdentityModule } from "../../src/modules/sources-identity/index.ts";
import { readBrowserUserAgent } from "../../sources-identity/service/user-agent.ts"; import { readBrowserUserAgent } from "../../src/modules/sources-identity/service/user-agent.ts";
import { egressProxy } from "../service.ts"; import { egressProxy } from "../../src/modules/browser/service.ts";
import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts"; import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts";
// BR-4:打开会话前按站点下发出口(站点开代理 = 与 HTTP 同一代理,否则直连);浏览器固定身份写进 browser_provider.identity, // BR-4:打开会话前按站点下发出口(站点开代理 = 与 HTTP 同一代理,否则直连);浏览器固定身份写进 browser_provider.identity,
@@ -1,6 +1,6 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { browserServiceFor } from "../index.ts"; import { browserServiceFor } from "../../src/modules/browser/index.ts";
import { BrowserKeepalive } from "../keepalive.ts"; import { BrowserKeepalive } from "../../src/modules/browser/keepalive.ts";
import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts"; import { type BrowserHarness, createBrowserHarness, type Loose, ORIGIN, pgAvailable } from "./harness.ts";
// AG-4 浏览器会话保活(对 testkit mock camofox):桌面连接 / 人工接管 / 智能体会话正在用的会话定期做一次 // AG-4 浏览器会话保活(对 testkit mock camofox):桌面连接 / 人工接管 / 智能体会话正在用的会话定期做一次
@@ -1,6 +1,6 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import type { ServerContext } from "../../types.ts"; import type { ServerContext } from "../../src/modules/types.ts";
import { BrowserService, BrowserServiceError, ENV_PROVIDER_NAME } from "../service.ts"; import { BrowserService, BrowserServiceError, ENV_PROVIDER_NAME } from "../../src/modules/browser/service.ts";
import { ACCESS_KEY, type BrowserHarness, createBrowserHarness, type Loose, pgAvailable } from "./harness.ts"; import { ACCESS_KEY, type BrowserHarness, createBrowserHarness, type Loose, pgAvailable } from "./harness.ts";
// BR-1:浏览器服务登记 —— 环境变量(BROWSER_PROVIDER_URL / BROWSER_ACCESS_KEY / BROWSER_ENABLED → camofox-env) // BR-1:浏览器服务登记 —— 环境变量(BROWSER_PROVIDER_URL / BROWSER_ACCESS_KEY / BROWSER_ENABLED → camofox-env)
@@ -1,6 +1,6 @@
import { getSiteDefinition } from "@umt/adapters"; import { getSiteDefinition } from "@umt/adapters";
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { resourceIdFromUrl } from "../resource.ts"; import { resourceIdFromUrl } from "../../src/modules/browser/resource.ts";
import { type BrowserHarness, createBrowserHarness, ORIGIN, pgAvailable, READ_KEY } from "./harness.ts"; import { type BrowserHarness, createBrowserHarness, ORIGIN, pgAvailable, READ_KEY } from "./harness.ts";
const def = getSiteDefinition("audiences")!; const def = getSiteDefinition("audiences")!;
@@ -12,24 +12,24 @@ import {
} from "@umt/testkit"; } from "@umt/testkit";
import { Kysely as K, PostgresDialect } from "kysely"; import { Kysely as K, PostgresDialect } from "kysely";
import pg from "pg"; import pg from "pg";
import { withSchemaLock } from "../../../../test/auth-settings/harness.ts"; import { withSchemaLock } from "../auth-settings/harness.ts";
import { DEV_KEY, memoryLogger, memoryStore } from "../../../../test/helpers.ts"; import { DEV_KEY, memoryLogger, memoryStore } from "../helpers.ts";
import { fakeEngine, fakeSecrets, PG_URL, plainSettings } from "../../../../test/sources-identity/harness.ts"; import { fakeEngine, fakeSecrets, PG_URL, plainSettings } from "../sources-identity/harness.ts";
import { createOutboxPort } from "../../../context.ts"; import { createOutboxPort } from "../../src/context.ts";
import { createApp } from "../../../http/app.ts"; import { createApp } from "../../src/http/app.ts";
import { EventBus } from "../../../http/sse.ts"; import { EventBus } from "../../src/http/sse.ts";
import { createAcquisitionOrganizeModule } from "../../acquisition-organize/index.ts"; import { createAcquisitionOrganizeModule } from "../../src/modules/acquisition-organize/index.ts";
import { createCapabilityRegistry } from "../../agent/capability-registry.ts"; import { createCapabilityRegistry } from "../../src/modules/agent/capability-registry.ts";
import { createAgentModule } from "../../agent/index.ts"; import { createAgentModule } from "../../src/modules/agent/index.ts";
import { createSourcesIdentityModule } from "../../sources-identity/index.ts"; import { createSourcesIdentityModule } from "../../src/modules/sources-identity/index.ts";
import type { ServerContext } from "../../types.ts"; import type { ServerContext } from "../../src/modules/types.ts";
import { registerBrowserCapabilities } from "../capabilities.ts"; import { registerBrowserCapabilities } from "../../src/modules/browser/capabilities.ts";
import { createBrowserModule } from "../index.ts"; import { createBrowserModule } from "../../src/modules/browser/index.ts";
import type { BrowserModuleOptions } from "../service.ts"; import type { BrowserModuleOptions } from "../../src/modules/browser/service.ts";
// C1-E 浏览器模块测试装配:独立 PG schema + 真实迁移 + 交互式 mock camofox(观众风格站点)+ 真实能力注册中心。 // C1-E 浏览器模块测试装配:独立 PG schema + 真实迁移 + 交互式 mock camofox(观众风格站点)+ 真实能力注册中心。
export { pgAvailable } from "../../../../test/sources-identity/harness.ts"; export { pgAvailable } from "../sources-identity/harness.ts";
export const ORIGIN = "https://audiences.example"; export const ORIGIN = "https://audiences.example";
export const PASSWORD = "brw-s3cret-login-pw"; export const PASSWORD = "brw-s3cret-login-pw";
@@ -1,7 +1,7 @@
import { OpenAPIHono } from "@hono/zod-openapi"; import { OpenAPIHono } from "@hono/zod-openapi";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { AuthEnv, Principal, SessionVar } from "../../../http/auth.ts"; import type { AuthEnv, Principal, SessionVar } from "../../src/http/auth.ts";
import { ApiException, errorBody } from "../../../http/errors.ts"; import { ApiException, errorBody } from "../../src/http/errors.ts";
import { import {
basicAuth, basicAuth,
type DesktopDeps, type DesktopDeps,
@@ -10,7 +10,7 @@ import {
desktopUpstream, desktopUpstream,
registerDesktopRoutes, registerDesktopRoutes,
sameOrigin, sameOrigin,
} from "../desktop.ts"; } from "../../src/modules/desktop/desktop.ts";
// 桌面模式反代:口令派生与容器 init 脚本一致、角色 → 口令注入、只接受控制台会话、响应头与路径约束。 // 桌面模式反代:口令派生与容器 init 脚本一致、角色 → 口令注入、只接受控制台会话、响应头与路径约束。
@@ -14,7 +14,7 @@ import { createE2E, type E2E, pgAvailable, runEvents, runRow, waitFor } from "./
// 运行(凭证只经环境变量传入,不写进代码与报告): // 运行(凭证只经环境变量传入,不写进代码与报告):
// UMT_EVAL_REAL=1 UMT_EVAL_API_KEY=… UMT_EVAL_PROVIDER=opencode-go UMT_EVAL_MODEL=deepseek-v4.1-flash \ // UMT_EVAL_REAL=1 UMT_EVAL_API_KEY=… UMT_EVAL_PROVIDER=opencode-go UMT_EVAL_MODEL=deepseek-v4.1-flash \
// UMT_EVAL_LEGACY_PROMPT=/tmp/legacy-system-prompt.ts UMT_EVAL_CASES=health,acquire,why UMT_EVAL_OUT=/tmp/eval.json \ // UMT_EVAL_LEGACY_PROMPT=/tmp/legacy-system-prompt.ts UMT_EVAL_CASES=health,acquire,why UMT_EVAL_OUT=/tmp/eval.json \
// DATABASE_URL=… deno run -A npm:vitest run test/e2e/e16-agent-prompt-eval.real.test.ts // DATABASE_URL=… deno run -A npm:vitest run test/e2e/agent-prompt-eval.real.test.ts
// UMT_EVAL_LEGACY_PROMPT 用 `git show main:packages/agent/src/system-prompt.ts > /tmp/legacy-system-prompt.ts` 生成; // UMT_EVAL_LEGACY_PROMPT 用 `git show main:packages/agent/src/system-prompt.ts > /tmp/legacy-system-prompt.ts` 生成;
// UMT_EVAL_PROMPTS=legacy,layered 控制跑哪几组(缺省两组都跑)。 // UMT_EVAL_PROMPTS=legacy,layered 控制跑哪几组(缺省两组都跑)。
@@ -9,7 +9,7 @@ import { createE2E, type E2E, pgAvailable, runEvents, runRow, waitFor } from "./
// - fake 模型按"合格模型"的路线调用能力:断言调用顺序、只读调用都成功、需要批准的被拒绝(不向用户要 ID: // - fake 模型按"合格模型"的路线调用能力:断言调用顺序、只读调用都成功、需要批准的被拒绝(不向用户要 ID:
// download.inspect 的 ID 来自 download.list); // download.inspect 的 ID 来自 download.list);
// - 汇报格式约束:合格汇报(只用工具结果里的数据写)通过 lintReport(无 UUID / 原始字节 / 内部字段,≤8 行,结论先行)。 // - 汇报格式约束:合格汇报(只用工具结果里的数据写)通过 lintReport(无 UUID / 原始字节 / 内部字段,≤8 行,结论先行)。
// 真实模型的前后对比见 e16-agent-prompt-eval.real.test.ts 与 docs/design/agent-prompt-eval.md。 // 真实模型的前后对比见 agent-prompt-eval.real.test.ts 与 docs/design/agent-prompt-eval.md。
const hasPg = await pgAvailable(); const hasPg = await pgAvailable();
File renamed without changes.
+1 -1
View File
@@ -20,7 +20,7 @@ import { createApp } from "../../src/http/app.ts";
import { deriveCsrfToken, hashApiKey, hashSessionToken } from "../../src/http/auth.ts"; import { deriveCsrfToken, hashApiKey, hashSessionToken } from "../../src/http/auth.ts";
import { EventBus } from "../../src/http/sse.ts"; import { EventBus } from "../../src/http/sse.ts";
import { createLogger } from "../../src/logger.ts"; import { createLogger } from "../../src/logger.ts";
import { fakeEngine } from "../../src/modules/acquisition-organize/__tests__/harness.ts"; import { fakeEngine } from "../acquisition-organize/harness.ts";
import { executeRecord, type FrozenPlan } from "../../src/modules/acquisition-organize/services/organize.ts"; import { executeRecord, type FrozenPlan } from "../../src/modules/acquisition-organize/services/organize.ts";
import { runScrapeRecord } from "../../src/modules/acquisition-organize/services/scrape.ts"; import { runScrapeRecord } from "../../src/modules/acquisition-organize/services/scrape.ts";
import { createCapabilityRegistry } from "../../src/modules/agent/capability-registry.ts"; import { createCapabilityRegistry } from "../../src/modules/agent/capability-registry.ts";
@@ -4,7 +4,7 @@ import {
type Harness, type Harness,
pgAvailable, pgAvailable,
seedEntity, seedEntity,
} from "../../src/modules/acquisition-organize/__tests__/harness.ts"; } from "../acquisition-organize/harness.ts";
import { MEDIA_SERVER_LIMITS } from "../../src/modules/acquisition-organize/routes/library.ts"; import { MEDIA_SERVER_LIMITS } from "../../src/modules/acquisition-organize/routes/library.ts";
// C1-D:GET /inventory/media-servers 按实例并行(allSettled)+ 实例内并发上限 + 整实例超时;部分失败 partial=true。 // C1-D:GET /inventory/media-servers 按实例并行(allSettled)+ 实例内并发上限 + 整实例超时;部分失败 partial=true。
+1 -1
View File
@@ -9,7 +9,7 @@ import {
seedEntity, seedEntity,
seedRelease, seedRelease,
seedSlot, seedSlot,
} from "../../src/modules/acquisition-organize/__tests__/harness.ts"; } from "../acquisition-organize/harness.ts";
import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
// W9-D 规则组:关键字表、规则串解析/序列化、CRUD + 修订、抑制记录、试算、默认链、评估接入、旧端点别名。 // W9-D 规则组:关键字表、规则串解析/序列化、CRUD + 修订、抑制记录、试算、默认链、评估接入、旧端点别名。
@@ -10,7 +10,7 @@ import {
seedBase, seedBase,
seedEntity, seedEntity,
seedRelease, seedRelease,
} from "../../src/modules/acquisition-organize/__tests__/harness.ts"; } from "../acquisition-organize/harness.ts";
import { import {
evaluateAcquisition, evaluateAcquisition,
subscriptionSavePath, subscriptionSavePath,
@@ -10,7 +10,7 @@ import {
seedBase, seedBase,
seedEntity, seedEntity,
seedRelease, seedRelease,
} from "../../src/modules/acquisition-organize/__tests__/harness.ts"; } from "../acquisition-organize/harness.ts";
import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts"; import { evaluateAcquisition } from "../../src/modules/acquisition-organize/services/acquisition.ts";
// W9-E 订阅重做后端:总览分组/排序/计数、覆盖矩阵(单集/季包/多版本/在途/临时单元/电影版本)、 // W9-E 订阅重做后端:总览分组/排序/计数、覆盖矩阵(单集/季包/多版本/在途/临时单元/电影版本)、
+35 -9
View File
@@ -1,11 +1,12 @@
import { defineConfig } from "vitest/config"; import { defineConfig } from "vitest/config";
// 两组测试: // 三组测试,按 groupOrder 依次跑:
// unit —— 纯内存、无 PG,文件并行; // unit —— 纯内存、无 PG,文件并行;
// pg —— 真实 PG 的集成/e2e(每文件独立业务 schema + 引擎 schema,真实 worker/后台循环),文件级串行, // pg —— 真实 PG、假引擎(每文件独立 schema),文件并行;
// 并排在 unit 之后(groupOrder)。并发全量跑时多个 e2e 的 worker/轮询/迁移互相抢 CPU 与连接, // serial —— 起真实引擎 / worker / 后台循环 / 子进程的集成与 e2e,文件级串行。并发跑时 worker、轮询、迁移
// 会把"入库→状态推进"这类时序拉长到超时,也会放大竞态;串行让每个文件拿到稳定的时序预算。 // 互相抢 CPU 与连接,会把"入库→状态推进"这类时序拉长到超时,串行让每个文件拿到稳定的时序预算。
// 新测试文件缺省归入 pg(串行,安全);确认不碰 PG 的才加进 UNIT。 // 新测试缺省进 pg;起真实 worker / 引擎的加进 SERIAL;确认不碰 PG 的加进 UNIT。
// 并行文件数受开发库连接数约束(max_connections 100,每个 harness 连接池 5–10):MAX_WORKERS 别随手调大。
const UNIT = [ const UNIT = [
"test/api-registry.test.ts", "test/api-registry.test.ts",
"test/organize-in-place-verify.test.ts", "test/organize-in-place-verify.test.ts",
@@ -22,23 +23,48 @@ const UNIT = [
"test/static.test.ts", "test/static.test.ts",
]; ];
const SERIAL = [
"test/e2e/**/*.test.ts",
"test/auth-settings/admin-api.test.ts",
"test/auth-settings/engine-retry.test.ts",
"test/system/**/*.test.ts",
"test/search/search.test.ts",
"test/search/two-stage-search.test.ts",
"test/worker-lanes.test.ts",
"test/preflight.test.ts",
"test/bootstrap.test.ts",
];
const MAX_WORKERS = 6;
export default defineConfig({ export default defineConfig({
test: { test: {
// 建/删 schema 在一把咨询锁上排队(withSchemaLock),迁移 + 引擎 schema 在负载下可能超过缺省 10s // 建/删 schema 在一把咨询锁上排队(withSchemaLock),迁移 + 引擎 schema 在负载下可能超过缺省 10s
hookTimeout: 120_000, hookTimeout: 120_000,
teardownTimeout: 60_000, teardownTimeout: 60_000,
maxWorkers: MAX_WORKERS,
projects: [ projects: [
{ extends: true, test: { name: "unit", include: UNIT } }, { extends: true, test: { name: "unit", include: UNIT } },
{ {
extends: true, extends: true,
test: { test: {
name: "pg", name: "pg",
include: ["test/**/*.test.ts", "src/**/*.test.ts"], include: ["test/**/*.test.ts"],
exclude: UNIT, exclude: [...UNIT, ...SERIAL],
fileParallelism: false, // 迁移类用例在用例体里建 schema + 迁移,并行时排队等同一把锁,缺省 5s 不够
testTimeout: 60_000,
sequence: { groupOrder: 1 }, sequence: { groupOrder: 1 },
}, },
}, },
{
extends: true,
test: {
name: "serial",
include: SERIAL,
fileParallelism: false,
sequence: { groupOrder: 2 },
},
},
], ],
}, },
}); });
+7
View File
@@ -173,3 +173,10 @@ deno task ci # typecheck(tsc 7.0.2 + deno check)→ biome → vitest
- 约 400 行相对 import 补扩展名,可自动修复。server `main.ts` 约 30 行。2 个崩溃恢复测试的 spawn 参数。db scripts 3 个。logger 约 15 行。web 的 tsconfig 和 package.json 各 1 到 2 行。 - 约 400 行相对 import 补扩展名,可自动修复。server `main.ts` 约 30 行。2 个崩溃恢复测试的 spawn 参数。db scripts 3 个。logger 约 15 行。web 的 tsconfig 和 package.json 各 1 到 2 行。
- 文档:README、plan §1、ADR-001 标记 superseded。 - 文档:README、plan §1、ADR-001 标记 superseded。
- 业务逻辑(domain/contracts/adapters/agent/tasks 的实现)**不用改**。 - 业务逻辑(domain/contracts/adapters/agent/tasks 的实现)**不用改**。
## 补充(2026-10-02):维持 Deno,不迁移
讨论过换 Bun / Node 24,结论是不换。
- 实际权限:生产编译参数为 `--allow-net --allow-env --allow-sys --allow-read --allow-write --allow-run=ffprobe,sha256sum,/app/umt-server`,只有 `--allow-run` 白名单真正起限制,上文"读路径白名单"没有落实;选 Deno 的理由以直接跑 TS、单二进制、集中的 workspace 配置为主。
- 已知摩擦都有固定做法:web 测试用 Node 跑 vitest、编译产物自检兜底漏嵌、`deno.lock` 被常驻进程改写(见 `docs/plan/lessons.md`)。
+2 -2
View File
@@ -31,7 +31,7 @@
## 3. fake 模型评测(e16,CI) ## 3. fake 模型评测(e16,CI)
`apps/server/test/e2e/e16-agent-prompt-eval.test.ts`,测试库数据仿反例现场:下载器「家里的 qB」(mock qB,3 个任务:下载中 / 文件缺失 / 已入库做种)、站点「憨憨」(上次检查正常,账号带原始用户数据)、没有媒体服务器、《沙丘2》(追更中订阅 + 1080p / 2160p 两个收录版本、一条 2160p 资源)、4 条已取消订阅、1 条待处理。 `apps/server/test/e2e/agent-prompt-eval.test.ts`,测试库数据仿反例现场:下载器「家里的 qB」(mock qB,3 个任务:下载中 / 文件缺失 / 已入库做种)、站点「憨憨」(上次检查正常,账号带原始用户数据)、没有媒体服务器、《沙丘2》(追更中订阅 + 1080p / 2160p 两个收录版本、一条 2160p 资源)、4 条已取消订阅、1 条待处理。
| # | 提示 | 载入的 Skill | 能力调用顺序 | 结果 | | # | 提示 | 载入的 Skill | 能力调用顺序 | 结果 |
|---|---|---|---|---| |---|---|---|---|---|
@@ -52,7 +52,7 @@ fake 模型只能证明"按提示的路线走得通、数据够写出合格汇
**本次没有跑,真跑 0 次。** 任务说明里的凭据文件 `.local/admin-credentials` 只有管理员、TMDB、Brave 搜索与 qB 的条目,没有 OpenCode Go 的 key;用户栈里配置的模型服务按约束不能碰。评测脚手架已就绪: **本次没有跑,真跑 0 次。** 任务说明里的凭据文件 `.local/admin-credentials` 只有管理员、TMDB、Brave 搜索与 qB 的条目,没有 OpenCode Go 的 key;用户栈里配置的模型服务按约束不能碰。评测脚手架已就绪:
- `apps/server/test/e2e/e16-agent-prompt-eval.real.test.ts`:同一套测试库数据,"前"用 main 的 `buildSystemPrompt`(`git show main:packages/agent/src/system-prompt.ts`)+ 旧约束 + 不含 `download.list` 的能力集,"后"用分层提示;缺省跑反例那条 + 收录 + 入库排查共 3 条 × 2 组 = 6 次会话(上限 12)。 - `apps/server/test/e2e/agent-prompt-eval.real.test.ts`:同一套测试库数据,"前"用 main 的 `buildSystemPrompt`(`git show main:packages/agent/src/system-prompt.ts`)+ 旧约束 + 不含 `download.list` 的能力集,"后"用分层提示;缺省跑反例那条 + 收录 + 入库排查共 3 条 × 2 组 = 6 次会话(上限 12)。
- 结果写 JSON:每次的能力调用序列(含是否成功)、汇报原文、`lintReport` 结果、行数、token / 费用。 - 结果写 JSON:每次的能力调用序列(含是否成功)、汇报原文、`lintReport` 结果、行数、token / 费用。
- 跑法见 runbook;拿到 key 后执行一次,把结果按下表补进本节: - 跑法见 runbook;拿到 key 后执行一次,把结果按下表补进本节:
+3
View File
@@ -28,9 +28,12 @@
- 任务恢复的三处边界:升级前未结束任务的版本处置(发布前查在途任务版本,没有在途旧任务就不保留旧处理器);业务结果提交、run 关联、事件派发三个时点分别注入失败确认最终状态;引擎查询异常时只记录并跳过本轮,仅在明确查无 run 或终态时补投。 - 任务恢复的三处边界:升级前未结束任务的版本处置(发布前查在途任务版本,没有在途旧任务就不保留旧处理器);业务结果提交、run 关联、事件派发三个时点分别注入失败确认最终状态;引擎查询异常时只记录并跳过本轮,仅在明确查无 run 或终态时补投。
- 生产模块装配的处理器来源断言(防止漏注册的任务静默回显 todo),下次改任务注册时补。 - 生产模块装配的处理器来源断言(防止漏注册的任务静默回显 todo),下次改任务注册时补。
- 测试装配去重:server 6 份 harness(约 1800 行)各自实现建 schema、迁移、假引擎、临时目录,抽公共部分到 `@umt/testkit`,各模块只留自己的装配;下次改到相关 harness 时做。
- 大文件拆分候选,改到相关功能时顺手拆,不单独重构:`packages/contracts/src/api.ts`(~2700 行)、`acquisition-organize/services/organize.ts`(~2600)、`browser/service.ts`(~2400)、`web/app/components/organize-wizard/organize-wizard.tsx`。 - 大文件拆分候选,改到相关功能时顺手拆,不单独重构:`packages/contracts/src/api.ts`(~2700 行)、`acquisition-organize/services/organize.ts`(~2600)、`browser/service.ts`(~2400)、`web/app/components/organize-wizard/organize-wizard.tsx`。
### 业务 ### 业务
- 疑似竞争:同一 Slot 两个版本几乎同时评估时,`evaluateForSlot`(`acquisition.ts`)读在途计划与写计划 / 延后之间没有锁,可能都不被延后。现象:`test/e2e/continuous-series.test.ts`「另一版本延后」在全量跑时偶发等待超时(2026-10-02 并行化后 3 次全量中 1 次,单跑 4 次均过)。先复现确认,再考虑按 Slot 加行锁。
- 与 MP 对照里仍未定的项,见 [对照 §6.4/6.5](../porting/moviepilot-v3-comparison.md#64-用户反馈与后续讨论2026-10-02)。 - 与 MP 对照里仍未定的项,见 [对照 §6.4/6.5](../porting/moviepilot-v3-comparison.md#64-用户反馈与后续讨论2026-10-02)。
- 需求里仍是"部分实现"的:R06 通用 HTTP 失败转浏览器、真实 Cloudflare 挑战验收;R21 缓存阈值里未纳管占用的专项验收;四站与真实下载、媒体服务器的完整验收。 - 需求里仍是"部分实现"的:R06 通用 HTTP 失败转浏览器、真实 Cloudflare 挑战验收;R21 缓存阈值里未纳管占用的专项验收;四站与真实下载、媒体服务器的完整验收。
+1 -1
View File
@@ -68,7 +68,7 @@ MP 是优先采用的业务参考;用户已确认尽量复用其成熟业务
| 先持久接受,再安排执行 | [workflow.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/workflow.py#L116) `TransferQueueService.put`;[queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L1194) `__record_enqueue_failure`;[test_transfer_queue_service.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_queue_service.py#L139) `test_transfer_queue_service_commits_admission_before_failed_enqueue` 使用 SQLite 仓储注入队列失败 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueTracked` 第 134 行;[scrape.ts](../../apps/server/src/modules/acquisition-organize/services/scrape.ts) `queueScrape` 第 152 行 | | 先持久接受,再安排执行 | [workflow.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/workflow.py#L116) `TransferQueueService.put`;[queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L1194) `__record_enqueue_failure`;[test_transfer_queue_service.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_queue_service.py#L139) `test_transfer_queue_service_commits_admission_before_failed_enqueue` 使用 SQLite 仓储注入队列失败 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueTracked` 第 134 行;[scrape.ts](../../apps/server/src/modules/acquisition-organize/services/scrape.ts) `queueScrape` 第 152 行 |
| 查询异常不当作不存在 | [queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L754) `__replay_pending` 第 754 行;[admission.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/transfer/admission.py#L296) `claim_recoverable`;[test_transfer_pending_replay.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_pending_replay.py#L423) `test_replay_survives_db_failure` 断言不执行恢复 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueSweep` 第 159 行起 | | 查询异常不当作不存在 | [queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L754) `__replay_pending` 第 754 行;[admission.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/transfer/admission.py#L296) `claim_recoverable`;[test_transfer_pending_replay.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_pending_replay.py#L423) `test_replay_survives_db_failure` 断言不执行恢复 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueSweep` 第 159 行起 |
| 旧执行迹象保守隔离 | [models.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/models.py#L485) `TransferPlanCheckpoint.from_payload`;[迁移](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/database/versions/e5c7a9b1d3f6_3_0_16.py#L404) `_backfill_pending`;[test_transfer_execution_migration.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_migration.py#L282) `test_upgrade_is_conservative_and_repairs_interrupted_indexes` | [handlers.ts](../../apps/server/src/modules/acquisition-organize/handlers.ts) 第 193 行;[0058](../../packages/db/migrations/0058_scrape_record.ts);[引擎](../../packages/tasks/src/openworkflow-engine.ts) `startWorker` 第 221 行 | | 旧执行迹象保守隔离 | [models.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/models.py#L485) `TransferPlanCheckpoint.from_payload`;[迁移](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/database/versions/e5c7a9b1d3f6_3_0_16.py#L404) `_backfill_pending`;[test_transfer_execution_migration.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_migration.py#L282) `test_upgrade_is_conservative_and_repairs_interrupted_indexes` | [handlers.ts](../../apps/server/src/modules/acquisition-organize/handlers.ts) 第 193 行;[0058](../../packages/db/migrations/0058_scrape_record.ts);[引擎](../../packages/tasks/src/openworkflow-engine.ts) `startWorker` 第 221 行 |
| 步骤恢复先看已有结果和实际副作用 | [execution.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/execution.py#L193) `_DurableTransferStepRunner.run`;[test_transfer_execution_runner.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_runner.py#L148) 成功步骤不重做、未知孤立步骤进入人工复核断言 | [op-ledger.ts](../../packages/tasks/src/op-ledger.ts);[organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts);[E4](../../apps/server/test/e2e/e4-crash-recovery.test.ts)、[tasks 崩溃恢复](../../packages/tasks/test/crash-recovery.test.ts) | | 步骤恢复先看已有结果和实际副作用 | [execution.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/execution.py#L193) `_DurableTransferStepRunner.run`;[test_transfer_execution_runner.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_runner.py#L148) 成功步骤不重做、未知孤立步骤进入人工复核断言 | [op-ledger.ts](../../packages/tasks/src/op-ledger.ts);[organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts);[E4](../../apps/server/test/e2e/crash-recovery.test.ts)、[tasks 崩溃恢复](../../packages/tasks/test/crash-recovery.test.ts) |
| 结果和事件共同提交 | [chain.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/chain.py#L219) `TransactionalChainDurableEventWriter.transfer_result` | [organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts) 核验事务第 1386 行、`emitRecord` 第 1710 行;[outbox-relay.ts](../../packages/tasks/src/outbox-relay.ts) 第 46 行起 | | 结果和事件共同提交 | [chain.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/chain.py#L219) `TransactionalChainDurableEventWriter.transfer_result` | [organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts) 核验事务第 1386 行、`emitRecord` 第 1710 行;[outbox-relay.ts](../../packages/tasks/src/outbox-relay.ts) 第 46 行起 |
| handler 装配风险不可强行类比 | [runtime/tasks.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/runtime/tasks.py) | [context.ts](../../apps/server/src/context.ts) 第 96 行;[modules.test.ts](../../apps/server/test/modules.test.ts) 第 86 行 | | handler 装配风险不可强行类比 | [runtime/tasks.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/runtime/tasks.py) | [context.ts](../../apps/server/src/context.ts) 第 96 行;[modules.test.ts](../../apps/server/test/modules.test.ts) 第 86 行 |
+2 -2
View File
@@ -364,7 +364,7 @@ curl -X POST localhost:4380/api/v1/agent/runs -H "Authorization: Bearer umt-dev-
### 评测 ### 评测
- 汇报核对 `lintReport(text)`(`packages/agent/src/prompt/report-lint.ts`):行数 >8、UUID、hash、7 位以上裸数字(原始字节数)、ISO 时间、内部字段(adapter、snake_case、camelCase)、能力名、Markdown 标题、开头不是结论("检查结果""以下…")、向用户要 ID、说教句。反例原文必须不过,期望样子必须过(`report-lint.test.ts`)。 - 汇报核对 `lintReport(text)`(`packages/agent/src/prompt/report-lint.ts`):行数 >8、UUID、hash、7 位以上裸数字(原始字节数)、ISO 时间、内部字段(adapter、snake_case、camelCase)、能力名、Markdown 标题、开头不是结论("检查结果""以下…")、向用户要 ID、说教句。反例原文必须不过,期望样子必须过(`report-lint.test.ts`)。
- fake 模型:`apps/server/test/e2e/e16-agent-prompt-eval.test.ts`,夹具 `fixtures/agent-eval.ts`(测试库里的一套用户环境 + 8 条固定提示 + 合格模型的调用路线与汇报),随 `deno task test` 跑。 - fake 模型:`apps/server/test/e2e/agent-prompt-eval.test.ts`,夹具 `fixtures/agent-eval.ts`(测试库里的一套用户环境 + 8 条固定提示 + 合格模型的调用路线与汇报),随 `deno task test` 跑。
- 真实模型(手动,不进 CI,每次最多 12 次调用模型会话): - 真实模型(手动,不进 CI,每次最多 12 次调用模型会话):
```sh ```sh
@@ -373,7 +373,7 @@ curl -X POST localhost:4380/api/v1/agent/runs -H "Authorization: Bearer umt-dev-
UMT_EVAL_PROVIDER=opencode-go UMT_EVAL_MODEL=deepseek-v4.1-flash \ UMT_EVAL_PROVIDER=opencode-go UMT_EVAL_MODEL=deepseek-v4.1-flash \
UMT_EVAL_LEGACY_PROMPT=/tmp/legacy-system-prompt.ts UMT_EVAL_CASES=health,acquire,why \ UMT_EVAL_LEGACY_PROMPT=/tmp/legacy-system-prompt.ts UMT_EVAL_CASES=health,acquire,why \
UMT_EVAL_OUT=/tmp/agent-eval.json DATABASE_URL=postgres://… \ UMT_EVAL_OUT=/tmp/agent-eval.json DATABASE_URL=postgres://… \
deno run -A npm:vitest run test/e2e/e16-agent-prompt-eval.real.test.ts deno run -A npm:vitest run test/e2e/agent-prompt-eval.real.test.ts
``` ```
"前"用 main 的 `buildSystemPrompt` + 旧约束,能力集去掉 `download.list`;"后"用分层提示。结果 JSON 每条含能力调用序列、汇报原文、`lintReport` 结果、行数、用量;对比写进 `docs/design/agent-prompt-eval.md`。站点地址是本机关闭端口、下载器是 mock qB,智能体不会请求真实站点。key 只经环境变量传入,不写进代码与报告。 "前"用 main 的 `buildSystemPrompt` + 旧约束,能力集去掉 `download.list`;"后"用分层提示。结果 JSON 每条含能力调用序列、汇报原文、`lintReport` 结果、行数、用量;对比写进 `docs/design/agent-prompt-eval.md`。站点地址是本机关闭端口、下载器是 mock qB,智能体不会请求真实站点。key 只经环境变量传入,不写进代码与报告。
+3 -3
View File
@@ -473,7 +473,7 @@ camofox 容器重启(`docker compose up -d` 重建、OOM、手动 restart)后,
### 10.4 核验 ### 10.4 核验
- 自动化:`apps/server/src/modules/browser/__tests__/browser-reconnect.test.ts`(mock camofox `restart()`:标签页清空、旧 tabId 410、 - 自动化:`apps/server/test/browser/browser-reconnect.test.ts`(mock camofox `restart()`:标签页清空、旧 tabId 410、
实例标识换新、可选丢 profile)——截图自愈并注入 Cookie、回到原地址;实例标识变化 + `reconnect` 幂等;接管中重连保持租约; 实例标识换新、可选丢 profile)——截图自愈并注入 Cookie、回到原地址;实例标识变化 + `reconnect` 幂等;接管中重连保持租约;
失败 503 与退避;前端冒烟"原站视图 → 浏览器重启后自动重连(mock)"。 失败 503 与退避;前端冒烟"原站视图 → 浏览器重启后自动重连(mock)"。
- 真机:未对真实 camofox 容器重启核验。桌面镜像要重建后才有 `GET /umt/instance`(`docker compose build camofox && docker compose up -d camofox`); - 真机:未对真实 camofox 容器重启核验。桌面镜像要重建后才有 `GET /umt/instance`(`docker compose build camofox && docker compose up -d camofox`);
@@ -621,7 +621,7 @@ Camoufox 浏览器包的层都走构建缓存,**不会重新下载 Camoufox(约
仍从本地 `camofox-browser:1.17.0` 复制,不从网上下)。首次启动生成身份,之后 profile 卷里的登录状态照常可用。 仍从本地 `camofox-browser:1.17.0` 复制,不从网上下)。首次启动生成身份,之后 profile 卷里的登录状态照常可用。
核验:`apps/server/test/sources-identity/user-agent.test.ts`(每条出站请求的 UA、优先级、待切换、CF Cookie)、 核验:`apps/server/test/sources-identity/user-agent.test.ts`(每条出站请求的 UA、优先级、待切换、CF Cookie)、
`migration-browser-identity.test.ts`、`apps/server/src/modules/browser/__tests__/browser-identity.test.ts`(出口下发、身份存库)、 `migration-browser-identity.test.ts`、`apps/server/test/browser/browser-identity.test.ts`(出口下发、身份存库)、
`packages/adapters/src/browser/umt-plugin.test.ts`(插件纯函数、适配器接口);真机:本地临时容器(`--network none`,只开容器内本地 `packages/adapters/src/browser/umt-plugin.test.ts`(插件纯函数、适配器接口);真机:本地临时容器(`--network none`,只开容器内本地
页面)核验了 §11.1 表格与 §11.5 出口行为,未对真实站点发请求。 页面)核验了 §11.1 表格与 §11.5 出口行为,未对真实站点发请求。
@@ -658,7 +658,7 @@ camofox 按 **API 调用**判断活动,有三道空闲回收(`docker-compose.yml
→ 按 §10 自愈重连(注入 Cookie、同一会话新开标签页回到原地址)后在新标签页上再保活一次;重连也失败(浏览器服务不可用)只记 warn 日志 → 按 §10 自愈重连(注入 Cookie、同一会话新开标签页回到原地址)后在新标签页上再保活一次;重连也失败(浏览器服务不可用)只记 warn 日志
「浏览器会话保活失败」,会话照常标 `needsReconnect` / `reconnect.status = failed`,下一轮再试。 「浏览器会话保活失败」,会话照常标 `needsReconnect` / `reconnect.status = failed`,下一轮再试。
核验:`apps/server/src/modules/browser/__tests__/browser-keepalive.test.ts`(登记 / 引用计数 / 复查;对 mock camofox:接管期间保活、 核验:`apps/server/test/browser/browser-keepalive.test.ts`(登记 / 引用计数 / 复查;对 mock camofox:接管期间保活、
不记自动化活动、交还后停;桌面连接期间标签页被回收 → 保活时自愈重连;智能体会话结束后停、会话关闭撤销)。真机未核验。 不记自动化活动、交还后停;桌面连接期间标签页被回收 → 保活时自愈重连;智能体会话结束后停、会话关闭撤销)。真机未核验。
## 13. Cloudflare 挑战(CF-2) ## 13. Cloudflare 挑战(CF-2)
+1 -1
View File
@@ -96,7 +96,7 @@ select key, value->>'title' title, value->>'year' y, value->>'source' src from k
测试:`apps/server/test/sources-identity/id-escalation.test.ts`(观众 DET-2 / 憨憨 HH-2 详情样本经注入 fetch、TMDB mock,零真实请求); 测试:`apps/server/test/sources-identity/id-escalation.test.ts`(观众 DET-2 / 憨憨 HH-2 详情样本经注入 fetch、TMDB mock,零真实请求);
差分组 `packages/domain/test/differential/identity/site-ids-*.json`(MP `search/result.py _match_source`:种子 IMDb 与作品一致即命中; 差分组 `packages/domain/test/differential/identity/site-ids-*.json`(MP `search/result.py _match_source`:种子 IMDb 与作品一致即命中;
我们年份 / 类型冲突时只作候选,登记为 intentional,见 `site-ids-deviations.json`),`packages/domain/test/identity-id3.test.ts`。 我们年份 / 类型冲突时只作候选,登记为 intentional,见 `site-ids-deviations.json`),`packages/domain/test/identity-site-ids.test.ts`。
## 接管任务按文件名识别(QB-1) ## 接管任务按文件名识别(QB-1)
+1 -1
View File
@@ -1,6 +1,6 @@
# 全局搜索运行手册(W9-G、SEARCH-2) # 全局搜索运行手册(W9-G、SEARCH-2)
实现:`apps/server/src/modules/search/`(`routes.ts` 端点、`releases.ts` 跨站搜索/缓存/筛选/叠加、`media.ts` 本地作品与 TMDB、`works.ts` 作品合并/库存对比/查询词、`actions.ts` 页面与操作清单、`dispatch.ts` 进程内转调);合同 `packages/contracts/src/search.ts`;测试 `apps/server/test/search/`(`search2.test.ts` 为两阶段)。 实现:`apps/server/src/modules/search/`(`routes.ts` 端点、`releases.ts` 跨站搜索/缓存/筛选/叠加、`media.ts` 本地作品与 TMDB、`works.ts` 作品合并/库存对比/查询词、`actions.ts` 页面与操作清单、`dispatch.ts` 进程内转调);合同 `packages/contracts/src/search.ts`;测试 `apps/server/test/search/`(`search-results-ui.test.ts` 为两阶段)。
## 两阶段流程(SEARCH-2) ## 两阶段流程(SEARCH-2)
+1 -1
View File
@@ -197,7 +197,7 @@
**测试**:`packages/adapters/src/sites/challenge.test.ts`(分类)、`apps/server/test/sources-identity/egress.test.ts`(出口)、 **测试**:`packages/adapters/src/sites/challenge.test.ts`(分类)、`apps/server/test/sources-identity/egress.test.ts`(出口)、
`cloudflare.test.ts`(级联:自动通过 / 视觉 / 转人工 / 复用失败回退浏览器 / 节流单飞 / 同步条件 / 健康)、 `cloudflare.test.ts`(级联:自动通过 / 视觉 / 转人工 / 复用失败回退浏览器 / 节流单飞 / 同步条件 / 健康)、
`modules/browser/__tests__/browser-cloudflare.test.ts`(mock camofox:自动放行、插件真实鼠标点击、选图转人工、交还继续、代抓与 profile 锁)、 `modules/browser/__tests__/browser-cloudflare.test.ts`(mock camofox:自动放行、插件真实鼠标点击、选图转人工、交还继续、代抓与 profile 锁)、
`test/e2e/e14-vision.test.ts`(系统发起的视觉子流程)。真机:未对真实站点验证(CF-2 只用 mock);插件鼠标点击见 browser.md §13。 `test/e2e/vision.test.ts`(系统发起的视觉子流程)。真机:未对真实站点验证(CF-2 只用 mock);插件鼠标点击见 browser.md §13。
**要人处理时**:站点页 Cloudflare 行显示「需要你验证」→ 原站视图接管 → 过验证 → 交还。一直过不去(站点绑定 TLS 指纹、代理 IP 被拉黑): **要人处理时**:站点页 Cloudflare 行显示「需要你验证」→ 原站视图接管 → 过验证 → 交还。一直过不去(站点绑定 TLS 指纹、代理 IP 被拉黑):
关掉站点代理或换出口;实在不行关闭该站点的定时快照,只保留 RSS。 关掉站点代理或换出口;实在不行关闭该站点的定时快照,只保留 RSS。
+1 -1
View File
@@ -48,7 +48,7 @@ curl -X POST -H "authorization: Bearer $KEY" -H 'content-type: application/json'
## 验证 ## 验证
- 单元:`packages/adapters/src/websearch/websearch.test.ts`(参数与鉴权头、错误分类、归一化、地址守卫、截断、重定向到内网、超时)。 - 单元:`packages/adapters/src/websearch/websearch.test.ts`(参数与鉴权头、错误分类、归一化、地址守卫、截断、重定向到内网、超时)。
- e2e:`apps/server/test/e2e/e13-websearch.test.ts`(见 [agent.md](agent.md#e2e))。 - e2e:`apps/server/test/e2e/websearch.test.ts`(见 [agent.md](agent.md#e2e))。
- 真实 Key(2026-09-28,开发库,后端 4415):`POST /websearch/test` 3 条 / 914 ms;`web.search`「MoviePilot v3 订阅规则」5 条 / 736 ms,再查命中缓存;`web.fetch` 第一条(wiki.movie-pilot.org)200、13.6 KB → 1342 字正文 / 1.9 s;读白名单外链接、`127.0.0.1` 均被拒。 - 真实 Key(2026-09-28,开发库,后端 4415):`POST /websearch/test` 3 条 / 914 ms;`web.search`「MoviePilot v3 订阅规则」5 条 / 736 ms,再查命中缓存;`web.fetch` 第一条(wiki.movie-pilot.org)200、13.6 KB → 1342 字正文 / 1.9 s;读白名单外链接、`127.0.0.1` 均被拒。
## 已知限制 ## 已知限制