package refundretry import ( "testing" "time" "hfb_sys/backend/internal/model" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func setupRefundRetryTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("创建测试数据库失败: %v", err) } if err := db.AutoMigrate(&model.PaymentOrder{}, &model.RentalOrder{}); err != nil { t.Fatalf("数据库迁移失败: %v", err) } return db } func TestRetryBackoffCapsAtOneHour(t *testing.T) { cases := []struct { retryCount int want time.Duration }{ {retryCount: 1, want: 5 * time.Minute}, {retryCount: 2, want: 10 * time.Minute}, {retryCount: 3, want: 20 * time.Minute}, {retryCount: 4, want: 40 * time.Minute}, {retryCount: 5, want: time.Hour}, {retryCount: 10, want: time.Hour}, } for _, tc := range cases { if got := retryBackoff(tc.retryCount); got != tc.want { t.Fatalf("retryBackoff(%d) = %s, want %s", tc.retryCount, got, tc.want) } } } func TestMarkRetryFailedStopsAtMaxCount(t *testing.T) { db := setupRefundRetryTestDB(t) job := New(db, nil, zap.NewNop(), nil) now := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC) row := model.PaymentOrder{ PaymentNo: "PAY202606140101", OrderID: 1, OrderNo: "ORD202606140101", UserID: 2, Provider: "mock", ThirdOrderID: "REF202606140101", BizType: "admin_refund", Status: "failed", RetryCount: maxRetryCount - 1, } if err := db.Create(&row).Error; err != nil { t.Fatalf("创建退款单失败: %v", err) } if err := job.markRetryFailed(t.Context(), row, now); err != nil { t.Fatalf("markRetryFailed() error = %v", err) } var latest model.PaymentOrder if err := db.First(&latest, row.ID).Error; err != nil { t.Fatalf("查询退款单失败: %v", err) } if latest.RetryCount != maxRetryCount { t.Fatalf("RetryCount = %d, want %d", latest.RetryCount, maxRetryCount) } if latest.LastRetryAt == nil || !latest.LastRetryAt.Equal(now) { t.Fatalf("LastRetryAt = %v, want %v", latest.LastRetryAt, now) } if latest.NextRetryAt != nil { t.Fatalf("NextRetryAt = %v, want nil after max retry", latest.NextRetryAt) } } func TestResetRetryClearsRetryFields(t *testing.T) { db := setupRefundRetryTestDB(t) job := New(db, nil, zap.NewNop(), nil) now := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC) next := now.Add(time.Hour) row := model.PaymentOrder{ PaymentNo: "PAY202606140102", OrderID: 1, OrderNo: "ORD202606140102", UserID: 2, Provider: "mock", ThirdOrderID: "REF202606140102", BizType: "admin_refund", Status: "refunded", RetryCount: 3, LastRetryAt: &now, NextRetryAt: &next, } if err := db.Create(&row).Error; err != nil { t.Fatalf("创建退款单失败: %v", err) } if err := job.resetRetry(t.Context(), row.ID); err != nil { t.Fatalf("resetRetry() error = %v", err) } var latest model.PaymentOrder if err := db.First(&latest, row.ID).Error; err != nil { t.Fatalf("查询退款单失败: %v", err) } if latest.RetryCount != 0 || latest.LastRetryAt != nil || latest.NextRetryAt != nil { t.Fatalf("retry fields = count:%d last:%v next:%v, want zero/nil", latest.RetryCount, latest.LastRetryAt, latest.NextRetryAt) } }