143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
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)
|
|
}
|
|
}
|