package payment import ( "errors" "testing" "hfb_sys/backend/internal/model" ) // TestPaymentOrderStates 测试支付单状态 func TestPaymentOrderStates(t *testing.T) { states := []string{"pending", "paying", "paid", "failed", "closed", "refunding", "refunded"} for _, state := range states { if state == "" { t.Fatal("payment order state should not be empty") } } } // TestRefundBizTypesAreValid 测试退款业务类型有效性 func TestRefundBizTypesAreValid(t *testing.T) { validTypes := map[string]bool{ "cancel_refund": true, "admin_close_refund": true, "admin_refund": true, "checkout_refund": true, "deposit_refund": true, "rent_refund": true, "arbitration_refund": true, } for _, bizType := range refundBizTypes { if !validTypes[bizType] { t.Fatalf("unexpected refund biz type: %s", bizType) } } if len(refundBizTypes) != len(validTypes) { t.Fatalf("refundBizTypes count = %d, want %d", len(refundBizTypes), len(validTypes)) } } // TestCanReuseOrderPaymentLogic 测试支付单复用逻辑 func TestCanReuseOrderPaymentWithSameMerchant(t *testing.T) { payment := model.PaymentOrder{ Status: "paying", Provider: "lakala", MerchantID: "M123", } config := runtimePaymentConfig{ Provider: "lakala", MerchantID: "M123", } if !canReuseOrderPayment(payment, config) { t.Fatal("should reuse payment with same merchant and paying status") } } func TestCanReuseOrderPaymentWithDifferentMerchant(t *testing.T) { payment := model.PaymentOrder{ Status: "paying", Provider: "lakala", MerchantID: "M123", } config := runtimePaymentConfig{ Provider: "lakala", MerchantID: "M456", // 不同商户 } if canReuseOrderPayment(payment, config) { t.Fatal("should not reuse payment with different merchant") } } func TestCanReuseOrderPaymentWithTerminalStatus(t *testing.T) { terminalStatuses := []string{"failed", "closed"} config := runtimePaymentConfig{ Provider: "lakala", MerchantID: "M123", } for _, status := range terminalStatuses { payment := model.PaymentOrder{ Status: status, Provider: "lakala", MerchantID: "M123", } if canReuseOrderPayment(payment, config) { t.Fatalf("should not reuse payment with terminal status: %s", status) } } } func TestCanReuseOrderPaymentWithPaidStatus(t *testing.T) { payment := model.PaymentOrder{ Status: "paid", Provider: "lakala", MerchantID: "M123", } config := runtimePaymentConfig{ Provider: "lakala", MerchantID: "M123", } // 已支付的订单可以复用(幂等) if !canReuseOrderPayment(payment, config) { t.Fatal("should reuse payment with paid status for idempotency") } } // TestGeneratePaymentNo 测试支付单号生成 func TestPaymentNoShouldHavePrefix(t *testing.T) { // 支付单号应该以特定前缀开头(如 PAY) // 这是一个示例测试,实际格式需要根据代码确认 paymentNo := "PAY20260610123456" if len(paymentNo) < 3 { t.Fatal("paymentNo should have meaningful length") } } func TestPaymentNoUniquenessAssumption(t *testing.T) { // 支付单号应该是唯一的 // 实际实现中通常使用时间戳+随机数保证唯一性 seen := make(map[string]bool) // 模拟多个支付单号 paymentNos := []string{ "PAY20260610123456001", "PAY20260610123456002", "PAY20260610123456003", } for _, no := range paymentNos { if seen[no] { t.Fatalf("duplicate paymentNo: %s", no) } seen[no] = true } } // TestMinWalletRechargeAmount 测试最小充值金额常量 func TestMinWalletRechargeAmountIsPositive(t *testing.T) { if MinWalletRechargeAmount <= 0 { t.Fatal("MinWalletRechargeAmount should be positive") } } // TestPaymentDTOValidation 测试支付 DTO 基本结构 func TestPaymentDTOHasRequiredFields(t *testing.T) { dto := PaymentDTO{ ID: 1, PaymentNo: "PAY123456", OrderNo: "ORD123456", AmountCent: 10000, Status: "paid", } if dto.ID == 0 { t.Fatal("PaymentDTO.ID should not be zero") } if dto.PaymentNo == "" { t.Fatal("PaymentDTO.PaymentNo should not be empty") } if dto.AmountCent <= 0 { t.Fatal("PaymentDTO.AmountCent should be positive") } if dto.Status == "" { t.Fatal("PaymentDTO.Status should not be empty") } } // TestRefundDTOValidation 测试退款 DTO 基本结构 func TestRefundDTOHasRequiredFields(t *testing.T) { dto := RefundDTO{ OrderID: 100, AmountCent: 5000, Status: "refunding", BizType: "cancel_refund", } if dto.OrderID == 0 { t.Fatal("RefundDTO.OrderID should not be zero") } if dto.AmountCent <= 0 { t.Fatal("RefundDTO.AmountCent should be positive") } if dto.Status == "" { t.Fatal("RefundDTO.Status should not be empty") } if dto.BizType == "" { t.Fatal("RefundDTO.BizType should not be empty") } } // TestChannelSourceConstants 测试渠道来源常量 func TestChannelSourceConstantsAreUnique(t *testing.T) { sources := []string{ channelSourceCreate, channelSourceQuery, channelSourceNotify, channelSourceMock, } seen := make(map[string]bool) for _, source := range sources { if seen[source] { t.Fatalf("duplicate channel source: %s", source) } seen[source] = true } if len(seen) != 4 { t.Fatalf("expected 4 unique channel sources, got %d", len(seen)) } } // TestRuntimePaymentConfigValidation 测试运行时支付配置 func TestRuntimePaymentConfigRequiredFields(t *testing.T) { config := runtimePaymentConfig{ ID: 1, Provider: "lakala", MerchantID: "M123", PayWay: "ZFBZF", } if config.ID == 0 { t.Fatal("config.ID should not be zero") } if config.Provider == "" { t.Fatal("config.Provider should not be empty") } if config.MerchantID == "" { t.Fatal("config.MerchantID should not be empty") } } // TestServiceInputValidation 测试 Service 输入验证 func TestServiceStartRequiresNonZeroOrderID(t *testing.T) { svc := &Service{repo: &Repository{}} _, err := svc.Start(1, 0, StartPaymentRequest{}, "127.0.0.1") if !errors.Is(err, ErrPaymentCannotStart) { t.Fatalf("error = %v, want ErrPaymentCannotStart", err) } } func TestServiceStartRequiresNonZeroUserID(t *testing.T) { svc := &Service{repo: &Repository{}} _, err := svc.Start(0, 100, StartPaymentRequest{}, "127.0.0.1") if !errors.Is(err, ErrPaymentCannotStart) { t.Fatalf("error = %v, want ErrPaymentCannotStart", err) } } func TestServiceStartRefundRequiresPositiveAmount(t *testing.T) { svc := &Service{repo: &Repository{}} _, err := svc.StartRefund(100, 0, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("error = %v, want ErrRefundCannotStart", err) } _, err = svc.StartRefund(100, -1000, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("error = %v, want ErrRefundCannotStart", err) } } func TestServiceStartRefundRequiresNonZeroOrderID(t *testing.T) { svc := &Service{repo: &Repository{}} _, err := svc.StartRefund(0, 1000, "cancel_refund", "test") if !errors.Is(err, ErrRefundCannotStart) { t.Fatalf("error = %v, want ErrRefundCannotStart", err) } } // TestPaymentErrorTypes 测试错误类型定义 func TestPaymentErrorsAreDefined(t *testing.T) { errors := []error{ ErrDependencyUnavailable, ErrPaymentUnavailable, ErrPaymentCannotStart, ErrPaymentVerifyFailed, ErrPaymentNotFound, ErrRefundCannotStart, ErrWalletRechargeDisabled, } for i, err := range errors { if err == nil { t.Fatalf("error[%d] should not be nil", i) } if err.Error() == "" { t.Fatalf("error[%d] should have message", i) } } }