* 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>
73 lines
2.1 KiB
Go
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")
|
|
}
|
|
}
|