150 lines
5.2 KiB
Go
150 lines
5.2 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/images/42/primary": "artwork",
|
|
"/v1/recommendations": "recommendations",
|
|
"/v1/for-you": "recommendations",
|
|
"/v1/my-shows": "my-shows",
|
|
"/admin/api/status": "admin",
|
|
"/hooks/radarr": "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 TestRequestClientIPPrefersOriginalForwardedAddress(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/items/42/trailers/report", nil)
|
|
request.RemoteAddr = "10.0.0.2:41234"
|
|
request.Header.Set("X-Forwarded-For", "203.0.113.9, 10.0.0.2")
|
|
if got := requestClientIP(request); got != "203.0.113.9" {
|
|
t.Fatalf("client ip = %q", got)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|