Files
2026-08-28 23:00:02 +12:00

143 lines
5.0 KiB
Go

package api
import (
"log/slog"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/ponzischeme89/memby/server/internal/store"
)
func TestRequestLogLevel(t *testing.T) {
tests := []struct {
path string
status int
want slog.Level
}{
{"/healthz", http.StatusOK, slog.LevelDebug},
{"/v1/status", http.StatusOK, slog.LevelDebug},
{"/v1/images/123/primary", http.StatusOK, slog.LevelDebug},
{"/admin/api/status", http.StatusOK, slog.LevelDebug},
{"/admin/api/status", http.StatusUnauthorized, slog.LevelWarn},
{"/v1/home", http.StatusOK, slog.LevelInfo},
{"/v1/images/123/primary", http.StatusNotFound, slog.LevelWarn},
{"/v1/home", http.StatusServiceUnavailable, slog.LevelError},
}
for _, test := range tests {
if got := requestLogLevel(test.path, test.status); got != test.want {
t.Errorf("requestLogLevel(%q, %d) = %v, want %v", test.path, test.status, got, test.want)
}
}
}
func TestComponentNamesThePartOfTheAppARouteBelongsTo(t *testing.T) {
tests := map[string]string{
"/healthz": "health",
"/v1/status": "status",
"/v1/home": "home",
"/v1/preferences": "settings",
"/v1/screensaver": "screensaver",
"/v1/auth/login": "auth",
"/v1/auth/devices/tv-1": "devices",
"/v1/search": "search",
"/v1/genres/Comedy/items": "search",
"/v1/library/items": "search",
"/v1/items/42": "details",
"/v1/items/42/related": "details",
"/v1/items/42/playback": "playback",
"/v1/items/42/next": "playback",
"/v1/items/42/trailers/resolve": "playback",
"/v1/items/42/trailers/report": "playback",
"/v1/items/42/subtitles/search": "playback",
"/v1/items/42/trickplay": "playback",
"/v1/items/42/trickplay/12.jpg": "playback",
"/v1/playback/started": "playback",
"/v1/magic": "playback",
"/v1/images/42/primary": "artwork",
"/v1/recommendations": "recommendations",
"/v1/for-you": "recommendations",
"/v1/my-shows": "my-shows",
"/admin/api/status": "admin",
"/hooks/radarr": "webhooks",
"/hooks/sonarr": "webhooks",
"/install": "installer",
"/updates/latest.apk": "updates",
"/something-nobody-has-written": "api",
}
for path, want := range tests {
if got := componentFor(path); got != want {
t.Errorf("componentFor(%q) = %q, want %q", path, got, want)
}
}
}
func TestIdentifyNamesTheViewerAndTelevision(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "/v1/home", nil)
request.Header.Set("X-Memby-Version", "0.1.60")
request, identity := withRequestIdentity(request)
identify(request.Context(), store.Session{
Username: "matt", DeviceName: "Living room", ClientProtocol: "1",
})
if identity.component != "home" || identity.user != "matt" || identity.device != "Living room" {
t.Fatalf("identity was not filled in: %+v", identity)
}
// The header the TV actually sent must survive a session that has no version yet.
if identity.client != "0.1.60" || identity.protocol != "1" {
t.Fatalf("client identity was lost: %+v", identity)
}
}
// A device with no name at all must still be identifiable, or an old APK's traffic
// becomes anonymous exactly when someone is trying to work out which television it is.
func TestIdentifyFallsBackToTheDeviceID(t *testing.T) {
request, identity := withRequestIdentity(httptest.NewRequest(http.MethodGet, "/v1/home", nil))
identify(request.Context(), store.Session{DeviceID: "tv-1"})
if identity.device != "tv-1" {
t.Fatalf("device = %q", identity.device)
}
}
func TestPlaybackTitlesNamesReportsAndStaysBounded(t *testing.T) {
var titles playbackTitles
titles.remember("42", "Dune")
if got := titles.name("42"); got != "Dune" {
t.Fatalf("remembered title = %q", got)
}
// An unknown item reports as itself rather than as an empty field.
if got := titles.name("99"); got != "99" {
t.Fatalf("unknown title = %q", got)
}
for i := range rememberedTitles + 10 {
titles.remember(strconv.Itoa(1000+i), "Title")
}
if len(titles.titles) > rememberedTitles {
t.Fatalf("title memory grew to %d entries", len(titles.titles))
}
}
func TestWatchedPercentReadsAsProgress(t *testing.T) {
if got := watchedPercent(30_000, 60_000); got != "50%" {
t.Errorf("half watched = %q", got)
}
// Emby's reported position can overshoot a runtime by a frame or two.
if got := watchedPercent(61_000, 60_000); got != "100%" {
t.Errorf("overshoot = %q", got)
}
if got := watchedPercent(30_000, 0); got != "unknown" {
t.Errorf("unknown runtime = %q", got)
}
}
func TestClientLogValueIsNeverBlank(t *testing.T) {
if got := clientLogValue(""); got != "unknown" {
t.Fatalf("blank identity logged as %q", got)
}
if got := clientLogValue("0.1.60"); got != "0.1.60" {
t.Fatalf("reported identity changed to %q", got)
}
}