package handlers import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" "veza-backend-api/internal/models" "veza-backend-api/internal/services" "veza-backend-api/internal/workers" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "gorm.io/driver/sqlite" "gorm.io/gorm" ) // setupTestWebhookHandler creates a test handler with real services and in-memory database func setupTestWebhookHandler(t *testing.T) (*WebhookHandler, *gorm.DB, *gin.Engine, func()) { gin.SetMode(gin.TestMode) logger := zaptest.NewLogger(t) // Setup in-memory SQLite database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) db.Exec("PRAGMA foreign_keys = ON") // Auto-migrate models err = db.AutoMigrate( &models.User{}, &models.Webhook{}, ) require.NoError(t, err) // Setup webhook service jwtSecret := "test-secret-key" webhookService := services.NewWebhookService(db, logger, jwtSecret) // Setup webhook worker webhookWorker := workers.NewWebhookWorker( db, webhookService, logger, 100, // Queue size 5, // Workers 3, // Max retries ) handler := NewWebhookHandler(webhookService, webhookWorker, logger) router := gin.New() router.Use(func(c *gin.Context) { // Mock auth middleware - set user_id from header if present userIDStr := c.GetHeader("X-User-ID") if userIDStr != "" { uid, err := uuid.Parse(userIDStr) if err == nil { c.Set("user_id", uid) } } c.Next() }) cleanup := func() { // Database cleanup handled by test } return handler, db, router, cleanup } // Helper to create a test user func createTestUserForWebhook(id uuid.UUID, username string) *models.User { return &models.User{ ID: id, Username: username, Email: fmt.Sprintf("%s@example.com", username), IsActive: true, IsVerified: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } } // TestWebhookHandler_RegisterWebhook_Success tests successful webhook registration func TestWebhookHandler_RegisterWebhook_Success(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.POST("/webhooks", handler.RegisterWebhook()) registerReq := map[string]interface{}{ "url": "https://example.com/webhook", "events": []string{"track.created", "track.updated"}, } body, _ := json.Marshal(registerReq) req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) var response APIResponse err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.Success) } // TestWebhookHandler_RegisterWebhook_InvalidURL tests webhook registration with invalid URL func TestWebhookHandler_RegisterWebhook_InvalidURL(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.POST("/webhooks", handler.RegisterWebhook()) registerReq := map[string]interface{}{ "url": "not-a-valid-url", "events": []string{"track.created"}, } body, _ := json.Marshal(registerReq) req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestWebhookHandler_RegisterWebhook_NoEvents tests webhook registration with no events func TestWebhookHandler_RegisterWebhook_NoEvents(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.POST("/webhooks", handler.RegisterWebhook()) registerReq := map[string]interface{}{ "url": "https://example.com/webhook", "events": []string{}, } body, _ := json.Marshal(registerReq) req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestWebhookHandler_RegisterWebhook_Unauthorized tests webhook registration without authentication func TestWebhookHandler_RegisterWebhook_Unauthorized(t *testing.T) { handler, _, router, cleanup := setupTestWebhookHandler(t) defer cleanup() router.POST("/webhooks", handler.RegisterWebhook()) registerReq := map[string]interface{}{ "url": "https://example.com/webhook", "events": []string{"track.created"}, } body, _ := json.Marshal(registerReq) req := httptest.NewRequest(http.MethodPost, "/webhooks", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") // No X-User-ID header w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // TestWebhookHandler_ListWebhooks_Success tests successful webhook listing func TestWebhookHandler_ListWebhooks_Success(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) // Create test webhooks with unique API keys logger := zaptest.NewLogger(t) webhookService := services.NewWebhookService(db, logger, "test-secret") for i := 0; i < 3; i++ { apiKey, err := webhookService.GenerateAPIKey() require.NoError(t, err) webhook := &models.Webhook{ ID: uuid.New(), UserID: userID, URL: fmt.Sprintf("https://example.com/webhook%d", i+1), Events: []string{"track.created"}, Active: true, APIKey: apiKey, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = db.Create(webhook).Error require.NoError(t, err) } router.GET("/webhooks", handler.ListWebhooks()) req := httptest.NewRequest(http.MethodGet, "/webhooks", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response APIResponse err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.Success) } // TestWebhookHandler_DeleteWebhook_Success tests successful webhook deletion func TestWebhookHandler_DeleteWebhook_Success(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) // Create test webhook webhookID := uuid.New() webhook := &models.Webhook{ ID: webhookID, UserID: userID, URL: "https://example.com/webhook", Events: []string{"track.created"}, Active: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = db.Create(webhook).Error require.NoError(t, err) router.DELETE("/webhooks/:id", handler.DeleteWebhook()) req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/webhooks/%s", webhookID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response APIResponse err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.Success) } // TestWebhookHandler_DeleteWebhook_NotFound tests webhook deletion with non-existent webhook func TestWebhookHandler_DeleteWebhook_NotFound(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.DELETE("/webhooks/:id", handler.DeleteWebhook()) nonExistentID := uuid.New() req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/webhooks/%s", nonExistentID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // TestWebhookHandler_DeleteWebhook_InvalidID tests webhook deletion with invalid ID format func TestWebhookHandler_DeleteWebhook_InvalidID(t *testing.T) { handler, _, router, cleanup := setupTestWebhookHandler(t) defer cleanup() router.DELETE("/webhooks/:id", handler.DeleteWebhook()) req := httptest.NewRequest(http.MethodDelete, "/webhooks/invalid-id", nil) req.Header.Set("X-User-ID", uuid.New().String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestWebhookHandler_GetWebhookStats_Success tests successful webhook stats retrieval func TestWebhookHandler_GetWebhookStats_Success(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.GET("/webhooks/stats", handler.GetWebhookStats()) req := httptest.NewRequest(http.MethodGet, "/webhooks/stats", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response APIResponse err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.Success) } // TestWebhookHandler_TestWebhook_Success tests successful webhook test func TestWebhookHandler_TestWebhook_Success(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) // Create test webhook webhookID := uuid.New() webhook := &models.Webhook{ ID: webhookID, UserID: userID, URL: "https://example.com/webhook", Events: []string{"track.created"}, Active: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = db.Create(webhook).Error require.NoError(t, err) router.POST("/webhooks/:id/test", handler.TestWebhook()) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/test", webhookID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response APIResponse err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.Success) } // TestWebhookHandler_TestWebhook_NotFound tests webhook test with non-existent webhook func TestWebhookHandler_TestWebhook_NotFound(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.POST("/webhooks/:id/test", handler.TestWebhook()) nonExistentID := uuid.New() req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/test", nonExistentID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // TestWebhookHandler_RegenerateAPIKey_Success tests successful API key regeneration func TestWebhookHandler_RegenerateAPIKey_Success(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) // Create test webhook webhookID := uuid.New() webhook := &models.Webhook{ ID: webhookID, UserID: userID, URL: "https://example.com/webhook", Events: []string{"track.created"}, Active: true, APIKey: "old_api_key", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = db.Create(webhook).Error require.NoError(t, err) router.POST("/webhooks/:id/regenerate-key", handler.RegenerateAPIKey()) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/regenerate-key", webhookID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response APIResponse err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response.Success) // Verify that API key is present in response dataBytes, _ := json.Marshal(response.Data) var data map[string]interface{} err = json.Unmarshal(dataBytes, &data) require.NoError(t, err) assert.Contains(t, data, "api_key") assert.NotEmpty(t, data["api_key"]) } // TestWebhookHandler_RegenerateAPIKey_NotFound tests API key regeneration with non-existent webhook func TestWebhookHandler_RegenerateAPIKey_NotFound(t *testing.T) { handler, db, router, cleanup := setupTestWebhookHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForWebhook(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.POST("/webhooks/:id/regenerate-key", handler.RegenerateAPIKey()) nonExistentID := uuid.New() req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/webhooks/%s/regenerate-key", nonExistentID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // TestWebhookHandler_RegenerateAPIKey_InvalidID tests API key regeneration with invalid ID format func TestWebhookHandler_RegenerateAPIKey_InvalidID(t *testing.T) { handler, _, router, cleanup := setupTestWebhookHandler(t) defer cleanup() router.POST("/webhooks/:id/regenerate-key", handler.RegenerateAPIKey()) req := httptest.NewRequest(http.MethodPost, "/webhooks/invalid-id/regenerate-key", nil) req.Header.Set("X-User-ID", uuid.New().String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) }