diff --git a/.agents/knowledge/external-deps-and-gotchas.md b/.agents/knowledge/external-deps-and-gotchas.md index 9124788f..81cc7423 100644 --- a/.agents/knowledge/external-deps-and-gotchas.md +++ b/.agents/knowledge/external-deps-and-gotchas.md @@ -35,12 +35,16 @@ ## 四、跑 P1R real-PG 集成测试(IT)的配方 + 本机代理坑(2026-06-14 实证) -**结论先行**:P1 后端 23 个 `P1r*IT`(completed-approval / Flyway 迁移 / events-publish outbox / live-acceptance)**可在共享远端 PG 上真跑验证**。**2026-06-15 mini-infra PG 全量重跑 = 23/23 IT 类全绿(99 用例 0F/0E;2 例 external-acceptance 因未设 `MUSE_P1R_EXTERNAL_ACCEPTANCE` 而 assumeTrue 跳过)**。此前唯一红 `P1rKnowledgeFlywayMigrationIT`(硬编码版本断言随 schema 增长失效,V14→V21→V23 已复发两次)**已修为动态**:读本次 Flyway `MigrateResult.targetSchemaVersion/migrationsExecuted` 自适应(commit 4d46d7a,Codex+Opus 双代理并行提案合并;反假绿仍校验迁移成功 + history 计数一致 + Knowledge 表/索引/列存在)。 +**结论先行**:P1 后端 `P1r*IT`(completed-approval / Flyway 迁移 / events-publish outbox / live-acceptance)**可在共享远端 PG 上真跑验证**。**2026-06-26 全量 50 个 `P1r*IT` 全绿(199 用例 0F/0E,1 Skipped = RagFlow GraphRAG attribution 因 `..._GRAPHRAG_ATTRIBUTION_READY=false` 而 assumeTrue 跳过)**;4 个 live-acceptance(NewApi/RagFlow/AiRuntime/KnowledgeRuntime)载外部 env + `MUSE_P1R_EXTERNAL_ACCEPTANCE=true` 真打 New-API/RAGFlow 全绿。本轮修 2 个**测试上下文落后产品演进**的预存红:① `P1rContentPlanningCompletedApprovalIT` 的 `TARGET_VERSION=21` 停在 V21,但 `savePlanningItem→captureFieldSnapshot` 已依赖 V25 新增的 `muse_content_planning_field_snapshot` 表(commit 94a2379)→ 写入 `relation does not exist` 500;**修=推进 TARGET_VERSION 21→31 + migrationsExecuted 断言 21→31**(与 `P1rContentCoreCompletedApprovalIT` V30、`P1rAiRuntimeEndToEndLiveAcceptanceIT` V31 同向,核心断言不放宽)。② live-acceptance HTTP 代理坑(见下条)。**经验**:`*CompletedApprovalIT` 各自硬编 `TARGET_VERSION`,只迁到其所测路径需要的版本(V14~V31 混杂);产品给某 service 新增依赖表后,对应 IT 的 target 须跟进,否则该 IT(且仅该 IT)红——其他停在低版本的 IT 不受影响是因其路径不碰新表。此前 `P1rKnowledgeFlywayMigrationIT` 硬编码版本断言随 schema 增长失效(V14→V21→V23 复发两次)**已修为动态**:读本次 Flyway `MigrateResult.targetSchemaVersion/migrationsExecuted` 自适应(commit 4d46d7a)。 **▲ 最大坑(排查极久):本机 env `HTTP_PROXY=HTTPS_PROXY=http://127.0.0.1:7897` → JVM 取 `socksProxyHost=127.0.0.1:7897`,把所有 Java socket 走 SOCKS;转发 HTTP 正常,但破坏 PostgreSQL 原始线协议(发完 StartupMessage 即被 EOF,认证前关闭,极像 pg_hba/密码错)。** `nc`/`curl` 直连正常,只有 JDBC/JVM 中招;且 `env -u HTTP_PROXY` 清不掉(socksProxyHost 由 JVM 层注入)。 - 判定 PG 真可达:`printf '\x00\x00\x00\x08\x04\xd2\x16\x2f' | nc -w5 100.64.0.8 5433 | xxd` 回 `N` 即 PG 在说话(纯 nc 直连,不经代理)。 - **修复:给 forked 测试 JVM 加 `-DargLine='-DsocksProxyHost= -DsocksProxyPort='`**(清空 SOCKS,JVM 直连 tailnet);代码内可用 `new Socket(java.net.Proxy.NO_PROXY)`。 +**▲ 第二代理坑(2026-06-26 实证,live-acceptance IT 专属):上一条只清 `socksProxyHost`,但 macOS 系统网络代理 `127.0.0.1:7897` 还会被 JVM 自动注入 `http.proxyHost/https.proxyHost`(`-XshowSettings:properties -version` 可见,即便 shell 已 `unset HTTP_PROXY` 且 mise exec 环境无代理)。** `java.net.http.HttpClient`(RagFlow/NewApi runtime client 用它,`HttpClient.newBuilder()...build()` 无显式 `.proxy(...)`)默认走 `ProxySelector.getDefault()` → 经死代理 7897 → RagFlow health 报 `RAGFLOW_UNAVAILABLE`、New-API completions 报 `provider_5xx / 502`(`retryable=true`)。**关键诊断**:`http.nonProxyHosts` 不含 tailnet `100.64.0.8`,故这些请求不被 bypass;`curl --noproxy '*'` 直打同地址 health/completions 全 200(证明外部服务健康、是 JVM 客户端走代理),极易误判成"外部环境波动/限流"。**纯 PG IT 不中此坑**(PG 走原始 socket 受 `socksProxyHost` 管,已被上一条清掉;不碰 HTTP 客户端)。 +- **修复:live-acceptance 的 argLine 须同时清 HTTP 代理**:`-DargLine='-DsocksProxyHost= -DsocksProxyPort= -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttps.proxyHost= -Dhttps.proxyPort= -Djava.net.useSystemProxies=false'`。本轮加此参后 4 个 live-acceptance(NewApi 2/2、RagFlow 2/2[1 GraphRAG skip]、AiRuntime 3/3、KnowledgeRuntime 3/3)全绿。 +- 判定外部服务真可达(绕本机代理):`curl -s --noproxy '*' http://100.64.0.8/v1/system/healthz`(RAGFlow 回 `{"status":"ok",...}`)、`curl -s --noproxy '*' -X POST http://100.64.0.8:3000/v1/chat/completions -H "Authorization: Bearer " -d '{"model":"MiniMax-M2.5","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'`(回 200 + completion)。两者 200 而 live IT 报 unavailable/5xx → 必是 JVM 代理坑,非外部波动。 + **跑单个 IT 的配方(已验证)**: ```bash export MUSE_POSTGRES_PASSWORD= diff --git a/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentPlanningCompletedApprovalIT.java b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentPlanningCompletedApprovalIT.java index 59bbfcd2..52496304 100644 --- a/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentPlanningCompletedApprovalIT.java +++ b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentPlanningCompletedApprovalIT.java @@ -104,7 +104,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @TestInstance(TestInstance.Lifecycle.PER_CLASS) class P1rContentPlanningCompletedApprovalIT { - private static final String TARGET_VERSION = "21"; + // 迁移目标对齐当前 schema 头(V31):savePlanningItem 经 captureFieldSnapshot 写 V25 新增的 + // muse_content_planning_field_snapshot 表(commit 94a2379「planning 字段用量快照表」),停在 V21 会缺表 → 写入 500。 + // 与 P1rContentCoreCompletedApprovalIT(V30)、P1rAiRuntimeEndToEndLiveAcceptanceIT(V31)同向收口, + // 仅推进测试上下文到产品现状,核心断言(planning fact/command/outbox)不放宽。 + private static final String TARGET_VERSION = "31"; private static final Long TENANT_ID = 100L; private static final Long OTHER_TENANT_ID = 200L; private static final Long LOGIN_USER_ID = 9001L; @@ -158,8 +162,8 @@ class P1rContentPlanningCompletedApprovalIT { .load(); cleanSchema(flyway, settings); MigrateResult result = migrateSchema(flyway, settings); - assertEquals(21, result.migrationsExecuted, - "Content Planning completed approval 必须在隔离库执行 V1-V21 全量迁移,实际: " + assertEquals(31, result.migrationsExecuted, + "Content Planning completed approval 必须在隔离库执行 V1-V31 全量迁移(含 V25 planning 字段快照表),实际: " + result.migrationsExecuted); }