package api import ( "encoding/json" "testing" "github.com/ponzischeme89/memby/server/internal/store" ) func readyReq(itemID, mediaType string) store.SurfacedReadyRequest { return store.SurfacedReadyRequest{MediaType: mediaType, ItemID: itemID, Title: itemID} } func isStamped(t *testing.T, raw json.RawMessage) bool { t.Helper() var item struct { Ready bool `json:"MembyRequestReady"` Label string `json:"MembyRequestReadyLabel"` } if err := json.Unmarshal(raw, &item); err != nil { t.Fatalf("unmarshal: %v", err) } return item.Ready && item.Label == requestReadyLabel } // The overwhelmingly common case: nobody is waiting on anything, so the row is untouched. func TestMergeRequestReadyNoArrivalsIsANoOp(t *testing.T) { items := []json.RawMessage{resumeItem(t, "film", "", "2026-08-01T20:00:00Z")} got := mergeRequestReady(items, nil, nil) if !equalIDs(mergedIDs(t, got), []string{"film"}) { t.Fatalf("ids = %v", mergedIDs(t, got)) } if isStamped(t, got[0]) { t.Fatal("item stamped with no arrivals") } } // An arrival not in the row is prepended and tagged. func TestMergeRequestReadyPrependsMissingTitle(t *testing.T) { items := []json.RawMessage{resumeItem(t, "film", "", "2026-08-01T20:00:00Z")} surfaced := []store.SurfacedReadyRequest{readyReq("new-movie", "movie")} fetched := map[string]json.RawMessage{ "new-movie": continueRaw(t, map[string]any{"Id": "new-movie", "Type": "Movie"}), } got := mergeRequestReady(items, surfaced, fetched) if want := []string{"new-movie", "film"}; !equalIDs(mergedIDs(t, got), want) { t.Fatalf("ids = %v, want %v", mergedIDs(t, got), want) } if !isStamped(t, got[0]) { t.Fatal("prepended card not stamped") } if isStamped(t, got[1]) { t.Fatal("existing card wrongly stamped") } } // Newest arrival first when several are prepended (surfaced is ordered newest first). func TestMergeRequestReadyKeepsNewestArrivalFirst(t *testing.T) { items := []json.RawMessage{resumeItem(t, "film", "", "2026-08-01T20:00:00Z")} surfaced := []store.SurfacedReadyRequest{readyReq("newer", "movie"), readyReq("older", "movie")} fetched := map[string]json.RawMessage{ "newer": continueRaw(t, map[string]any{"Id": "newer", "Type": "Movie"}), "older": continueRaw(t, map[string]any{"Id": "older", "Type": "Movie"}), } got := mergedIDs(t, mergeRequestReady(items, surfaced, fetched)) if want := []string{"newer", "older", "film"}; !equalIDs(got, want) { t.Fatalf("ids = %v, want %v", got, want) } } // A surfaced title already in Continue Watching — by its own id, or as the series an // episode belongs to — is tagged in place, never duplicated. func TestMergeRequestReadyTagsExistingCardInPlace(t *testing.T) { items := []json.RawMessage{ resumeItem(t, "s1e3", "severance", "2026-08-06T19:00:00Z"), resumeItem(t, "the-film", "", "2026-08-01T20:00:00Z"), } surfaced := []store.SurfacedReadyRequest{ readyReq("severance", "series"), readyReq("the-film", "movie"), } got := mergeRequestReady(items, surfaced, nil) if want := []string{"s1e3", "the-film"}; !equalIDs(mergedIDs(t, got), want) { t.Fatalf("ids = %v, want %v", mergedIDs(t, got), want) } if !isStamped(t, got[0]) || !isStamped(t, got[1]) { t.Fatal("existing cards not tagged in place") } } // A fetch that returned nothing for an arrival degrades to leaving it out rather than // inserting a blank card. func TestMergeRequestReadyDropsUnfetchableArrival(t *testing.T) { items := []json.RawMessage{resumeItem(t, "film", "", "2026-08-01T20:00:00Z")} surfaced := []store.SurfacedReadyRequest{readyReq("gone", "movie")} got := mergeRequestReady(items, surfaced, map[string]json.RawMessage{}) if !equalIDs(mergedIDs(t, got), []string{"film"}) { t.Fatalf("ids = %v", mergedIDs(t, got)) } } func TestStampRequestReadyPreservesOtherFields(t *testing.T) { raw := continueRaw(t, map[string]any{"Id": "x", "Name": "Keep me", "Type": "Movie"}) stamped := stampRequestReady(raw) var item struct { Name string `json:"Name"` Ready bool `json:"MembyRequestReady"` } if err := json.Unmarshal(stamped, &item); err != nil { t.Fatalf("unmarshal: %v", err) } if item.Name != "Keep me" || !item.Ready { t.Fatalf("stamp lost fields: %+v", item) } }