Files
silo-server/internal/metadata/trakt/user_list_test.go
193a2905b2 feat(collections): back collections with user-authored Trakt lists (#286)
* feat(collections): back collections with user-authored Trakt lists

Collections could sync only Trakt's built-in trending/popular/recommended
feeds; a server admin could not populate a collection from a specific
user's Trakt list (e.g. a curated 'Saw in timeline order' list) (#214).

- trakt.Client.GetUserList fetches /users/{user}/lists/{slug}/items in
  list order, mixing movies and shows and skipping non-title entries.
- New 'trakt_list' collection source mode: catalog.ParseTraktListURL
  accepts a trakt.tv list URL (or bare user/slug), and
  syncTraktListCollection reuses the preset pipeline's matching/ordering
  via an extracted completeTraktEntrySync helper. Public lists need no
  access token.
- Trakt import handler accepts list_url as an alternative to preset;
  the admin collection editor's Trakt form gains a Source toggle
  (discovery feed vs user list) with a list-URL input.

Additive-only: new source mode + optional request field; preset path
unchanged.

Fixes #214

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(collections): round-trip trakt_list through the edit form, unlock mixed libraries, validate list host

Three review fixes for user-list-backed collections:

- The admin edit form now detects mode "trakt_list", shows an editable
  list URL (mirroring the create form) and saves the source back as
  trakt_list with list_url preserved — previously any edit silently
  rewrote the collection into a trakt_preset Trending Movies feed.
- Library eligibility in list mode is mixed (movies + shows) instead of
  inheriting the hidden media-type default of movie, since Trakt lists
  mix both and entries match by their own type.
- ParseTraktListURL only accepts trakt.tv / www.trakt.tv hosts, so a
  list-shaped URL on another domain fails fast with the format error
  instead of a confusing later sync failure.

source_config now carries list_url alongside the legacy url key
(additive); sync reads list_url, then url, then source_url.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
2026-07-05 02:08:19 -04:00

73 lines
2.1 KiB
Go

package trakt
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetUserListDecodesMixedTypesInListOrder(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/users/jjjonesjr33/lists/saw-timeline/items" {
t.Fatalf("path = %s, want /users/jjjonesjr33/lists/saw-timeline/items", r.URL.Path)
}
writeJSON(t, w, []map[string]any{
{
"rank": 1,
"type": "movie",
"movie": map[string]any{
"title": "Saw",
"year": 2004,
"ids": map[string]any{"trakt": 10, "tmdb": 176, "imdb": "tt0387564"},
},
},
{
"rank": 2,
"type": "show",
"show": map[string]any{
"title": "Saw: The Series",
"year": 2024,
"ids": map[string]any{"trakt": 11, "tvdb": 999001},
},
},
// Unknown types (person, episode, season) are skipped, not fatal.
{
"rank": 3,
"type": "person",
},
})
}))
defer server.Close()
client := NewClient("client-id", 1000)
client.SetBaseURL(server.URL)
results, err := client.GetUserList(context.Background(), "jjjonesjr33", "saw-timeline", 10, "")
if err != nil {
t.Fatalf("GetUserList: %v", err)
}
if len(results) != 2 {
t.Fatalf("results = %d, want 2 (unknown types skipped)", len(results))
}
if results[0].MediaType != "movie" || results[0].TMDBID != 176 || results[0].Title != "Saw" {
t.Fatalf("first entry = %+v, want the Saw movie", results[0])
}
if results[1].MediaType != "tv" || results[1].TVDBID != 999001 {
t.Fatalf("second entry = %+v, want the show mapped to media type tv", results[1])
}
if results[0].Rank != 1 || results[1].Rank != 2 {
t.Fatalf("ranks = %d,%d, want list order preserved", results[0].Rank, results[1].Rank)
}
}
func TestGetUserListRequiresUserAndSlug(t *testing.T) {
client := NewClient("client-id", 1000)
if _, err := client.GetUserList(context.Background(), "", "slug", 10, ""); err == nil {
t.Fatal("empty user must error")
}
if _, err := client.GetUserList(context.Background(), "user", "", 10, ""); err == nil {
t.Fatal("empty list slug must error")
}
}