Big changes
This commit is contained in:
@@ -5,9 +5,11 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/config"
|
||||
"github.com/ponzischeme89/memby/server/internal/emby"
|
||||
"github.com/ponzischeme89/memby/server/internal/store"
|
||||
)
|
||||
|
||||
func TestBearerTokenSources(t *testing.T) {
|
||||
@@ -116,6 +118,55 @@ func TestPlaybackHintAvoidsAnUpstreamItemLookup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEpisodeAfterUsesPositionNotListLength(t *testing.T) {
|
||||
episode := func(id string) json.RawMessage {
|
||||
return json.RawMessage(`{"Id":"` + id + `","Name":"Episode ` + id + `","SeriesName":"Westworld"}`)
|
||||
}
|
||||
|
||||
t.Run("middle of a season", func(t *testing.T) {
|
||||
items := []json.RawMessage{episode("1"), episode("2"), episode("3")}
|
||||
raw, next, ok := episodeAfter(items, "2")
|
||||
if !ok {
|
||||
t.Fatal("expected an episode after 2")
|
||||
}
|
||||
if next.ID != "3" {
|
||||
t.Fatalf("next = %q, want 3", next.ID)
|
||||
}
|
||||
if got := seriesNameOf(raw); got != "Westworld" {
|
||||
t.Fatalf("series name = %q, want Westworld", got)
|
||||
}
|
||||
})
|
||||
|
||||
// Emby drops the leading entry for the first episode, so a two-item list can mean
|
||||
// either "first, second" or "second-to-last, last" depending on where we are.
|
||||
t.Run("first episode has no previous", func(t *testing.T) {
|
||||
items := []json.RawMessage{episode("1"), episode("2")}
|
||||
if _, next, ok := episodeAfter(items, "1"); !ok || next.ID != "2" {
|
||||
t.Fatalf("next = %+v, ok = %v, want episode 2", next, ok)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("series finale has nothing after it", func(t *testing.T) {
|
||||
items := []json.RawMessage{episode("1"), episode("2")}
|
||||
if _, _, ok := episodeAfter(items, "2"); ok {
|
||||
t.Fatal("the last episode must not resolve a next one")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("current episode missing from the result", func(t *testing.T) {
|
||||
items := []json.RawMessage{episode("7"), episode("8")}
|
||||
if _, _, ok := episodeAfter(items, "42"); ok {
|
||||
t.Fatal("an unrelated result must not resolve a next episode")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty result", func(t *testing.T) {
|
||||
if _, _, ok := episodeAfter(nil, "1"); ok {
|
||||
t.Fatal("no episodes must not resolve a next one")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Empty rows must serialise as [] so kotlinx.serialization can decode them into the
|
||||
// client's non-null List fields.
|
||||
func TestHomeResponseEncodesEmptyRowsAsArrays(t *testing.T) {
|
||||
@@ -137,7 +188,22 @@ func TestHomeResponseEncodesEmptyRowsAsArrays(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The four fixed rows must keep their order, ids and kinds: the client maps kinds onto
|
||||
func TestSearchHistoryResponseEncodesEmptyQueriesAsArray(t *testing.T) {
|
||||
resp := searchHistoryResponse{Queries: []string{}}
|
||||
body, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var decoded map[string]any
|
||||
if err := json.Unmarshal(body, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if _, ok := decoded["queries"].([]any); !ok {
|
||||
t.Fatalf("queries encoded as %T, want array", decoded["queries"])
|
||||
}
|
||||
}
|
||||
|
||||
// The fixed rows must keep their order, ids and kinds: the client maps kinds onto
|
||||
// card shapes and uses ids as Compose keys.
|
||||
func TestBaseRowsShape(t *testing.T) {
|
||||
rows := baseRows(homeResponse{
|
||||
@@ -168,6 +234,29 @@ func TestBaseRowsShape(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHomeForYouWindowAt(t *testing.T) {
|
||||
tests := []struct {
|
||||
hour int
|
||||
id string
|
||||
minutes int
|
||||
}{
|
||||
{hour: 4, id: "late-night", minutes: 60},
|
||||
{hour: 5, id: "morning", minutes: 30},
|
||||
{hour: 11, id: "morning", minutes: 30},
|
||||
{hour: 12, id: "afternoon", minutes: 60},
|
||||
{hour: 16, id: "afternoon", minutes: 60},
|
||||
{hour: 17, id: "evening", minutes: 120},
|
||||
{hour: 22, id: "evening", minutes: 120},
|
||||
{hour: 23, id: "late-night", minutes: 60},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got := homeForYouWindowAt(time.Date(2026, time.July, 29, test.hour, 0, 0, 0, time.UTC))
|
||||
if got.ID != test.id || got.Minutes != test.minutes {
|
||||
t.Errorf("hour %d: got %#v, want id=%q minutes=%d", test.hour, got, test.id, test.minutes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecommendationBuildsAreDeduplicatedPerUser(t *testing.T) {
|
||||
var builds recommendationBuilds
|
||||
|
||||
@@ -200,3 +289,32 @@ func TestSummariseReadsResumePosition(t *testing.T) {
|
||||
t.Fatalf("resume position = %d ms, want 3600000", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeClientIdentityPersistsReportedHeaders(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/status", nil)
|
||||
req.Header.Set("X-Memby-Version", "0.1.60")
|
||||
req.Header.Set("X-Memby-Protocol", "1")
|
||||
sess := store.Session{}
|
||||
|
||||
if changed := mergeClientIdentity(req, &sess); !changed {
|
||||
t.Fatal("reported identity should update the session")
|
||||
}
|
||||
if sess.ClientVersion != "0.1.60" || sess.ClientProtocol != "1" {
|
||||
t.Fatalf("session identity = %q/%q", sess.ClientVersion, sess.ClientProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeClientIdentityAttributesHeaderlessAuthenticatedRequest(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/images/42/primary?t=token", nil)
|
||||
sess := store.Session{ClientVersion: "0.1.60", ClientProtocol: "1"}
|
||||
|
||||
if changed := mergeClientIdentity(req, &sess); changed {
|
||||
t.Fatal("inheriting identity should not write the session again")
|
||||
}
|
||||
if got := clientVersion(req); got != "0.1.60" {
|
||||
t.Fatalf("inherited client version = %q", got)
|
||||
}
|
||||
if got := clientProtocol(req); got != "1" {
|
||||
t.Fatalf("inherited client protocol = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user