@@ -0,0 +1,142 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gin.SetMode(gin.TestMode)
|
||||
}
|
||||
|
||||
func TestSecurityHeaders(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(SecurityHeaders())
|
||||
r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
want := map[string]string{
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "DENY",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
"Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'",
|
||||
"Strict-Transport-Security": "max-age=63072000; includeSubDomains",
|
||||
}
|
||||
for header, expected := range want {
|
||||
if got := w.Header().Get(header); got != expected {
|
||||
t.Errorf("header %s = %q, want %q", header, got, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORS_AllowedOrigin(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(CORS([]string{"https://omnex.example"}))
|
||||
r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set("Origin", "https://omnex.example")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if got := w.Header().Get("Access-Control-Allow-Origin"); got != "https://omnex.example" {
|
||||
t.Errorf("Access-Control-Allow-Origin = %q, want %q", got, "https://omnex.example")
|
||||
}
|
||||
if got := w.Header().Get("Vary"); got != "Origin" {
|
||||
t.Errorf("Vary = %q, want %q", got, "Origin")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORS_DisallowedOrigin(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(CORS([]string{"https://omnex.example"}))
|
||||
r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set("Origin", "https://evil.example")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if got := w.Header().Get("Access-Control-Allow-Origin"); got != "" {
|
||||
t.Errorf("Access-Control-Allow-Origin = %q, want empty for disallowed origin", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORS_PreflightOptions(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(CORS([]string{"https://omnex.example"}))
|
||||
r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodOptions, "/", nil)
|
||||
req.Header.Set("Origin", "https://omnex.example")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Errorf("OPTIONS status = %d, want %d", w.Code, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit_AllowsWithinBurst(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(RateLimit(1, 3))
|
||||
r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.RemoteAddr = "10.0.0.1:1234"
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("request %d: status = %d, want %d", i, w.Code, http.StatusOK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit_BlocksOverBurst(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(RateLimit(1, 2))
|
||||
r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.RemoteAddr = "10.0.0.2:1234"
|
||||
r.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.RemoteAddr = "10.0.0.2:1234"
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusTooManyRequests {
|
||||
t.Errorf("status = %d, want %d", w.Code, http.StatusTooManyRequests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit_PerIPIsolation(t *testing.T) {
|
||||
r := gin.New()
|
||||
r.Use(RateLimit(1, 1))
|
||||
r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
// Épuise le burst pour l'IP A.
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.RemoteAddr = "10.0.0.3:1234"
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// L'IP B doit rester indépendante.
|
||||
w2 := httptest.NewRecorder()
|
||||
req2 := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req2.RemoteAddr = "10.0.0.4:1234"
|
||||
r.ServeHTTP(w2, req2)
|
||||
if w2.Code != http.StatusOK {
|
||||
t.Errorf("IP B status = %d, want %d (isolation from IP A)", w2.Code, http.StatusOK)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user