package handler_test import ( "net/http" "testing" "github.com/gin-gonic/gin" "github.com/wangjia/pay/config" "github.com/wangjia/pay/internal/accounts" "github.com/wangjia/pay/internal/gateway" "github.com/wangjia/pay/internal/model" "github.com/wangjia/pay/internal/provider" "github.com/wangjia/pay/internal/provider/fake" "github.com/wangjia/pay/internal/router" "github.com/wangjia/pay/internal/store" ) // jiu 反馈波 item 3:v2 改状态端点(POST /orders 等)超过 config.RateLimit 设定的 // per-IP 速率应回 429。config.C 用完整重赋值 + defer 恢复(与 refund_test.go // buildRefundEngine 同一套约定),避免污染同包其它测试的全局配置状态。 func TestV2CreateOrderRateLimited(t *testing.T) { gin.SetMode(gin.TestMode) prev := config.C config.C = config.Config{RateLimit: config.RateLimitConfig{RequestsPerMin: 2}} defer func() { config.C = prev }() db := model.OpenTestDB(t) orders := store.NewOrderStore(db) refunds := store.NewRefundStore(db) preg := provider.NewRegistry() preg.Register(fake.New()) areg := accounts.New([]config.AccountConfig{ {AccountID: "fake-a1", Channel: "fake", Region: "global", Enabled: true, Weight: 1}, }) picker := accounts.NewRouter(areg, nil, nil) g := gateway.New(orders, refunds, preg, picker, oneResolver{}, nopEnqueuer{}, "global", store.NewSubscriptionStore(db), store.NewChargebackStore(db)) r := gin.New() router.SetupV2(r, g) body := map[string]any{"sku": "pro_year", "method": "fake"} var codes []int for i := 0; i < 3; i++ { w, _ := do(t, r, http.MethodPost, "/api/v2/orders", body) codes = append(codes, w.Code) } if codes[0] != http.StatusOK || codes[1] != http.StatusOK { t.Fatalf("前 2 次(= burst=RequestsPerMin)应放行, got codes=%v", codes) } if codes[2] != http.StatusTooManyRequests { t.Fatalf("第 3 次(超 burst)应 429, got codes=%v", codes) } } // callback 端点(渠道来源,不是终端用户)不挂限流:哪怕下单类速率上限压得很低, // 回调也不该被误伤,否则会把渠道异步通知的正常重投当成攻击拦掉。 func TestV2CallbackNotRateLimited(t *testing.T) { gin.SetMode(gin.TestMode) prev := config.C config.C = config.Config{RateLimit: config.RateLimitConfig{RequestsPerMin: 1}} defer func() { config.C = prev }() r, _ := buildEngineWithStore(t) for i := 0; i < 5; i++ { w, _ := do(t, r, http.MethodPost, "/api/v2/callback/fake", map[string]any{ "provider_ref": "GHOST", "status": "succeeded", "amount_minor": 1, "currency": "USDT", }) if w.Code != http.StatusOK { t.Fatalf("callback 第 %d 次不应被限流, code=%d", i+1, w.Code) } } } // disabled:true 应完全放行改状态端点,不管速率配多低。 func TestV2RateLimitDisabled(t *testing.T) { gin.SetMode(gin.TestMode) prev := config.C config.C = config.Config{RateLimit: config.RateLimitConfig{Disabled: true, RequestsPerMin: 1}} defer func() { config.C = prev }() db := model.OpenTestDB(t) orders := store.NewOrderStore(db) refunds := store.NewRefundStore(db) preg := provider.NewRegistry() preg.Register(fake.New()) areg := accounts.New([]config.AccountConfig{ {AccountID: "fake-a1", Channel: "fake", Region: "global", Enabled: true, Weight: 1}, }) picker := accounts.NewRouter(areg, nil, nil) g := gateway.New(orders, refunds, preg, picker, oneResolver{}, nopEnqueuer{}, "global", store.NewSubscriptionStore(db), store.NewChargebackStore(db)) r := gin.New() router.SetupV2(r, g) body := map[string]any{"sku": "pro_year", "method": "fake"} for i := 0; i < 5; i++ { w, _ := do(t, r, http.MethodPost, "/api/v2/orders", body) if w.Code != http.StatusOK { t.Fatalf("disabled=true 时第 %d 次不应被限流, code=%d", i+1, w.Code) } } }