Fix the Servers page failing when no servers are configured
CI / test (push) Successful in 54s
Release / release (push) Successful in 2m31s
Docker / image (push) Successful in 2m52s

An empty server list was sent as null, so the page threw on .length and
showed only the loading placeholder. The API now always returns a list,
the page tolerates null, and tests cover the empty config and the token.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-26 23:58:26 -06:00
1 parent 8f01c6213f
commit 4afbf2d266
4 files changed
+78 -3

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ func (a *Admin) overview(w http.ResponseWriter, r *http.Request) {
for _, s := range snap.Servers {
stats[s.Name] = s
}
var servers []serverView
servers := []serverView{}
for _, s := range a.Cfg.Current().Servers {
v := serverView{Server: s}
if h, ok := states[s.Name]; ok {
+72
View File
@@ -0,0 +1,72 @@
package admin
import (
"io"
"log/slog"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"git.veillax.com/Veillax/spinner/internal/config"
"git.veillax.com/Veillax/spinner/internal/health"
"git.veillax.com/Veillax/spinner/internal/proxy"
)
func newAdmin(t *testing.T, cfg string) http.Handler {
t.Helper()
log := slog.New(slog.NewTextHandler(io.Discard, nil))
path := filepath.Join(t.TempDir(), "spinner.json")
if err := os.WriteFile(path, []byte(cfg), 0o600); err != nil {
t.Fatal(err)
}
m, err := config.NewManager(path, log)
if err != nil {
t.Fatal(err)
}
h := health.New(log)
return (&Admin{Cfg: m, Health: h, Proxy: proxy.New(m, h, log), Token: "secret"}).Handler()
}
func get(h http.Handler, path, token string) *httptest.ResponseRecorder {
req := httptest.NewRequest("GET", path, nil)
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
return rec
}
// The page reads .length on these lists, so an empty config must give [] and never null.
func TestEmptyConfigGivesEmptyLists(t *testing.T) {
for _, cfg := range []string{`{}`, `{"servers":[]}`} {
h := newAdmin(t, cfg)
if body := get(h, "/api/overview", "secret").Body.String(); !strings.Contains(body, `"servers":[]`) {
t.Errorf("%s: overview servers not an empty list: %s", cfg, body)
}
if body := get(h, "/api/config", "secret").Body.String(); !strings.Contains(body, `"servers":[]`) {
t.Errorf("%s: config servers not an empty list: %s", cfg, body)
}
}
}
func TestAuthRequired(t *testing.T) {
h := newAdmin(t, `{}`)
for _, p := range []string{"/api/overview", "/api/config", "/metrics"} {
if c := get(h, p, "").Code; c != http.StatusUnauthorized {
t.Errorf("%s without token: %d", p, c)
}
if c := get(h, p, "wrong").Code; c != http.StatusUnauthorized {
t.Errorf("%s with wrong token: %d", p, c)
}
if c := get(h, p, "secret").Code; c != http.StatusOK {
t.Errorf("%s with token: %d", p, c)
}
}
if c := get(h, "/", "").Code; c != http.StatusOK {
t.Errorf("page should load without a token: %d", c)
}
}