Files
Charon/backend/internal/api/handlers/handlers_test.go
GitHub Actions d7939bed70 feat: add ManualDNSChallenge component and related hooks for manual DNS challenge management
- Implemented `useManualChallenge`, `useChallengePoll`, and `useManualChallengeMutations` hooks for managing manual DNS challenges.
- Created tests for the `useManualChallenge` hooks to ensure correct fetching and mutation behavior.
- Added `ManualDNSChallenge` component for displaying challenge details and actions.
- Developed end-to-end tests for the Manual DNS Provider feature, covering provider selection, challenge UI, and accessibility compliance.
- Included error handling tests for verification failures and network errors.
2026-01-12 04:01:40 +00:00

435 lines
12 KiB
Go

package handlers_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"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 setupTestDB(t *testing.T) *gorm.DB {
db := handlers.OpenTestDB(t)
// Auto migrate all models that handlers depend on
_ = db.AutoMigrate(
&models.ProxyHost{},
&models.Location{},
&models.RemoteServer{},
&models.ImportSession{},
&models.Notification{},
&models.NotificationProvider{},
)
return db
}
func TestRemoteServerHandler_List(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
// Create test server
server := &models.RemoteServer{
UUID: uuid.NewString(),
Name: "Test Server",
Provider: "docker",
Host: "localhost",
Port: 8080,
Enabled: true,
}
db.Create(server)
ns := services.NewNotificationService(db)
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test List
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/remote-servers", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var servers []models.RemoteServer
err := json.Unmarshal(w.Body.Bytes(), &servers)
assert.NoError(t, err)
assert.Len(t, servers, 1)
assert.Equal(t, "Test Server", servers[0].Name)
}
func TestRemoteServerHandler_Create(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
ns := services.NewNotificationService(db)
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Create
serverData := map[string]any{
"name": "New Server",
"provider": "generic",
"host": "192.168.1.100",
"port": 3000,
"enabled": true,
}
body, _ := json.Marshal(serverData)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/remote-servers", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var server models.RemoteServer
err := json.Unmarshal(w.Body.Bytes(), &server)
assert.NoError(t, err)
assert.Equal(t, "New Server", server.Name)
assert.NotEmpty(t, server.UUID)
}
func TestRemoteServerHandler_TestConnection(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
// Create test server
server := &models.RemoteServer{
UUID: uuid.NewString(),
Name: "Test Server",
Provider: "docker",
Host: "localhost",
Port: 99999, // Invalid port to test failure
Enabled: true,
}
db.Create(server)
ns := services.NewNotificationService(db)
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test connection
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/remote-servers/"+server.UUID+"/test", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var result map[string]any
err := json.Unmarshal(w.Body.Bytes(), &result)
assert.NoError(t, err)
assert.False(t, result["reachable"].(bool))
assert.NotEmpty(t, result["error"])
}
func TestRemoteServerHandler_Get(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
// Create test server
server := &models.RemoteServer{
UUID: uuid.NewString(),
Name: "Test Server",
Provider: "docker",
Host: "localhost",
Port: 8080,
Enabled: true,
}
db.Create(server)
ns := services.NewNotificationService(db)
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Get
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/remote-servers/"+server.UUID, http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var fetched models.RemoteServer
err := json.Unmarshal(w.Body.Bytes(), &fetched)
assert.NoError(t, err)
assert.Equal(t, server.UUID, fetched.UUID)
}
func TestRemoteServerHandler_Update(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
// Create test server
server := &models.RemoteServer{
UUID: uuid.NewString(),
Name: "Test Server",
Provider: "docker",
Host: "localhost",
Port: 8080,
Enabled: true,
}
db.Create(server)
ns := services.NewNotificationService(db)
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Update
updateData := map[string]any{
"name": "Updated Server",
"provider": "generic",
"host": "10.0.0.1",
"port": 9000,
"enabled": false,
}
body, _ := json.Marshal(updateData)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/api/v1/remote-servers/"+server.UUID, bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var updated models.RemoteServer
err := json.Unmarshal(w.Body.Bytes(), &updated)
assert.NoError(t, err)
assert.Equal(t, "Updated Server", updated.Name)
assert.Equal(t, "generic", updated.Provider)
assert.False(t, updated.Enabled)
}
func TestRemoteServerHandler_Delete(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
// Create test server
server := &models.RemoteServer{
UUID: uuid.NewString(),
Name: "Test Server",
Provider: "docker",
Host: "localhost",
Port: 8080,
Enabled: true,
}
db.Create(server)
ns := services.NewNotificationService(db)
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Delete
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/remote-servers/"+server.UUID, http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNoContent, w.Code)
// Verify Delete
w2 := httptest.NewRecorder()
req2, _ := http.NewRequest("GET", "/api/v1/remote-servers/"+server.UUID, http.NoBody)
router.ServeHTTP(w2, req2)
assert.Equal(t, http.StatusNotFound, w2.Code)
}
func TestProxyHostHandler_List(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
// Create test proxy host
host := &models.ProxyHost{
UUID: uuid.NewString(),
Name: "Test Host",
DomainNames: "test.local",
ForwardScheme: "http",
ForwardHost: "localhost",
ForwardPort: 3000,
Enabled: true,
}
db.Create(host)
ns := services.NewNotificationService(db)
handler := handlers.NewProxyHostHandler(db, nil, ns, nil)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test List
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/proxy-hosts", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var hosts []models.ProxyHost
err := json.Unmarshal(w.Body.Bytes(), &hosts)
assert.NoError(t, err)
assert.Len(t, hosts, 1)
assert.Equal(t, "Test Host", hosts[0].Name)
}
func TestProxyHostHandler_Create(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
ns := services.NewNotificationService(db)
handler := handlers.NewProxyHostHandler(db, nil, ns, nil)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Test Create
hostData := map[string]any{
"name": "New Host",
"domain_names": "new.local",
"forward_scheme": "http",
"forward_host": "192.168.1.200",
"forward_port": 8080,
"enabled": true,
}
body, _ := json.Marshal(hostData)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/proxy-hosts", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var host models.ProxyHost
err := json.Unmarshal(w.Body.Bytes(), &host)
assert.NoError(t, err)
assert.Equal(t, "New Host", host.Name)
assert.Equal(t, "new.local", host.DomainNames)
assert.NotEmpty(t, host.UUID)
}
func TestProxyHostHandler_PartialUpdate_DoesNotWipeFields(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
// Seed a proxy host
original := &models.ProxyHost{
UUID: uuid.NewString(),
Name: "Bazarr",
DomainNames: "bazarr.example.com",
ForwardScheme: "http",
ForwardHost: "10.0.0.20",
ForwardPort: 6767,
Enabled: true,
}
db.Create(original)
ns := services.NewNotificationService(db)
handler := handlers.NewProxyHostHandler(db, nil, ns, nil)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Perform partial update: only toggle enabled=false
body := bytes.NewBufferString(`{"enabled": false}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/api/v1/proxy-hosts/"+original.UUID, body)
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var updated models.ProxyHost
err := json.Unmarshal(w.Body.Bytes(), &updated)
assert.NoError(t, err)
// Validate that only 'enabled' changed; other fields remain intact
assert.Equal(t, false, updated.Enabled)
assert.Equal(t, "Bazarr", updated.Name)
assert.Equal(t, "bazarr.example.com", updated.DomainNames)
assert.Equal(t, "http", updated.ForwardScheme)
assert.Equal(t, "10.0.0.20", updated.ForwardHost)
assert.Equal(t, 6767, updated.ForwardPort)
// Fetch via GET to ensure DB persisted state correctly
w2 := httptest.NewRecorder()
req2, _ := http.NewRequest("GET", "/api/v1/proxy-hosts/"+original.UUID, http.NoBody)
router.ServeHTTP(w2, req2)
assert.Equal(t, http.StatusOK, w2.Code)
var fetched models.ProxyHost
err = json.Unmarshal(w2.Body.Bytes(), &fetched)
assert.NoError(t, err)
assert.Equal(t, false, fetched.Enabled)
assert.Equal(t, "Bazarr", fetched.Name)
assert.Equal(t, "bazarr.example.com", fetched.DomainNames)
assert.Equal(t, 6767, fetched.ForwardPort)
}
func TestHealthHandler(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
router := gin.New()
router.GET("/health", handlers.HealthHandler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/health", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var result map[string]string
err := json.Unmarshal(w.Body.Bytes(), &result)
assert.NoError(t, err)
assert.Equal(t, "ok", result["status"])
}
func TestRemoteServerHandler_Errors(t *testing.T) {
t.Parallel()
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
ns := services.NewNotificationService(db)
handler := handlers.NewRemoteServerHandler(services.NewRemoteServerService(db), ns)
router := gin.New()
handler.RegisterRoutes(router.Group("/api/v1"))
// Get non-existent
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/remote-servers/non-existent", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
// Update non-existent
w = httptest.NewRecorder()
req, _ = http.NewRequest("PUT", "/api/v1/remote-servers/non-existent", strings.NewReader(`{}`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
// Delete non-existent
w = httptest.NewRecorder()
req, _ = http.NewRequest("DELETE", "/api/v1/remote-servers/non-existent", http.NoBody)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}