服务器测试与本地数据库

本文件是 talent-card-server 的测试约定。目标是让**新会话**不需要重新摸索环境:一条命令跑测试,
一条命令重置库,并且不重蹈已知的坑。

环境一览(2026-09-17 实测)

库 版本 Flyway 谁在连
开发库 192.168.110.13:3306/talent_card(局域网共享) MySQL 5.7.44 关(flyway.enabled=false) 后端、后台管理页、两个小程序
测试库 127.0.0.1:8134/talent_card_test(本机实例) MySQL 8.0.36 开 本文件的测试脚本

三条由此推出的硬约束:

  • 迁移必须同时兼容 5.7 与 8.0:测试只在 8.0 上跑,跑绿不代表开发库能执行(V148 的存储过程写法
    已在 5.7 上实跑验证过,但每次新增迁移都值得照做一遍)。
  • 开发库的结构不由 Flyway 维护:缺表缺列只能靠启动时的初始化器 best-effort 补,补不上就手工补(见下)。
  • 本机 8134 实例上另有 talent_card、talent_card_dryrun、talent_card_verify1 三个库:它们是
    2026-09-16 把本机库并入服务器库**之前**的本机副本,**不再是开发库**,不要拿它们判断开发库状态。
    早先会话留在 .scratch/ 下的一次性探针(*Probe*.java、Inspect*.java、Apply*.java 等)大多把 URL
    写死成 127.0.0.1:8134/talent_card,指的就是这份旧副本;复用时要显式传开发库 URL,别照默认值跑。

一条命令跑测试

cd talent-card-server
.\scripts\run-server-tests.ps1                              # 全量
.\scripts\run-server-tests.ps1 -Test PaymentSettlementTests # 单个类
.\scripts\run-server-tests.ps1 -Test A,B                    # 多个类
.\scripts\run-server-tests.ps1 -Clean -Test A               # 构建产物可疑时先 clean
.\scripts\run-server-tests.ps1 -Reset                       # 重置库后再跑

脚本会设置 TEST_DATASOURCE_*(MySQL 127.0.0.1:8134、talent_card_test、root/123456,
也可用环境变量覆盖),把日志写到 target/server-tests.log,并在失败时列出已知基线失败。

必须走这个脚本(或等价地只跑一个 Maven 进程)。 同一个 checkout 上并发跑两个构建会写坏
target/,症状是看起来不可能的报错——例如 No qualifying bean of type 'HuifuPaymentProperties'。
遇到这类"不可能"的 bean/classpath 错误,先 -Clean,再怀疑代码。

重置测试库

.\scripts\reset-test-db.ps1                    # 默认 talent_card_test
.\scripts\reset-test-db.ps1 -Database talent_card

脚本用项目已有的 MySQL JDBC 驱动(本仓库没有 mysql 客户端、没有 Docker),删掉该库里的全部表;
下一次测试由 Flyway 从 db/migration 重建。

不要 drop database,也不要只删 flyway_schema_history 里的某一行。
- drop database 之后重建会触发 BankBranchDirectoryInitializer 导入 10 万行网点数据(几分钟,
且曾观察到卡死)。测试 profile 已关闭该导入,所以上面的重置脚本不会再有这个代价。
- 手工删 history 行会让已执行的 DDL 与 Flyway 记录不一致,后续迁移报错更难查。

不要重建开发库

开发库是局域网共享的那一个(192.168.110.13:3306/talent_card,见上表),不是测试库。它里面有
一批**迁移从未创建**的表——当年是直接在库里手建的,例如 dependent_card_apply、merchant_benefit_rule。
所以「把测试库结构克隆到开发库」「reset-test-db.ps1 -Database talent_card」这类操作会把开发库里这些
表连同数据一起删掉:2026-09-13 就这样丢过一批,最后靠一份同源快照才回灌回来(reset-test-db.ps1
对名为 talent_card 的库一律拒绝,正是为此)。

注意开发库的 spring.flyway.enabled=false:它**不会**被迁移自动修正,缺表缺列只能手工补。

需要让开发库结构跟上迁移时:

  • 只补缺失的 DDL(create table if not exists、add column),**不要**删表重建。手工补 DDL 正是
    2026-09-17「点开收款能力就 500 / Unknown column 'capability_sync_until'」的解药。
  • 动结构之前先备份。本机**没有** mysql 客户端、也没有 Docker(README 里指向 D:\MySQL\... 和
    docker exec talent-card-mysql-1 的两条老做法都已失效),要备份就在 DB 主机上做,或照
    scripts/reset-test-db.ps1 的办法用项目已有的 JDBC 驱动写个单文件程序。
  • 初始化器是 best-effort 的:它的 alter table 等 DDL 元数据锁的上限只有 5 秒,等不到就记一条
    error 放行(宁可少一列,也不能让启动被 DDL 挂住——2026-09-15 为此瘫过一次)。所以「重启过就一定有
    新列」不成立:启动日志里搜 Merchant onboarding schema: could not add,那里会给出可直接执行的手工补救语句。

迁移是只追加的

已经执行过的 V*__*.sql 不要改(改一个字符就会 checksum 不匹配,测试库直接起不来)。需要调整
就新增一个迁移文件,或用 reset-test-db.ps1 重建后再跑。历史上 scripts/repair-local-mysql-v*.sql
就是为此付出的代价。

已知基线失败(不是回归)

整套测试有 3 项在 HEAD 基线上就失败,与本轮改动无关;用 HEAD worktree + 同一测试库复现过:

测试 现象
MerchantWorkServiceTests.filtersOrdersByCategoryStatusAndDateWithoutExposingFullMobile stays.total() 期望 1 实得 0
TalentAuthServiceTests.movesWechatBindingWhenSameWechatLogsInWithAnotherMobile insert into talent_session BadSqlGrammar
MerchantSettlementStatisticsMapperTests.includesHotelFreeOfflineSubsidyWithoutIncreasingTransactionAmount 持久化断言失败

新会话不要为了证明它们"是既有的"而重建 worktree:直接看这张表。修好其中任何一项时,请删掉对应行。

环境相关失败(不是基线,别当既有失败放过)

PaymentFundConfirmationTests.aPendingConfirmationOfAVerifiedOrderIsRetriedWithoutAnotherVerification
在测试库累计的「已核销 + 待确认」订单超过 50 条时会**必然**失败:补偿任务扫全库、order by o.id asc limit 50(PaymentFundConfirmationRetry.retry),而用例新建的那单 id 最大、排在窗口之外,于是状态停在
pending。这是测试库长期不清理累积出来的,不是代码回归——2026-09-17 实测符合条件的行为 60 条,最早一条
09-14 就留下了,而且被处理过的行多数停在 confirming(仍在扫描条件内),所以不会自己排空。

现象、证据与建议改法见
.scratch/payment-fund-confirmation-retry/issues/01
(属支付链路范围,本轮只排查未修)。要么让用例自己把窗口腾出来,要么清理测试库遗留(后者只能缓解)。

测试库的状态是共享的

Spring 测试共用一个 schema,测试之间不做清理(没有 @Transactional 回滚)。因此:

  • 断言要按自己创建的 ID / 单号过滤,不要断言"全表只有一行"。
  • 断言"后台任务只处理了我这条"时,先把自己之外的相关行挪出时间窗口(例如把历史
    payment_transaction.created_at 置为 now()),否则前面的用例会漏进来。
  • 需要真实 MySQL 语义(当前所有 Spring 测试都是)时,H2 只作为没有 MySQL 时的兜底。