Publish current app and server
This commit is contained in:
@@ -6,10 +6,13 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/config"
|
||||
"github.com/ponzischeme89/memby/server/internal/recommend"
|
||||
"github.com/ponzischeme89/memby/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -81,7 +84,9 @@ func TestMaintenanceGateFallsBackToADefaultMessage(t *testing.T) {
|
||||
func TestHealthAndAdminStayReachableDuringMaintenance(t *testing.T) {
|
||||
// Health checks and the admin page sit outside the gate on purpose: they are what
|
||||
// you need most while the app is deliberately down.
|
||||
server := testServer(config.Config{AdminToken: "secret"})
|
||||
server := testServer(config.Config{
|
||||
AdminToken: "secret", ReleasePublishToken: "release-secret",
|
||||
})
|
||||
server.maintenance.set(store.Maintenance{Enabled: true})
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -92,8 +97,9 @@ func TestHealthAndAdminStayReachableDuringMaintenance(t *testing.T) {
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
server.adminRoutes().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/admin/", nil))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("admin page should stay reachable, got %d", rec.Code)
|
||||
if rec.Code != http.StatusFound || rec.Header().Get("Location") != "/admin/features" {
|
||||
t.Fatalf("admin root should stay reachable via library redirect, got %d %q",
|
||||
rec.Code, rec.Header().Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +172,17 @@ func TestAdminIsDisabledWithoutAToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaybackPolicyRejectsUnsafeDuration(t *testing.T) {
|
||||
server := testServer(config.Config{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/admin/api/playback-policy",
|
||||
strings.NewReader(`{"prerollEnabled":true,"prerollDurationMs":500}`))
|
||||
rec := httptest.NewRecorder()
|
||||
server.handleAdminPlaybackPolicy(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminAuthRejectsAWrongToken(t *testing.T) {
|
||||
server := testServer(config.Config{AdminToken: "secret"})
|
||||
handler := server.adminAuth(func(w http.ResponseWriter, _ *http.Request) {
|
||||
@@ -198,12 +215,45 @@ func TestAdminAuthRejectsAWrongToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminPageEstablishesPersistentCookie(t *testing.T) {
|
||||
func TestAdminRuntimeMetricsAreProtectedAndReportHeap(t *testing.T) {
|
||||
server := testServer(config.Config{AdminToken: "secret"})
|
||||
req := httptest.NewRequest(http.MethodGet, "https://memby.local/admin/", nil)
|
||||
unauthorized := httptest.NewRecorder()
|
||||
server.adminRoutes().ServeHTTP(
|
||||
unauthorized,
|
||||
httptest.NewRequest(http.MethodGet, "/admin/api/runtime", nil),
|
||||
)
|
||||
if unauthorized.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("unauthorized runtime status = %d", unauthorized.Code)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/api/runtime", nil)
|
||||
req.Header.Set("Authorization", "Bearer secret")
|
||||
rec := httptest.NewRecorder()
|
||||
server.adminRoutes().ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("runtime status = %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var body adminRuntimeStatus
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Goroutines < 1 || body.HeapInuse == 0 || body.MemoryLimit <= 0 {
|
||||
t.Fatalf("runtime metrics = %+v", body)
|
||||
}
|
||||
if rec.Header().Get("Cache-Control") != "no-store" {
|
||||
t.Fatalf("cache control = %q", rec.Header().Get("Cache-Control"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminPageEstablishesPersistentCookie(t *testing.T) {
|
||||
server := testServer(config.Config{
|
||||
AdminToken: "secret", ReleasePublishToken: "release-secret",
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodGet, "https://memby.local/admin/library", nil)
|
||||
addInstallerSession(t, server, req)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
server.handleAdminPage(rec, req)
|
||||
server.adminRoutes().ServeHTTP(rec, req)
|
||||
|
||||
result := rec.Result()
|
||||
cookies := result.Cookies()
|
||||
@@ -223,7 +273,116 @@ func TestAdminPageEstablishesPersistentCookie(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminAuthAcceptsPersistentCookie(t *testing.T) {
|
||||
server := testServer(config.Config{AdminToken: "secret"})
|
||||
server := testServer(config.Config{
|
||||
AdminToken: "secret", ReleasePublishToken: "release-secret",
|
||||
})
|
||||
handler := server.adminAuth(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/api/status", nil)
|
||||
req.AddCookie(&http.Cookie{Name: adminCookieName, Value: "secret"})
|
||||
addInstallerSession(t, server, req)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("the admin cookie should be accepted, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminPageRequiresDiscreetEmbyGate(t *testing.T) {
|
||||
server := testServer(config.Config{
|
||||
AdminToken: "secret", ReleasePublishToken: "release-secret",
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/logs", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
server.adminRoutes().ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("admin gate status = %d", rec.Code)
|
||||
}
|
||||
body := strings.ToLower(rec.Body.String())
|
||||
for _, forbidden := range []string{"memby", "emby", "installer", "administration", "analytics"} {
|
||||
if strings.Contains(body, forbidden) {
|
||||
t.Fatalf("admin gate disclosed %q before login", forbidden)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(body, "login required to continue.") ||
|
||||
!strings.Contains(body, `name="next" type="hidden" value="/admin/logs"`) {
|
||||
t.Fatalf("admin gate has wrong copy or return destination: %s", rec.Body.String())
|
||||
}
|
||||
if len(rec.Result().Cookies()) != 0 {
|
||||
t.Fatal("admin gate issued an admin cookie before Emby authentication")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminPagesUseRealRoutes(t *testing.T) {
|
||||
server := testServer(config.Config{
|
||||
AdminToken: "secret", ReleasePublishToken: "release-secret",
|
||||
})
|
||||
for _, page := range []string{
|
||||
"library", "recommendations", "requests", "maintenance",
|
||||
"updates", "engagement", "imports", "logs",
|
||||
} {
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/"+page, nil)
|
||||
addInstallerSession(t, server, req)
|
||||
rec := httptest.NewRecorder()
|
||||
server.adminRoutes().ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("/admin/%s = %d", page, rec.Code)
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, `href="/admin/logs"`) ||
|
||||
!strings.Contains(body, `data-admin-page="`+page+`"`) {
|
||||
t.Fatalf("/admin/%s does not contain routed navigation/page marker", page)
|
||||
}
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/not-a-page", nil)
|
||||
addInstallerSession(t, server, req)
|
||||
rec := httptest.NewRecorder()
|
||||
server.adminRoutes().ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("unknown admin page = %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallerDestinationAllowsOnlyKnownAdminPages(t *testing.T) {
|
||||
if got := cleanInstallerDestination("/admin/recommendations"); got != "/admin/recommendations" {
|
||||
t.Fatalf("recommendation destination = %q", got)
|
||||
}
|
||||
for _, unsafe := range []string{
|
||||
"/admin/not-real", "/admin/../updates/latest.apk", "/admin/logs?next=https://evil.test",
|
||||
} {
|
||||
if got := cleanInstallerDestination(unsafe); got != "/install" {
|
||||
t.Fatalf("unsafe destination %q accepted as %q", unsafe, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminEligibleRowsExplainPlacement(t *testing.T) {
|
||||
item := store.PreparedForYouItem{
|
||||
RuntimeMinutes: 28, CompatibilityScore: 0.8,
|
||||
ReasonKind: "completed-title", ReasonGenre: "Drama",
|
||||
ReasonSourceTitle: "Arrival",
|
||||
}
|
||||
rows := adminEligibleRows(item, recommend.Item{Type: "Series"})
|
||||
for _, wanted := range []string{
|
||||
"Top picks for you", "Because you finished Arrival", "More Drama for you",
|
||||
"Plays well on this TV", "One episode before bed", "Hidden in your library",
|
||||
} {
|
||||
if !slices.Contains(rows, wanted) {
|
||||
t.Fatalf("eligible rows %v missing %q", rows, wanted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminCookieCannotOutliveEmbyGate(t *testing.T) {
|
||||
server := testServer(config.Config{
|
||||
AdminToken: "secret", ReleasePublishToken: "release-secret",
|
||||
})
|
||||
handler := server.adminAuth(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
@@ -233,8 +392,8 @@ func TestAdminAuthAcceptsPersistentCookie(t *testing.T) {
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("the admin cookie should be accepted, got %d", rec.Code)
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("admin cookie without Emby gate got %d, want 401", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user