test(pay): 补 settle 并发/跨包全链 e2e/reconcile 装配/alipay 验签健壮性/money 零小数位

- gateway: TestSettleConcurrentSameOrder 真并发钉住 MarkAttemptPaid 行锁不变量
  (文件型 sqlite,规避 in-memory cache=shared 的 SQLITE_LOCKED_SHAREDCACHE)。
- gateway: e2e_fullchain_test.go 补下单→回调→settle→webhook 实际 HTTP 投递→
  delivered 整链(httptest server + 真实 store.WebhookStore/webhook.Notifier)。
- reconcile: main.go 装配抽到 reconcile.Assemble(+Runner.TaskNames 访问器),
  补 assembly_test.go 钉住 7 个后台任务全部注册 + crypto 缺渠道/interval=0 分支。
- alipay: 补验签健壮性(未知多余字段/字段乱序/中文unicode/空值字段),意外定位
  vendor Encoder 对空值字段的真实语义与官方文档描述不同,已记录在测试注释。
- money: 补 TestZeroDecimalCurrencyJPYNotSupported,记录 JPY/KRW 当前未注册进
  exponents(接入会先踩 ErrUnknownCurrency,不是乘除法坑)。
This commit is contained in:
wangjia
2026-07-11 08:50:45 +08:00
parent 1f87b0fcbf
commit 0ae7769abe
8 changed files with 696 additions and 16 deletions
+3 -16
View File
@@ -108,23 +108,10 @@ func main() {
// crypto 预留冷启动 Warm + 孤儿扫描(Task 6,AddCryptoJobs)。
if config.C.Reconcile.Enabled {
rc := config.C.Reconcile
runner := reconcile.NewRunner()
// 纯本地(DB-only)任务用 AddLocal:会被启动预热 RunOnceLocal 同步跑一遍。
runner.AddLocal("order-expire", time.Duration(rc.ExpireEverySec)*time.Second,
reconcile.OrderExpirerTask(orderStore, time.Duration(rc.OrderTTLMin)*time.Minute, time.Now))
runner.AddLocal("usage-refresh", time.Duration(rc.UsageEverySec)*time.Second,
reconcile.RefreshUsageTask(usage))
runner.AddLocal("refund-apply-sweep", time.Duration(rc.RefundApplyEverySec)*time.Second,
reconcile.RefundApplyTask(orderStore, refundStore, time.Duration(rc.RefundApplyLookbackMin)*time.Minute, time.Now, 200))
runner.AddLocal("refund-stuck-alert", time.Duration(rc.RefundApplyEverySec)*time.Second,
reconcile.RefundStuckAlertTask(refundStore, time.Duration(rc.RefundStuckWarnMin)*time.Minute, time.Now))
// 网络型(出网 HTTP,10-15s 超时)任务用 Add:启动预热不跑,交各自 ticker 首跳。
runner.Add("sync-pending", time.Duration(rc.SyncEverySec)*time.Second,
reconcile.SyncPendingTask(gw, 100))
runner.Add("paid-spotcheck", time.Duration(rc.SpotCheckEverySec)*time.Second,
reconcile.PaidSpotCheckTask(orderStore, pReg, time.Duration(rc.SpotCheckWindowMin)*time.Minute, time.Now))
orphanStore := store.NewOrphanStore(db)
reconcile.AddCryptoJobs(runner, pReg, acctReg, orderStore, orphanStore, rc) // crypto 预留冷启动 Warm(直跑,不受影响)+ 孤儿扫描(网络型,交 ticker 首跳)
// 装配逻辑抽到 reconcile.Assemble(单测直接断言任务集合,见
// internal/reconcile/assembly_test.go);这里只是薄薄一层调用 + 预热/常驻启动。
runner := reconcile.Assemble(orderStore, refundStore, usage, gw, pReg, acctReg, orphanStore, rc)
ctx := context.Background()
runner.RunOnceLocal(ctx) // 启动预热:只跑本地任务(usage 快照/过期清理/退款自愈立即生效);网络型任务(sync-pending/paid-spotcheck/crypto-orphan-scan)交各自 ticker 首跳,避免上游慢拖住 r.Run(addr) 前的启动