fix(chat): 优化后台限流与 SSE 日志
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"hfb_sys/backend/internal/modules/auth"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func runRateLimitedRequest(router *gin.Engine, bearerToken string) int {
|
||||
req := httptest.NewRequest(http.MethodGet, "/ping", nil)
|
||||
if bearerToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+bearerToken)
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(recorder, req)
|
||||
return recorder.Code
|
||||
}
|
||||
|
||||
func newRateLimitRouter(limit int, rdb interface{}, handler gin.HandlerFunc) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
router.Use(handler)
|
||||
router.GET("/ping", func(c *gin.Context) {
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
return router
|
||||
}
|
||||
|
||||
func adminAccessToken(t *testing.T, jwtManager *auth.JWTManager, adminID uint64, phone string) string {
|
||||
t.Helper()
|
||||
pair, err := jwtManager.GenerateSubjectPairWithVersion(adminID, phone, "admin", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("生成后台令牌失败: %v", err)
|
||||
}
|
||||
return pair.AccessToken
|
||||
}
|
||||
|
||||
func TestAdminAwareRateLimitIsolatesAdminsBehindSharedIP(t *testing.T) {
|
||||
jwtManager := auth.NewJWTManager("test-secret")
|
||||
adminA := adminAccessToken(t, jwtManager, 101, "13800000001")
|
||||
adminB := adminAccessToken(t, jwtManager, 102, "13800000002")
|
||||
router := newRateLimitRouter(2, nil, AdminAwareRateLimitPerMinute(2, nil, jwtManager))
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if code := runRateLimitedRequest(router, adminA); code != http.StatusOK {
|
||||
t.Fatalf("客服A 第 %d 次请求 = %d, want 200", i+1, code)
|
||||
}
|
||||
}
|
||||
if code := runRateLimitedRequest(router, adminA); code != http.StatusTooManyRequests {
|
||||
t.Fatalf("客服A 超额请求 = %d, want 429", code)
|
||||
}
|
||||
if code := runRateLimitedRequest(router, adminB); code != http.StatusOK {
|
||||
t.Fatalf("同出口IP的客服B 应不受客服A影响, got %d, want 200", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminAwareRateLimitFallsBackToIP(t *testing.T) {
|
||||
jwtManager := auth.NewJWTManager("test-secret")
|
||||
userPair, err := jwtManager.GenerateSubjectPairWithVersion(201, "13900000001", "user", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("生成用户令牌失败: %v", err)
|
||||
}
|
||||
router := newRateLimitRouter(2, nil, AdminAwareRateLimitPerMinute(2, nil, jwtManager))
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
if code := runRateLimitedRequest(router, ""); code != http.StatusOK {
|
||||
t.Fatalf("匿名请求 第 %d 次 = %d, want 200", i+1, code)
|
||||
}
|
||||
}
|
||||
if code := runRateLimitedRequest(router, ""); code != http.StatusTooManyRequests {
|
||||
t.Fatalf("匿名请求超额 = %d, want 429", code)
|
||||
}
|
||||
// 用户令牌无法按 admin 身份解析,应与匿名共享同一个 IP 桶。
|
||||
if code := runRateLimitedRequest(router, userPair.AccessToken); code != http.StatusTooManyRequests {
|
||||
t.Fatalf("用户令牌应回退到IP维度, got %d, want 429", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyRateLimitPerMinuteKeepsIPKey(t *testing.T) {
|
||||
router := newRateLimitRouter(1, nil, RateLimitPerMinute(1, nil))
|
||||
if code := runRateLimitedRequest(router, ""); code != http.StatusOK {
|
||||
t.Fatalf("首次匿名请求 = %d, want 200", code)
|
||||
}
|
||||
if code := runRateLimitedRequest(router, ""); code != http.StatusTooManyRequests {
|
||||
t.Fatalf("第二次匿名请求 = %d, want 429", code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user