package middleware import ( "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" ) func TestRateLimitAllowsUnderLimit(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(RateLimit()) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) for i := 0; i < 10; i++ { req := httptest.NewRequest(http.MethodGet, "/test", nil) req.RemoteAddr = "192.168.1.1:12345" resp := httptest.NewRecorder() router.ServeHTTP(resp, req) if resp.Code != http.StatusOK { t.Fatalf("request %d should be allowed, got %d", i+1, resp.Code) } } } func TestRateLimitBlocksOverLimit(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(RateLimit()) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) // 发送 11 个请求,第 11 个应被限流 for i := 0; i < 11; i++ { req := httptest.NewRequest(http.MethodGet, "/test", nil) req.RemoteAddr = "192.168.1.2:12345" resp := httptest.NewRecorder() router.ServeHTTP(resp, req) if i < 10 && resp.Code != http.StatusOK { t.Fatalf("request %d should be allowed, got %d", i+1, resp.Code) } if i == 10 && resp.Code != http.StatusTooManyRequests { t.Fatalf("request 11 should be rate limited, got %d", resp.Code) } } } func TestRateLimitWindowReset(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(RateLimit()) router.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) // 先发送 10 个请求达到上限 for i := 0; i < 10; i++ { req := httptest.NewRequest(http.MethodGet, "/test", nil) req.RemoteAddr = "192.168.1.3:12345" resp := httptest.NewRecorder() router.ServeHTTP(resp, req) } // 模拟时间过去 61 秒(超过 1 分钟窗口) // 注意:由于实现使用真实时间,这里只能验证当前窗口内的行为 // 实际窗口重置测试需要 mock 时间或等待 req := httptest.NewRequest(http.MethodGet, "/test", nil) req.RemoteAddr = "192.168.1.3:12345" resp := httptest.NewRecorder() router.ServeHTTP(resp, req) if resp.Code != http.StatusTooManyRequests { t.Fatalf("should still be rate limited within window, got %d", resp.Code) } } func TestAuthRejectsMissingToken(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Auth()) router.GET("/protected", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) req := httptest.NewRequest(http.MethodGet, "/protected", nil) resp := httptest.NewRecorder() router.ServeHTTP(resp, req) if resp.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", resp.Code) } } func TestAuthRejectsMalformedToken(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(Auth()) router.GET("/protected", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) req := httptest.NewRequest(http.MethodGet, "/protected", nil) req.Header.Set("Authorization", "InvalidFormat token123") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) if resp.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", resp.Code) } } func TestCORSAllowsConfiguredOrigin(t *testing.T) { gin.SetMode(gin.TestMode) t.Setenv("CORS_ORIGINS", "https://example.com,https://app.example.com") router := gin.New() router.Use(CORS()) router.GET("/api/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) req := httptest.NewRequest(http.MethodGet, "/api/test", nil) req.Header.Set("Origin", "https://example.com") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) if resp.Code != http.StatusOK { t.Fatalf("expected 200, got %d", resp.Code) } if resp.Header().Get("Access-Control-Allow-Origin") != "https://example.com" { t.Fatalf("expected CORS header for allowed origin, got %s", resp.Header().Get("Access-Control-Allow-Origin")) } } func TestCORSRejectsUnconfiguredOrigin(t *testing.T) { gin.SetMode(gin.TestMode) t.Setenv("CORS_ORIGINS", "https://example.com") router := gin.New() router.Use(CORS()) router.GET("/api/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) }) req := httptest.NewRequest(http.MethodGet, "/api/test", nil) req.Header.Set("Origin", "https://evil.com") resp := httptest.NewRecorder() router.ServeHTTP(resp, req) // 非白名单源不应返回 CORS 头 if resp.Header().Get("Access-Control-Allow-Origin") == "https://evil.com" { t.Fatal("should not allow unconfigured origin") } }