package admin_test import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "git.wntrmute.dev/kyle/crossbar/internal/admin" "git.wntrmute.dev/kyle/crossbar/internal/config" "git.wntrmute.dev/kyle/crossbar/internal/health" ) type fakeHosts map[string]health.Status func (f fakeHosts) All() map[string]health.Status { return f } func testConfig(t *testing.T) *config.Config { c, err := config.Parse(strings.NewReader(` listen = "127.0.0.1:1" [hosts.alpha] base_url = "http://alpha:1" models = { "m" = { } } [hosts.beta] base_url = "http://beta:1" models = { "m" = { } } [routes.r] hosts = ["alpha", "beta"] default_model = "m" `)) if err != nil { t.Fatal(err) } return c } func TestHosts(t *testing.T) { when := time.Date(2026, 9, 25, 8, 0, 0, 0, time.UTC) h := admin.Handler(testConfig(t), fakeHosts{ "alpha": {Healthy: true, Loaded: []string{"m"}, LastOK: when}, "beta": {Healthy: false, LastErr: "HTTP 503"}, }) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/_crossbar/hosts", nil)) if rec.Code != 200 || !strings.HasPrefix(rec.Header().Get("Content-Type"), "application/json") { t.Fatalf("status %d, content-type %q", rec.Code, rec.Header().Get("Content-Type")) } var out map[string]admin.HostView if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatal(err) } if a := out["alpha"]; !a.Healthy || len(a.Loaded) != 1 || a.LastOK != "2026-09-25T08:00:00Z" || a.LastErr != "" { t.Errorf("alpha = %+v", a) } if b := out["beta"]; b.Healthy || b.LastOK != "" || b.LastErr != "HTTP 503" || b.Loaded == nil { t.Errorf("beta = %+v (loaded must be [] not null)", b) } if !strings.Contains(rec.Body.String(), `"loaded":[]`) { t.Errorf("beta.loaded must encode as []: %s", rec.Body.String()) } } func TestRoutes(t *testing.T) { h := admin.Handler(testConfig(t), fakeHosts{}) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/_crossbar/routes", nil)) var out map[string]admin.RouteView if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatalf("%v: %s", err, rec.Body.String()) } r := out["r"] if len(r.Hosts) != 2 || r.Hosts[0] != "alpha" || r.DefaultModel != "m" { t.Errorf("routes = %+v", out) } } func TestMethodsAndUnknown(t *testing.T) { h := admin.Handler(testConfig(t), fakeHosts{}) for _, tc := range []struct { method, path string want int }{ {http.MethodPost, "/_crossbar/hosts", 405}, {http.MethodDelete, "/_crossbar/routes", 405}, {http.MethodGet, "/_crossbar/nope", 404}, {http.MethodGet, "/_crossbar/", 404}, } { rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(tc.method, tc.path, nil)) if rec.Code != tc.want { t.Errorf("%s %s = %d, want %d", tc.method, tc.path, rec.Code, tc.want) } if !strings.HasPrefix(rec.Header().Get("Content-Type"), "application/json") { t.Errorf("%s %s: errors are JSON too", tc.method, tc.path) } } }