package server import ( "encoding/json" "io" "io/fs" "net/http" "net/http/httptest" "strings" "testing" "time" "gitea.tzgyn.dev/TZGyn/tunnel/internal/dashboard" ) func TestAdminOverview(t *testing.T) { store, err := OpenStore("") if err != nil { t.Fatal(err) } api := &API{ Store: store, Hub: NewHub(store), Domain: "opentunnel.test", AdminToken: "admin-secret", StartedAt: time.Now().Add(-90 * time.Second), Version: "test", } srv := httptest.NewServer(api) defer srv.Close() if _, _, err := store.createTunnel("opentunnel.test"); err != nil { t.Fatal(err) } // no token configured → 503 noToken := &API{Store: store, Hub: NewHub(store), Domain: "opentunnel.test", StartedAt: time.Now()} noTokenSrv := httptest.NewServer(noToken) defer noTokenSrv.Close() resp, err := http.Get(noTokenSrv.URL + "/api/admin/overview") if err != nil { t.Fatal(err) } if resp.StatusCode != 503 { t.Errorf("unconfigured admin token: got %d, want 503", resp.StatusCode) } resp.Body.Close() // missing and wrong token → 401 for _, header := range []string{"", "Bearer nope"} { req, _ := http.NewRequest("GET", srv.URL+"/api/admin/overview", nil) if header != "" { req.Header.Set("Authorization", header) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } if resp.StatusCode != 401 { t.Errorf("auth %q: got %d, want 401", header, resp.StatusCode) } resp.Body.Close() } // correct token → 200 with the tunnel and server info req, _ := http.NewRequest("GET", srv.URL+"/api/admin/overview", nil) req.Header.Set("Authorization", "Bearer admin-secret") resp, err = http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("admin overview: got %d, want 200", resp.StatusCode) } var overview adminOverview if err := json.NewDecoder(resp.Body).Decode(&overview); err != nil { t.Fatal(err) } if overview.Server.Domain != "opentunnel.test" || overview.Server.Version != "test" { t.Errorf("server info: %+v", overview.Server) } if overview.Server.UptimeSeconds < 60 { t.Errorf("uptime: %d", overview.Server.UptimeSeconds) } if overview.Totals.Tunnels != 1 || overview.Totals.Online != 0 { t.Errorf("totals: %+v", overview.Totals) } if len(overview.Tunnels) != 1 || !strings.HasSuffix(overview.Tunnels[0].Hostname, ".opentunnel.test") { t.Errorf("tunnels: %+v", overview.Tunnels) } if overview.Tunnels[0].State != "offline" { t.Errorf("tunnel state: %q", overview.Tunnels[0].State) } if overview.CA.Expires == "" { t.Error("CA expiry missing") } } func TestDashboardPage(t *testing.T) { store, err := OpenStore("") if err != nil { t.Fatal(err) } api := &API{Store: store, Hub: NewHub(store), Domain: "opentunnel.test", StartedAt: time.Now()} srv := httptest.NewServer(api) defer srv.Close() if _, err := fs.Stat(dashboard.FS(), "index.html"); err != nil { t.Skip("dashboard not built (run `bun install && bun run build` in web/)") } for _, path := range []string{"/", "/admin"} { resp, err := http.Get(srv.URL + path) if err != nil { t.Fatal(err) } body, _ := io.ReadAll(resp.Body) resp.Body.Close() if resp.StatusCode != 200 || !strings.Contains(resp.Header.Get("Content-Type"), "text/html") { t.Errorf("%s: got %d %q", path, resp.StatusCode, resp.Header.Get("Content-Type")) } if !strings.Contains(string(body), `id="app"`) { t.Errorf("%s: body does not look like the built dashboard", path) } } }