Unifies the two previously independent email subsystems — MailService (net/smtp transport) and NotificationService (HTTP-based providers) — so email can participate in the notification dispatch pipeline. Key changes: - SendEmail signature updated to accept context.Context and []string recipients to enable timeout propagation and multi-recipient dispatch - NotificationService.dispatchEmail() wires MailService as a first-class provider type with IsConfigured() guard and 30s context timeout - 'email' added to isSupportedNotificationProviderType() and supportsJSONTemplates() returns false for email (plain/HTML only) - settings_handler.go test-email endpoint updated to new SendEmail API - Frontend: 'email' added to provider type union in notifications.ts, Notifications.tsx shows recipient field and hides URL/token fields for email providers - All existing tests updated to match new SendEmail signature - New tests added covering dispatchEmail paths, IsConfigured guards, recipient validation, and context timeout behaviour Also fixes confirmed false-positive CodeQL go/email-injection alerts: - smtp.SendMail, sendSSL w.Write, and sendSTARTTLS w.Write sites now carry inline codeql[go/email-injection] annotations as required by the CodeQL same-line suppression spec; preceding-line annotations silently no-op in current CodeQL versions - auth_handler.go c.SetCookie annotated for intentional Secure=false on local non-HTTPS loopback (go/cookie-secure-not-set warning only) Closes part of #800
130 lines
3.7 KiB
Go
130 lines
3.7 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"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/api/handlers"
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/Wikid82/charon/backend/internal/services"
|
|
)
|
|
|
|
func setupRemoteServerTest_New(t *testing.T) (*gin.Engine, *handlers.RemoteServerHandler) {
|
|
t.Helper()
|
|
db := setupTestDB(t)
|
|
// Ensure RemoteServer table exists
|
|
_ = db.AutoMigrate(&models.RemoteServer{})
|
|
|
|
ns := services.NewNotificationService(db, nil)
|
|
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
|
|
|
|
r := gin.Default()
|
|
api := r.Group("/api/v1")
|
|
servers := api.Group("/remote-servers")
|
|
servers.GET("", handler.List)
|
|
servers.POST("", handler.Create)
|
|
servers.GET("/:uuid", handler.Get)
|
|
servers.PUT("/:uuid", handler.Update)
|
|
servers.DELETE("/:uuid", handler.Delete)
|
|
servers.POST("/test", handler.TestConnectionCustom)
|
|
servers.POST("/:uuid/test", handler.TestConnection)
|
|
|
|
return r, handler
|
|
}
|
|
|
|
func TestRemoteServerHandler_TestConnectionCustom(t *testing.T) {
|
|
r, _ := setupRemoteServerTest_New(t)
|
|
|
|
// Test with a likely closed port
|
|
payload := map[string]any{
|
|
"host": "127.0.0.1",
|
|
"port": 54321,
|
|
}
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", "/api/v1/remote-servers/test", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
var result map[string]any
|
|
err := json.Unmarshal(w.Body.Bytes(), &result)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, false, result["reachable"])
|
|
assert.NotEmpty(t, result["error"])
|
|
}
|
|
|
|
func TestRemoteServerHandler_FullCRUD(t *testing.T) {
|
|
r, _ := setupRemoteServerTest_New(t)
|
|
|
|
// Create
|
|
rs := models.RemoteServer{
|
|
Name: "Test Server CRUD",
|
|
Host: "192.168.1.100",
|
|
Port: 22,
|
|
Provider: "manual",
|
|
}
|
|
body, _ := json.Marshal(rs)
|
|
req, _ := http.NewRequest("POST", "/api/v1/remote-servers", bytes.NewBuffer(body))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusCreated, w.Code)
|
|
|
|
var created models.RemoteServer
|
|
err := json.Unmarshal(w.Body.Bytes(), &created)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, rs.Name, created.Name)
|
|
assert.NotEmpty(t, created.UUID)
|
|
|
|
// List
|
|
req, _ = http.NewRequest("GET", "/api/v1/remote-servers", http.NoBody)
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
// Get
|
|
req, _ = http.NewRequest("GET", "/api/v1/remote-servers/"+created.UUID, http.NoBody)
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
// Update
|
|
created.Name = "Updated Server CRUD"
|
|
body, _ = json.Marshal(created)
|
|
req, _ = http.NewRequest("PUT", "/api/v1/remote-servers/"+created.UUID, bytes.NewBuffer(body))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
// Delete
|
|
req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/"+created.UUID, http.NoBody)
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusNoContent, w.Code)
|
|
|
|
// Create - Invalid JSON
|
|
req, _ = http.NewRequest("POST", "/api/v1/remote-servers", bytes.NewBuffer([]byte("invalid json")))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
|
|
// Update - Not Found
|
|
req, _ = http.NewRequest("PUT", "/api/v1/remote-servers/non-existent-uuid", bytes.NewBuffer(body))
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
|
|
// Delete - Not Found
|
|
req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/non-existent-uuid", http.NoBody)
|
|
w = httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
}
|