Files
ClusterCanvas/service/internal/websrv/handler_test.go
T
Squid 2605c3b346 Add users and network settings tabs with admin protections.
List and delete accounts with last-admin and Administrators-group guards, and expose editable network settings via the configuration UI.
2026-07-18 15:18:01 +02:00

129 lines
3.8 KiB
Go

package websrv
import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestNewHandlerRejectsMissingWebDir(t *testing.T) {
t.Parallel()
_, err := NewHandler(filepath.Join(t.TempDir(), "missing"), "http://127.0.0.1:8080")
if err == nil {
t.Fatal("expected error for missing webdir")
}
}
func TestNewHandlerRejectsInvalidAPIURL(t *testing.T) {
t.Parallel()
webDir := t.TempDir()
_, err := NewHandler(webDir, "not-a-url")
if err == nil {
t.Fatal("expected error for invalid API_URL")
}
}
func TestSPAServesIndexAndAssets(t *testing.T) {
t.Parallel()
webDir := t.TempDir()
mustWriteFile(t, filepath.Join(webDir, "index.html"), "<html>home</html>")
mustWriteFile(t, filepath.Join(webDir, "assets", "app.js"), "console.log(1)")
api := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusNoContent)
}))
t.Cleanup(api.Close)
handler, err := NewHandler(webDir, api.URL)
if err != nil {
t.Fatalf("NewHandler: %v", err)
}
indexRecorder := httptest.NewRecorder()
handler.ServeHTTP(indexRecorder, httptest.NewRequest(http.MethodGet, "/", nil))
if indexRecorder.Code != http.StatusOK {
t.Fatalf("GET / status = %d", indexRecorder.Code)
}
if !strings.Contains(indexRecorder.Body.String(), "home") {
t.Fatalf("GET / body = %q", indexRecorder.Body.String())
}
assetRecorder := httptest.NewRecorder()
handler.ServeHTTP(assetRecorder, httptest.NewRequest(http.MethodGet, "/assets/app.js", nil))
if assetRecorder.Code != http.StatusOK {
t.Fatalf("GET /assets/app.js status = %d", assetRecorder.Code)
}
if assetRecorder.Body.String() != "console.log(1)" {
t.Fatalf("asset body = %q", assetRecorder.Body.String())
}
spaRecorder := httptest.NewRecorder()
handler.ServeHTTP(spaRecorder, httptest.NewRequest(http.MethodGet, "/settings", nil))
if spaRecorder.Code != http.StatusOK {
t.Fatalf("GET /settings status = %d", spaRecorder.Code)
}
if !strings.Contains(spaRecorder.Body.String(), "home") {
t.Fatalf("SPA fallback body = %q", spaRecorder.Body.String())
}
}
func TestProxyForwardsAPIAndHealth(t *testing.T) {
t.Parallel()
webDir := t.TempDir()
mustWriteFile(t, filepath.Join(webDir, "index.html"), "<html>home</html>")
var seenPaths []string
var seenForwardedHost string
api := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
seenPaths = append(seenPaths, request.URL.Path)
seenForwardedHost = request.Header.Get("X-Forwarded-Host")
writer.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(writer, `{"ok":true}`)
}))
t.Cleanup(api.Close)
handler, err := NewHandler(webDir, api.URL)
if err != nil {
t.Fatalf("NewHandler: %v", err)
}
healthRecorder := httptest.NewRecorder()
handler.ServeHTTP(healthRecorder, httptest.NewRequest(http.MethodGet, "/health", nil))
if healthRecorder.Code != http.StatusOK {
t.Fatalf("GET /health status = %d", healthRecorder.Code)
}
apiRequest := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil)
apiRequest.Host = "admin.example.test:5173"
apiRecorder := httptest.NewRecorder()
handler.ServeHTTP(apiRecorder, apiRequest)
if apiRecorder.Code != http.StatusOK {
t.Fatalf("GET /api/v1/status status = %d", apiRecorder.Code)
}
if len(seenPaths) != 2 || seenPaths[0] != "/health" || seenPaths[1] != "/api/v1/status" {
t.Fatalf("proxied paths = %#v", seenPaths)
}
if seenForwardedHost != "admin.example.test:5173" {
t.Fatalf("X-Forwarded-Host = %q, want browser host", seenForwardedHost)
}
}
func mustWriteFile(t *testing.T, path string, contents string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
t.Fatalf("write: %v", err)
}
}