Files
memby/server/internal/library/syncer_test.go
T
ponzischeme89andClaude Opus 5 2ce405c540 Memby v0.1.53: Android TV client plus gateway
Android TV client for Emby (Kotlin, Compose for TV) and the Memby gateway
(Go, Postgres, Redis) that fronts it.

Client:
- Setup, profiles, home rows, Media3 playback, system screensaver (Dream)
- Backend chosen at build time: gateway when memby.gatewayUrl is set,
  otherwise direct to Emby. Both paths stay working.
- Server-composed home rows, rendered verbatim so new row types ship
  without an app release
- Full-screen animated maintenance state, row engagement telemetry

Gateway:
- One request per TV screen; auth, caching, search and row shaping
- Library import from Emby into Postgres (manual, then hourly incremental)
- Recommendations from viewing history (recency-weighted genre affinity)
- Admin page for imports, an offline switch, and per-row analytics
- Video always direct-plays from Emby; only metadata passes through

Identity is com.ponzischeme89.memby throughout, replacing
com.mattcohen.embyclientsname. A changed applicationId installs as a new
app: TVs need a fresh sign-in and the old package uninstalled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:16:20 +12:00

80 lines
2.4 KiB
Go

package library
import (
"encoding/json"
"strings"
"testing"
)
func TestToLibraryItemFlattensColumnsAndKeepsPayload(t *testing.T) {
raw := json.RawMessage(`{
"Id":"42","Name":"Arrival","Type":"Movie","ProductionYear":2016,
"CommunityRating":7.9,"Genres":["Science Fiction"," Drama "],
"Studios":[{"Name":"Paramount"},{"Name":" "}],
"DateCreated":"2024-03-01T10:00:00Z",
"ImageTags":{"Primary":"abc"}
}`)
item, ok := toLibraryItem(raw)
if !ok {
t.Fatal("expected the item to parse")
}
if item.ID != "42" || item.Name != "Arrival" || item.Type != "Movie" {
t.Fatalf("unexpected columns: %+v", item)
}
if *item.ProductionYear != 2016 || *item.CommunityRating != 7.9 {
t.Fatalf("unexpected numbers: %+v", item)
}
// Whitespace-only studio names are dropped, real ones trimmed.
if len(item.Studios) != 1 || item.Studios[0] != "Paramount" {
t.Fatalf("unexpected studios: %v", item.Studios)
}
if len(item.Genres) != 2 || item.Genres[1] != "Drama" {
t.Fatalf("genres should be trimmed: %v", item.Genres)
}
if item.DateCreated == nil || item.DateCreated.Year() != 2024 {
t.Fatalf("unexpected date: %v", item.DateCreated)
}
// The payload must survive byte-identical: it is what the TV receives, and it holds
// fields (image tags, overview) that no column models.
if string(item.Payload) != string(raw) {
t.Fatal("payload was altered")
}
}
func TestToLibraryItemRejectsUnusableRows(t *testing.T) {
for name, raw := range map[string]string{
"malformed": `{"Id":`,
"no id": `{"Name":"Nameless"}`,
} {
if _, ok := toLibraryItem(json.RawMessage(raw)); ok {
t.Fatalf("%s should have been rejected", name)
}
}
}
func TestSearchTextIncludesSeriesNameSoEpisodesAreFindable(t *testing.T) {
year := 2022
text := searchText(syncItem{
Name: "Good News About Hell",
Type: "Episode",
SeriesName: "Severance",
ProductionYear: &year,
Genres: []string{"Drama", "Thriller"},
})
for _, want := range []string{"Good News About Hell", "Severance", "2022", "Drama"} {
if !strings.Contains(text, want) {
t.Fatalf("search text %q is missing %q", text, want)
}
}
}
func TestSearchTextDoesNotRepeatTheTitleForAMovie(t *testing.T) {
text := searchText(syncItem{Name: "Dune", Type: "Movie", SeriesName: "Dune"})
if strings.Count(strings.ToLower(text), "dune") != 1 {
t.Fatalf("title should appear once, got %q", text)
}
}