101 lines
3.7 KiB
Go
101 lines
3.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/company/ai-ops/internal/config"
|
|
"github.com/company/ai-ops/internal/service"
|
|
)
|
|
|
|
func TestAuthAllowsPublicPaths(t *testing.T) {
|
|
cfg := config.ServerConfig{JWTSecret: "secret", MetricsAuth: "metrics-key"}
|
|
for _, path := range []string{"/health", "/actuator/health/ready", "/api/v1/ai-ops/login", "/openapi.json", "/ops/dashboard"} {
|
|
t.Run(path, func(t *testing.T) {
|
|
called := false
|
|
h := Auth(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true }))
|
|
h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, path, nil))
|
|
if !called {
|
|
t.Fatalf("public path %s was blocked", path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuthMetricsAPIKeyAndJWT(t *testing.T) {
|
|
cfg := config.ServerConfig{JWTSecret: "secret", MetricsAuth: "metrics-key"}
|
|
t.Run("metrics api key", func(t *testing.T) {
|
|
called := false
|
|
h := Auth(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true }))
|
|
req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
|
req.Header.Set("X-API-Key", "metrics-key")
|
|
h.ServeHTTP(httptest.NewRecorder(), req)
|
|
if !called {
|
|
t.Fatal("metrics api key did not pass")
|
|
}
|
|
})
|
|
|
|
t.Run("missing token rejected", func(t *testing.T) {
|
|
h := Auth(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Fatal("should not call next") }))
|
|
w := httptest.NewRecorder()
|
|
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/api/v1/ai-ops/rules", nil))
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Fatalf("status = %d", w.Code)
|
|
}
|
|
})
|
|
|
|
t.Run("valid jwt sets context", func(t *testing.T) {
|
|
token, err := service.NewAuthService("secret").IssueToken("u1", "operator")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h := Auth(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Context().Value("user_id") != "u1" || r.Context().Value("role") != "operator" {
|
|
t.Fatalf("context not populated: user=%v role=%v", r.Context().Value("user_id"), r.Context().Value("role"))
|
|
}
|
|
}))
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/ai-ops/rules", nil)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
h.ServeHTTP(httptest.NewRecorder(), req)
|
|
})
|
|
}
|
|
|
|
func TestRequireRoleAndRequireWrite(t *testing.T) {
|
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusAccepted) })
|
|
|
|
allowed := RequireRole("admin")(next)
|
|
req := httptest.NewRequest(http.MethodGet, "/x", nil).WithContext(context.WithValue(context.Background(), "role", "admin"))
|
|
w := httptest.NewRecorder()
|
|
allowed.ServeHTTP(w, req)
|
|
if w.Code != http.StatusAccepted {
|
|
t.Fatalf("role allowed status = %d", w.Code)
|
|
}
|
|
|
|
denied := httptest.NewRecorder()
|
|
RequireRole("admin")(next).ServeHTTP(denied, httptest.NewRequest(http.MethodGet, "/x", nil))
|
|
if denied.Code != http.StatusForbidden {
|
|
t.Fatalf("role denied status = %d", denied.Code)
|
|
}
|
|
|
|
read := httptest.NewRecorder()
|
|
RequireWrite(next).ServeHTTP(read, httptest.NewRequest(http.MethodGet, "/x", nil))
|
|
if read.Code != http.StatusAccepted {
|
|
t.Fatalf("read status = %d", read.Code)
|
|
}
|
|
|
|
writeDenied := httptest.NewRecorder()
|
|
RequireWrite(next).ServeHTTP(writeDenied, httptest.NewRequest(http.MethodPost, "/x", nil))
|
|
if writeDenied.Code != http.StatusForbidden {
|
|
t.Fatalf("write denied status = %d", writeDenied.Code)
|
|
}
|
|
|
|
writeAllowed := httptest.NewRecorder()
|
|
writeReq := httptest.NewRequest(http.MethodPost, "/x", nil).WithContext(context.WithValue(context.Background(), "role", "operator"))
|
|
RequireWrite(next).ServeHTTP(writeAllowed, writeReq)
|
|
if writeAllowed.Code != http.StatusAccepted {
|
|
t.Fatalf("write allowed status = %d", writeAllowed.Code)
|
|
}
|
|
}
|