From 62e3e9688419f35f2fa1e69613db93cbd57623e2 Mon Sep 17 00:00:00 2001 From: senke Date: Wed, 25 Feb 2026 20:02:24 +0100 Subject: [PATCH] test(v0.803): unit tests for CCPA, reports, announcements, feature flags --- .../handlers/announcement_handler_test.go | 29 ++++++++ .../handlers/feature_flag_handler_test.go | 67 +++++++++++++++++++ .../internal/handlers/report_handler_test.go | 55 +++++++++++++++ .../internal/middleware/ccpa_test.go | 52 +++++++------- 4 files changed, 175 insertions(+), 28 deletions(-) create mode 100644 veza-backend-api/internal/handlers/announcement_handler_test.go create mode 100644 veza-backend-api/internal/handlers/feature_flag_handler_test.go create mode 100644 veza-backend-api/internal/handlers/report_handler_test.go diff --git a/veza-backend-api/internal/handlers/announcement_handler_test.go b/veza-backend-api/internal/handlers/announcement_handler_test.go new file mode 100644 index 000000000..144fa122f --- /dev/null +++ b/veza-backend-api/internal/handlers/announcement_handler_test.go @@ -0,0 +1,29 @@ +package handlers + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "veza-backend-api/internal/services" +) + +func TestAnnouncementHandler_Delete_InvalidID(t *testing.T) { + logger := zap.NewNop() + svc := services.NewAnnouncementService(nil, logger) + handler := NewAnnouncementHandler(svc) + + gin.SetMode(gin.TestMode) + router := gin.New() + router.DELETE("/admin/announcements/:id", handler.Delete) + + req := httptest.NewRequest("DELETE", "/admin/announcements/invalid-uuid", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} diff --git a/veza-backend-api/internal/handlers/feature_flag_handler_test.go b/veza-backend-api/internal/handlers/feature_flag_handler_test.go new file mode 100644 index 000000000..c133333d9 --- /dev/null +++ b/veza-backend-api/internal/handlers/feature_flag_handler_test.go @@ -0,0 +1,67 @@ +package handlers + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + + "veza-backend-api/internal/models" + "veza-backend-api/internal/services" +) + +func setupFeatureFlagTestDB(t *testing.T) *gorm.DB { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + assert.NoError(t, err) + assert.NoError(t, db.AutoMigrate(&models.FeatureFlag{})) + // Seed with one flag for toggle test + assert.NoError(t, db.Create(&models.FeatureFlag{ + Name: "TEST_FLAG", + Enabled: false, + Description: "Test", + }).Error) + return db +} + +func TestFeatureFlagHandler_List(t *testing.T) { + db := setupFeatureFlagTestDB(t) + logger := zap.NewNop() + svc := services.NewFeatureFlagService(db, logger) + handler := NewFeatureFlagHandler(svc) + + gin.SetMode(gin.TestMode) + router := gin.New() + router.GET("/admin/feature-flags", handler.List) + + req := httptest.NewRequest("GET", "/admin/feature-flags", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestFeatureFlagHandler_Toggle_NotFound(t *testing.T) { + db := setupFeatureFlagTestDB(t) + logger := zap.NewNop() + svc := services.NewFeatureFlagService(db, logger) + handler := NewFeatureFlagHandler(svc) + + gin.SetMode(gin.TestMode) + router := gin.New() + router.PUT("/admin/feature-flags/:name", handler.Toggle) + + body, _ := json.Marshal(map[string]bool{"enabled": true}) + req := httptest.NewRequest("PUT", "/admin/feature-flags/NONEXISTENT", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) +} diff --git a/veza-backend-api/internal/handlers/report_handler_test.go b/veza-backend-api/internal/handlers/report_handler_test.go new file mode 100644 index 000000000..ca5caa88c --- /dev/null +++ b/veza-backend-api/internal/handlers/report_handler_test.go @@ -0,0 +1,55 @@ +package handlers + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + + "veza-backend-api/internal/services" +) + +func TestReportHandler_ResolveReport_InvalidID(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.New() + logger := zap.NewNop() + svc := services.NewReportService(nil, logger) + handler := NewReportHandler(svc) + + router.POST("/admin/reports/:id/resolve", func(c *gin.Context) { + c.Set("user_id", uuid.New()) + handler.ResolveReport(c) + }) + + body, _ := json.Marshal(map[string]string{"action": "resolve"}) + req := httptest.NewRequest("POST", "/admin/reports/invalid-id/resolve", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestReportHandler_ResolveReport_Unauthorized(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.New() + logger := zap.NewNop() + svc := services.NewReportService(nil, logger) + handler := NewReportHandler(svc) + + router.POST("/admin/reports/:id/resolve", handler.ResolveReport) + + body, _ := json.Marshal(map[string]string{"action": "resolve"}) + req := httptest.NewRequest("POST", "/admin/reports/550e8400-e29b-41d4-a716-446655440000/resolve", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} diff --git a/veza-backend-api/internal/middleware/ccpa_test.go b/veza-backend-api/internal/middleware/ccpa_test.go index 41fab658a..5807ee44f 100644 --- a/veza-backend-api/internal/middleware/ccpa_test.go +++ b/veza-backend-api/internal/middleware/ccpa_test.go @@ -9,17 +9,34 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCCPA_SetsContextAndHeader(t *testing.T) { +func TestCCPA_NoHeader(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.Use(CCPA()) router.GET("/test", func(c *gin.Context) { - val, exists := c.Get(CCPAContextKey) - if exists && val.(bool) { - c.Status(http.StatusOK) - } else { - c.Status(http.StatusNoContent) - } + val, ok := c.Get(CCPAContextKey) + assert.False(t, ok) + assert.Nil(t, val) + c.Status(http.StatusOK) + }) + + req := httptest.NewRequest("GET", "/test", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Empty(t, w.Header().Get("GPC")) +} + +func TestCCPA_SecGPCHeader(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.New() + router.Use(CCPA()) + router.GET("/test", func(c *gin.Context) { + val, ok := c.Get(CCPAContextKey) + assert.True(t, ok) + assert.True(t, val.(bool)) + c.Status(http.StatusOK) }) req := httptest.NewRequest("GET", "/test", nil) @@ -30,24 +47,3 @@ func TestCCPA_SetsContextAndHeader(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "1", w.Header().Get("GPC")) } - -func TestCCPA_NoHeaderWhenAbsent(t *testing.T) { - gin.SetMode(gin.TestMode) - router := gin.New() - router.Use(CCPA()) - router.GET("/test", func(c *gin.Context) { - _, exists := c.Get(CCPAContextKey) - if !exists { - c.Status(http.StatusOK) - } else { - c.Status(http.StatusNoContent) - } - }) - - req := httptest.NewRequest("GET", "/test", nil) - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - assert.Empty(t, w.Header().Get("GPC")) -}