269 lines
8.5 KiB
Go
269 lines
8.5 KiB
Go
package handlers_test
|
|
|
|
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/api/handlers"
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/Wikid82/charon/backend/internal/services"
|
|
)
|
|
|
|
func setupNotificationProviderTest(t *testing.T) (*gin.Engine, *gorm.DB) {
|
|
t.Helper()
|
|
db := handlers.OpenTestDB(t)
|
|
require.NoError(t, db.AutoMigrate(&models.NotificationProvider{}, &models.Notification{}))
|
|
|
|
service := services.NewNotificationService(db)
|
|
handler := handlers.NewNotificationProviderHandler(service)
|
|
|
|
r := gin.Default()
|
|
r.Use(func(c *gin.Context) {
|
|
c.Set("role", "admin")
|
|
c.Set("userID", uint(1))
|
|
c.Next()
|
|
})
|
|
api := r.Group("/api/v1")
|
|
providers := api.Group("/notifications/providers")
|
|
providers.GET("", handler.List)
|
|
providers.POST("/preview", handler.Preview)
|
|
providers.POST("", handler.Create)
|
|
providers.PUT("/:id", handler.Update)
|
|
providers.DELETE("/:id", handler.Delete)
|
|
providers.POST("/test", handler.Test)
|
|
api.GET("/notifications/templates", handler.Templates)
|
|
|
|
return r, db
|
|
}
|
|
|
|
func TestNotificationProviderHandler_CRUD(t *testing.T) {
|
|
r, db := setupNotificationProviderTest(t)
|
|
|
|
// 1. Create
|
|
provider := models.NotificationProvider{
|
|
Name: "Test Discord",
|
|
Type: "discord",
|
|
URL: "https://discord.com/api/webhooks/...",
|
|
}
|
|
body, _ := json.Marshal(provider)
|
|
req, _ := http.NewRequest("POST", "/api/v1/notifications/providers", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusCreated, w.Code)
|
|
var created models.NotificationProvider
|
|
err := json.Unmarshal(w.Body.Bytes(), &created)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, provider.Name, created.Name)
|
|
assert.NotEmpty(t, created.ID)
|
|
|
|
// 2. List
|
|
req, _ = http.NewRequest("GET", "/api/v1/notifications/providers", http.NoBody)
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var list []models.NotificationProvider
|
|
err = json.Unmarshal(w.Body.Bytes(), &list)
|
|
require.NoError(t, err)
|
|
assert.Len(t, list, 1)
|
|
|
|
// 3. Update
|
|
created.Name = "Updated Discord"
|
|
body, _ = json.Marshal(created)
|
|
req, _ = http.NewRequest("PUT", "/api/v1/notifications/providers/"+created.ID, bytes.NewBuffer(body))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var updated models.NotificationProvider
|
|
err = json.Unmarshal(w.Body.Bytes(), &updated)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Updated Discord", updated.Name)
|
|
|
|
// Verify in DB
|
|
var dbProvider models.NotificationProvider
|
|
db.First(&dbProvider, "id = ?", created.ID)
|
|
assert.Equal(t, "Updated Discord", dbProvider.Name)
|
|
|
|
// 4. Delete
|
|
req, _ = http.NewRequest("DELETE", "/api/v1/notifications/providers/"+created.ID, http.NoBody)
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
// Verify Delete
|
|
var count int64
|
|
db.Model(&models.NotificationProvider{}).Count(&count)
|
|
assert.Equal(t, int64(0), count)
|
|
}
|
|
|
|
func TestNotificationProviderHandler_Templates(t *testing.T) {
|
|
r, _ := setupNotificationProviderTest(t)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/v1/notifications/templates", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var templates []map[string]string
|
|
err := json.Unmarshal(w.Body.Bytes(), &templates)
|
|
require.NoError(t, err)
|
|
assert.Len(t, templates, 3)
|
|
}
|
|
|
|
func TestNotificationProviderHandler_Test(t *testing.T) {
|
|
r, _ := setupNotificationProviderTest(t)
|
|
|
|
// Test with invalid provider (should fail validation or service check)
|
|
// Since we don't have a real shoutrrr backend mocked easily here without more work,
|
|
// we expect it might fail or pass depending on service implementation.
|
|
// Looking at service code (not shown but assumed), TestProvider likely calls shoutrrr.Send.
|
|
// If URL is invalid, it should error.
|
|
|
|
provider := models.NotificationProvider{
|
|
Type: "discord",
|
|
URL: "invalid-url",
|
|
}
|
|
body, _ := json.Marshal(provider)
|
|
req, _ := http.NewRequest("POST", "/api/v1/notifications/providers/test", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
// It should probably fail with 400
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestNotificationProviderHandler_Errors(t *testing.T) {
|
|
r, _ := setupNotificationProviderTest(t)
|
|
|
|
// Create Invalid JSON
|
|
req, _ := http.NewRequest("POST", "/api/v1/notifications/providers", bytes.NewBuffer([]byte("invalid")))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
|
|
// Update Invalid JSON
|
|
req, _ = http.NewRequest("PUT", "/api/v1/notifications/providers/123", bytes.NewBuffer([]byte("invalid")))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
|
|
// Test Invalid JSON
|
|
req, _ = http.NewRequest("POST", "/api/v1/notifications/providers/test", bytes.NewBuffer([]byte("invalid")))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestNotificationProviderHandler_InvalidCustomTemplate_Rejects(t *testing.T) {
|
|
r, _ := setupNotificationProviderTest(t)
|
|
|
|
// Create with invalid custom template should return 400
|
|
provider := models.NotificationProvider{
|
|
Name: "Bad",
|
|
Type: "webhook",
|
|
URL: "http://example.com",
|
|
Template: "custom",
|
|
Config: `{"broken": "{{.Title"}`,
|
|
}
|
|
body, _ := json.Marshal(provider)
|
|
req, _ := http.NewRequest("POST", "/api/v1/notifications/providers", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
|
|
// Create valid and then attempt update to invalid custom template
|
|
provider = models.NotificationProvider{
|
|
Name: "Good",
|
|
Type: "webhook",
|
|
URL: "http://example.com",
|
|
Template: "minimal",
|
|
}
|
|
body, _ = json.Marshal(provider)
|
|
req, _ = http.NewRequest("POST", "/api/v1/notifications/providers", bytes.NewBuffer(body))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusCreated, w.Code)
|
|
var created models.NotificationProvider
|
|
_ = json.Unmarshal(w.Body.Bytes(), &created)
|
|
|
|
created.Template = "custom"
|
|
created.Config = `{"broken": "{{.Title"}`
|
|
body, _ = json.Marshal(created)
|
|
req, _ = http.NewRequest("PUT", "/api/v1/notifications/providers/"+created.ID, bytes.NewBuffer(body))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestNotificationProviderHandler_Preview(t *testing.T) {
|
|
r, _ := setupNotificationProviderTest(t)
|
|
|
|
// Minimal template preview
|
|
provider := models.NotificationProvider{
|
|
Type: "webhook",
|
|
URL: "http://example.com",
|
|
Template: "minimal",
|
|
}
|
|
body, _ := json.Marshal(provider)
|
|
req, _ := http.NewRequest("POST", "/api/v1/notifications/providers/preview", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]any
|
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, resp, "rendered")
|
|
assert.Contains(t, resp, "parsed")
|
|
|
|
// Invalid template should not succeed
|
|
provider.Config = `{"broken": "{{.Title"}`
|
|
provider.Template = "custom"
|
|
body, _ = json.Marshal(provider)
|
|
req, _ = http.NewRequest("POST", "/api/v1/notifications/providers/preview", bytes.NewBuffer(body))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestNotificationProviderHandler_CreateRejectsDiscordIPHost(t *testing.T) {
|
|
r, _ := setupNotificationProviderTest(t)
|
|
|
|
provider := models.NotificationProvider{
|
|
Name: "Discord IP",
|
|
Type: "discord",
|
|
URL: "https://203.0.113.10/api/webhooks/123456/token_abc",
|
|
}
|
|
body, _ := json.Marshal(provider)
|
|
req, _ := http.NewRequest("POST", "/api/v1/notifications/providers", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
assert.Contains(t, w.Body.String(), "invalid Discord webhook URL")
|
|
assert.Contains(t, w.Body.String(), "IP address hosts are not allowed")
|
|
}
|
|
|
|
func TestNotificationProviderHandler_CreateAcceptsDiscordHostname(t *testing.T) {
|
|
r, _ := setupNotificationProviderTest(t)
|
|
|
|
provider := models.NotificationProvider{
|
|
Name: "Discord Host",
|
|
Type: "discord",
|
|
URL: "https://discord.com/api/webhooks/123456/token_abc",
|
|
}
|
|
body, _ := json.Marshal(provider)
|
|
req, _ := http.NewRequest("POST", "/api/v1/notifications/providers", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusCreated, w.Code)
|
|
}
|