package handlers import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "github.com/Wikid82/charon/backend/internal/models" "github.com/Wikid82/charon/backend/internal/services" ) func setupNotificationCoverageDB(t *testing.T) *gorm.DB { t.Helper() db := OpenTestDB(t) _ = db.AutoMigrate(&models.Notification{}, &models.NotificationProvider{}, &models.NotificationTemplate{}) return db } // Notification Handler Tests func TestNotificationHandler_List_Error(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationHandler(svc) // Drop the table to cause error _ = db.Migrator().DropTable(&models.Notification{}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("GET", "/notifications", http.NoBody) h.List(c) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), "Failed to list notifications") } func TestNotificationHandler_List_UnreadOnly(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationHandler(svc) // Create some notifications _, _ = svc.Create(models.NotificationTypeInfo, "Test 1", "Message 1") _, _ = svc.Create(models.NotificationTypeInfo, "Test 2", "Message 2") w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("GET", "/notifications?unread=true", http.NoBody) h.List(c) assert.Equal(t, 200, w.Code) } func TestNotificationHandler_MarkAsRead_Error(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.Notification{}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} h.MarkAsRead(c) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), "Failed to mark notification as read") } func TestNotificationHandler_MarkAllAsRead_Error(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.Notification{}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) h.MarkAllAsRead(c) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), "Failed to mark all notifications as read") } // Notification Provider Handler Tests func TestNotificationProviderHandler_List_Error(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationProvider{}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) h.List(c) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), "Failed to list providers") } func TestNotificationProviderHandler_Create_InvalidJSON(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/providers", bytes.NewBufferString("invalid json")) c.Request.Header.Set("Content-Type", "application/json") h.Create(c) assert.Equal(t, 400, w.Code) } func TestNotificationProviderHandler_Create_DBError(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationProvider{}) provider := models.NotificationProvider{ Name: "Test", Type: "webhook", URL: "https://example.com", Template: "minimal", } body, _ := json.Marshal(provider) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/providers", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Create(c) assert.Equal(t, 500, w.Code) } func TestNotificationProviderHandler_Create_InvalidTemplate(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) provider := models.NotificationProvider{ Name: "Test", Type: "webhook", URL: "https://example.com", Template: "custom", Config: "{{.Invalid", // Invalid template syntax } body, _ := json.Marshal(provider) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/providers", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Create(c) assert.Equal(t, 400, w.Code) } func TestNotificationProviderHandler_Update_InvalidJSON(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} c.Request = httptest.NewRequest("PUT", "/providers/test-id", bytes.NewBufferString("invalid")) c.Request.Header.Set("Content-Type", "application/json") h.Update(c) assert.Equal(t, 400, w.Code) } func TestNotificationProviderHandler_Update_InvalidTemplate(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) // Create a provider first provider := models.NotificationProvider{ Name: "Test", Type: "webhook", URL: "https://example.com", Template: "minimal", } require.NoError(t, svc.CreateProvider(&provider)) // Update with invalid template provider.Template = "custom" provider.Config = "{{.Invalid" // Invalid body, _ := json.Marshal(provider) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: provider.ID}} c.Request = httptest.NewRequest("PUT", "/providers/"+provider.ID, bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Update(c) assert.Equal(t, 400, w.Code) } func TestNotificationProviderHandler_Update_DBError(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationProvider{}) provider := models.NotificationProvider{ Name: "Test", Type: "webhook", URL: "https://example.com", Template: "minimal", } body, _ := json.Marshal(provider) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} c.Request = httptest.NewRequest("PUT", "/providers/test-id", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Update(c) assert.Equal(t, 500, w.Code) } func TestNotificationProviderHandler_Delete_Error(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationProvider{}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} h.Delete(c) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), "Failed to delete provider") } func TestNotificationProviderHandler_Test_InvalidJSON(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/providers/test", bytes.NewBufferString("invalid")) c.Request.Header.Set("Content-Type", "application/json") h.Test(c) assert.Equal(t, 400, w.Code) } func TestNotificationProviderHandler_Templates(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) h.Templates(c) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "minimal") assert.Contains(t, w.Body.String(), "detailed") assert.Contains(t, w.Body.String(), "custom") } func TestNotificationProviderHandler_Preview_InvalidJSON(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/providers/preview", bytes.NewBufferString("invalid")) c.Request.Header.Set("Content-Type", "application/json") h.Preview(c) assert.Equal(t, 400, w.Code) } func TestNotificationProviderHandler_Preview_WithData(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) payload := map[string]any{ "template": "minimal", "data": map[string]any{ "Title": "Custom Title", "Message": "Custom Message", }, } body, _ := json.Marshal(payload) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/providers/preview", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Preview(c) assert.Equal(t, 200, w.Code) } func TestNotificationProviderHandler_Preview_InvalidTemplate(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationProviderHandler(svc) payload := map[string]any{ "template": "custom", "config": "{{.Invalid", } body, _ := json.Marshal(payload) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/providers/preview", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Preview(c) assert.Equal(t, 400, w.Code) } // Notification Template Handler Tests func TestNotificationTemplateHandler_List_Error(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationTemplate{}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) h.List(c) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), "failed to list templates") } func TestNotificationTemplateHandler_Create_BadJSON(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/templates", bytes.NewBufferString("invalid")) c.Request.Header.Set("Content-Type", "application/json") h.Create(c) assert.Equal(t, 400, w.Code) } func TestNotificationTemplateHandler_Create_DBError(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationTemplate{}) tmpl := models.NotificationTemplate{ Name: "Test", Config: `{"test": true}`, } body, _ := json.Marshal(tmpl) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/templates", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Create(c) assert.Equal(t, 500, w.Code) } func TestNotificationTemplateHandler_Update_BadJSON(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} c.Request = httptest.NewRequest("PUT", "/templates/test-id", bytes.NewBufferString("invalid")) c.Request.Header.Set("Content-Type", "application/json") h.Update(c) assert.Equal(t, 400, w.Code) } func TestNotificationTemplateHandler_Update_DBError(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationTemplate{}) tmpl := models.NotificationTemplate{ Name: "Test", Config: `{"test": true}`, } body, _ := json.Marshal(tmpl) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} c.Request = httptest.NewRequest("PUT", "/templates/test-id", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Update(c) assert.Equal(t, 500, w.Code) } func TestNotificationTemplateHandler_Delete_Error(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) // Drop table to cause error _ = db.Migrator().DropTable(&models.NotificationTemplate{}) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Params = gin.Params{{Key: "id", Value: "test-id"}} h.Delete(c) assert.Equal(t, 500, w.Code) assert.Contains(t, w.Body.String(), "failed to delete template") } func TestNotificationTemplateHandler_Preview_BadJSON(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/templates/preview", bytes.NewBufferString("invalid")) c.Request.Header.Set("Content-Type", "application/json") h.Preview(c) assert.Equal(t, 400, w.Code) } func TestNotificationTemplateHandler_Preview_TemplateNotFound(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) payload := map[string]any{ "template_id": "nonexistent", } body, _ := json.Marshal(payload) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/templates/preview", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Preview(c) assert.Equal(t, 400, w.Code) assert.Contains(t, w.Body.String(), "template not found") } func TestNotificationTemplateHandler_Preview_WithStoredTemplate(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) // Create a template tmpl := &models.NotificationTemplate{ Name: "Test", Config: `{"title": "{{.Title}}"}`, } require.NoError(t, svc.CreateTemplate(tmpl)) payload := map[string]any{ "template_id": tmpl.ID, "data": map[string]any{ "Title": "Test Title", }, } body, _ := json.Marshal(payload) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/templates/preview", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Preview(c) assert.Equal(t, 200, w.Code) } func TestNotificationTemplateHandler_Preview_InvalidTemplate(t *testing.T) { gin.SetMode(gin.TestMode) db := setupNotificationCoverageDB(t) svc := services.NewNotificationService(db) h := NewNotificationTemplateHandler(svc) payload := map[string]any{ "template": "{{.Invalid", } body, _ := json.Marshal(payload) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/templates/preview", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") h.Preview(c) assert.Equal(t, 400, w.Code) }