// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later package adminui import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "echo-lot.app/server/internal/store" ) func adbFixture(t *testing.T) *Server { t.Helper() s := tokenFixture(t) s.Store.SetADBEndpointRetention(24 * time.Hour) now := time.Now().UTC() for _, e := range []store.ADBEndpoint{ {Device: "dev-a", Host: "10.13.102.128", Port: 45305, DeviceName: "TB330FU", SourceIP: "10.13.102.128", ReportedAt: now.Add(-10 * time.Minute)}, {Device: "dev-b", Host: "10.13.102.55", Port: 5555, SourceIP: "10.13.102.55", ReportedAt: now.Add(-time.Minute)}, } { if err := s.Store.PutADBEndpoint(e); err != nil { t.Fatal(err) } } return s } // A LAN address and a live debug port are exactly what must not be readable by anyone who can // reach the listener — which is how the beacon receiver this replaces worked. func TestADBEndpointsReadRequiresAuth(t *testing.T) { h := adbFixture(t).Handler() req := httptest.NewRequest("GET", "/admin/adb-endpoints", nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized || rec.Header().Get("WWW-Authenticate") == "" { t.Fatalf("unauthenticated: code=%d, want 401 with a challenge", rec.Code) } if rec.Body.Len() > 0 && json.Valid(rec.Body.Bytes()) { t.Fatalf("a refusal returned a JSON body: %s", rec.Body.String()) } req = httptest.NewRequest("GET", "/admin/adb-endpoints", nil) req.SetBasicAuth("admin", "wrong") rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("bad password: code=%d, want 401", rec.Code) } } func TestADBEndpointsReadNewestFirst(t *testing.T) { h := adbFixture(t).Handler() req := httptest.NewRequest("GET", "/admin/adb-endpoints", nil) req.SetBasicAuth("admin", "a-long-test-password") rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("code=%d body=%s", rec.Code, rec.Body.String()) } var rows []adbRow if err := json.Unmarshal(rec.Body.Bytes(), &rows); err != nil { t.Fatal(err) } if len(rows) != 2 { t.Fatalf("got %d rows, want 2: %s", len(rows), rec.Body.String()) } if rows[0].Device != "dev-b" { t.Fatalf("rows are not newest first: %+v", rows) } if rows[0].Host != "10.13.102.55" || rows[0].Port != 5555 || rows[0].SourceIP == "" { t.Fatalf("row is missing what a developer came for: %+v", rows[0]) } // age_s is the field that says whether the port is worth trying at all. if rows[0].AgeS < 50 || rows[0].AgeS > 120 { t.Fatalf("age_s = %d, want roughly 60", rows[0].AgeS) } if rows[1].AgeS <= rows[0].AgeS { t.Fatalf("ages do not follow the ordering: %+v", rows) } } // The card exists so the relay is usable without curl. Rendered here because a template error is // only found when the page is executed, not when it is parsed. func TestDashboardShowsTheRelayToAnAdmin(t *testing.T) { s := adbFixture(t) h := s.Handler() req := httptest.NewRequest("GET", "/", nil) req.AddCookie(&http.Cookie{ Name: sessionCookie, Value: s.Sessions.Issue("local:admin", "admin", true), }) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("dashboard: code=%d", rec.Code) } for _, want := range []string{"Dev relay", "10.13.102.55:5555", "min ago"} { if !strings.Contains(rec.Body.String(), want) { t.Errorf("the dashboard card does not show %q", want) } } // A plain user gets no rows at all — not an empty card, no card. req = httptest.NewRequest("GET", "/", nil) req.AddCookie(&http.Cookie{ Name: sessionCookie, Value: s.Sessions.Issue("oidc#someone", "Someone", false), }) rec = httptest.NewRecorder() h.ServeHTTP(rec, req) if strings.Contains(rec.Body.String(), "10.13.102.55") { t.Fatal("a non-admin session was shown a LAN address") } } // The read is a GET, so a signed-in browser session must not be asked for a CSRF token it has no // form to carry — but a session that is not an administrator is still refused. func TestADBEndpointsReadFromABrowserSession(t *testing.T) { s := adbFixture(t) h := s.Handler() for _, tc := range []struct { name string admin bool wantCode int }{ {"admin", true, http.StatusOK}, {"plain user", false, http.StatusForbidden}, } { req := httptest.NewRequest("GET", "/admin/adb-endpoints", nil) req.AddCookie(&http.Cookie{ Name: sessionCookie, Value: s.Sessions.Issue("local:admin", "admin", tc.admin), }) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != tc.wantCode { t.Errorf("%s: code=%d, want %d (%s)", tc.name, rec.Code, tc.wantCode, rec.Body.String()) } } }