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) } }