//go:build test // +build test package handlers // Package handlers contains tests for core middleware and health checks. // // Test Coverage: // - Health check: Basic HTTP 200 response // - RequireAuth middleware: Blocks unauthenticated requests (401), allows valid JWT (200) // - RequireRole middleware: Blocks non-admin users (403), allows admins (200) // - Integration test: Full user flow with JWT auth and context propagation // // Note: These tests focus on middleware behavior, not specific handler business logic. import ( "encoding/json" "io" "net/http" "net/http/httptest" "testing" "crussell/mw" "crussell/testutils" "crussell/testutils/fixtures" "crussell/testutils/jwt" ) // TestHealthCheck verifies the health check endpoint returns HTTP 200 OK. // This test ensures the basic HTTP server is responding and the health // check handler is properly wired up to return a status response. func TestHealthCheck(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"ok"}`)) }) server := httptest.NewServer(handler) defer server.Close() resp, err := server.Client().Get(server.URL) if err != nil { t.Fatalf("failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("expected status 200, got %d", resp.StatusCode) } } // TestRequireAuthMiddleware verifies the JWT authentication middleware correctly // blocks unauthenticated requests and allows valid JWT tokens through. // It tests three scenarios: missing auth header (401), valid token (200 with // user context), and invalid token (401). func TestRequireAuthMiddleware(t *testing.T) { t.Parallel() handler := mw.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID, _ := r.Context().Value(mw.UserIDKey).(string) role, _ := r.Context().Value(mw.UserRoleKey).(string) w.Write([]byte(`{"user_id":"` + userID + `","role":"` + role + `"}`)) })) t.Run("no auth header returns 401", func(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Errorf("expected status 401, got %d", w.Code) } }) t.Run("valid token passes auth", func(t *testing.T) { jwt.Init() token := jwt.GenerateTestToken("test-user-123", "verified_email") req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } body, _ := io.ReadAll(w.Body) var resp map[string]string json.Unmarshal(body, &resp) if resp["user_id"] != "test-user-123" { t.Errorf("expected user_id test-user-123, got %s", resp["user_id"]) } if resp["role"] != "verified_email" { t.Errorf("expected role verified_email, got %s", resp["role"]) } }) t.Run("invalid token returns 401", func(t *testing.T) { jwt.Init() req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Authorization", "Bearer invalid-token") w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Errorf("expected status 401, got %d", w.Code) } }) } // TestRequireRoleMiddleware verifies the role-based access control middleware // correctly blocks non-admin users (403) and allows admin users (200) to access // protected resources. It chains RequireAuth before RequireRole to populate // the role in the request context. func TestRequireRoleMiddleware(t *testing.T) { t.Parallel() // Chain RequireAuth before RequireRole to set the role in context // RequireRole expects role to be in context, but that's only set by RequireAuth adminOnlyHandler := mw.RequireAuth(mw.RequireRole("admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(`{"success":true}`)) }))) t.Run("admin role passes", func(t *testing.T) { jwt.Init() token := jwt.GenerateAdminToken() req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() adminOnlyHandler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } }) t.Run("non-admin role returns 403", func(t *testing.T) { jwt.Init() token := jwt.GenerateUserToken("test-user") req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() adminOnlyHandler.ServeHTTP(w, req) if w.Code != http.StatusForbidden { t.Errorf("expected status 403, got %d", w.Code) } }) } // TestIntegration_UserFlow tests the full authentication flow end-to-end, // verifying that JWT tokens are properly validated, user ID is extracted from // the token, and context values are correctly propagated to handlers. // This is an integration test that validates the complete middleware chain. func TestIntegration_UserFlow(t *testing.T) { t.Parallel() if testing.Short() { t.Skip("skipping integration test in short mode") } _, tx := testutils.SetupTestTx(t) userID, err := fixtures.CreateTestUser(tx) if err != nil { t.Fatalf("failed to create test user: %v", err) } token := jwt.GenerateUserToken(userID) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { http.Error(w, "no auth", http.StatusUnauthorized) return } userIDCtx, ok := r.Context().Value(mw.UserIDKey).(string) if !ok { http.Error(w, "no user id in context", http.StatusInternalServerError) return } w.Write([]byte(`{"user_id":"` + userIDCtx + `"}`)) }) req := httptest.NewRequest("GET", "/", nil) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() mw.RequireAuth(handler).ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } }