Files
silo-server/internal/metadata/tmdb/client_test.go
T

345 lines
10 KiB
Go

package tmdb
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetCollectionPresetTrending(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/trending/all/day" {
http.NotFound(w, r)
return
}
if got := r.URL.Query().Get("page"); got != "1" {
t.Fatalf("page query = %q, want 1", got)
}
if got := r.URL.Query().Get("api_key"); got != "test-key" {
t.Fatalf("api_key query = %q, want test-key", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"page": 1,
"total_pages": 1,
"total_results": 2,
"results": [
{"id": 10, "media_type": "movie", "title": "Movie Title"},
{"id": 20, "media_type": "tv", "name": "Series Title"}
]
}`))
}))
defer server.Close()
client := NewClient("test-key", 1000)
client.SetBaseURL(server.URL)
results, err := client.GetCollectionPreset(context.Background(), "trending", "all", "day", 10)
if err != nil {
t.Fatalf("GetCollectionPreset returned error: %v", err)
}
if len(results) != 2 {
t.Fatalf("len(results) = %d, want 2", len(results))
}
if results[0] != (CollectionResult{ID: 10, MediaType: "movie", Title: "Movie Title"}) {
t.Fatalf("results[0] = %+v", results[0])
}
if results[1] != (CollectionResult{ID: 20, MediaType: "tv", Title: "Series Title"}) {
t.Fatalf("results[1] = %+v", results[1])
}
}
func TestDiscoverMovieAppliesFilters(t *testing.T) {
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/discover/movie" {
http.NotFound(w, r)
return
}
calls++
q := r.URL.Query()
if got := q.Get("sort_by"); got != "popularity.desc" {
t.Errorf("sort_by = %q, want popularity.desc", got)
}
if got := q.Get("with_genres"); got != "28,12" {
t.Errorf("with_genres = %q, want 28,12", got)
}
if got := q.Get("without_genres"); got != "99" {
t.Errorf("without_genres = %q, want 99", got)
}
if got := q.Get("vote_count.gte"); got != "300" {
t.Errorf("vote_count.gte = %q, want 300", got)
}
if got := q.Get("vote_average.gte"); got != "6.5" {
t.Errorf("vote_average.gte = %q, want 6.5", got)
}
if got := q.Get("primary_release_date.gte"); got != "2020-01-01" {
t.Errorf("primary_release_date.gte = %q, want 2020-01-01", got)
}
if got := q.Get("primary_release_date.lte"); got != "2025-12-31" {
t.Errorf("primary_release_date.lte = %q, want 2025-12-31", got)
}
if got := q.Get("certification_country"); got != "US" {
t.Errorf("certification_country = %q, want US", got)
}
if got := q.Get("certification"); got != "PG|PG-13" {
t.Errorf("certification = %q, want PG|PG-13", got)
}
if got := q.Get("with_runtime.gte"); got != "90" {
t.Errorf("with_runtime.gte = %q, want 90", got)
}
if got := q.Get("with_runtime.lte"); got != "180" {
t.Errorf("with_runtime.lte = %q, want 180", got)
}
if got := q.Get("with_original_language"); got != "en" {
t.Errorf("with_original_language = %q, want en", got)
}
if got := q.Get("api_key"); got != "test-key" {
t.Errorf("api_key = %q, want test-key", got)
}
if got := q.Get("page"); got != "1" {
t.Errorf("page = %q, want 1", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"page": 1,
"total_pages": 1,
"total_results": 2,
"results": [
{"id": 11, "title": "First Movie"},
{"id": 22, "title": "Second Movie"}
]
}`))
}))
defer server.Close()
client := NewClient("test-key", 1000)
client.SetBaseURL(server.URL)
results, err := client.Discover(context.Background(), "movie", DiscoverParams{
SortBy: "popularity.desc",
WithGenres: []int{28, 12},
WithoutGenres: []int{99},
VoteCountGte: 300,
VoteAverageGte: 6.5,
ReleaseDateGte: "2020-01-01",
ReleaseDateLte: "2025-12-31",
Certifications: []string{"PG", "PG-13"},
WithRuntimeGte: 90,
WithRuntimeLte: 180,
OriginalLanguage: "en",
Limit: 10,
})
if err != nil {
t.Fatalf("Discover returned error: %v", err)
}
if calls != 1 {
t.Fatalf("expected 1 server call, got %d", calls)
}
if len(results) != 2 {
t.Fatalf("len(results) = %d, want 2", len(results))
}
if results[0] != (CollectionResult{ID: 11, MediaType: "movie", Title: "First Movie"}) {
t.Errorf("results[0] = %+v", results[0])
}
if results[1] != (CollectionResult{ID: 22, MediaType: "movie", Title: "Second Movie"}) {
t.Errorf("results[1] = %+v", results[1])
}
}
func TestDiscoverTVUsesFirstAirDate(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/discover/tv" {
http.NotFound(w, r)
return
}
q := r.URL.Query()
if got := q.Get("sort_by"); got != "vote_average.desc" {
t.Errorf("sort_by = %q, want vote_average.desc", got)
}
if got := q.Get("first_air_date.gte"); got != "2010-01-01" {
t.Errorf("first_air_date.gte = %q, want 2010-01-01", got)
}
if got := q.Get("first_air_date.lte"); got != "2020-01-01" {
t.Errorf("first_air_date.lte = %q, want 2020-01-01", got)
}
// TV requests must NOT carry primary_release_date.* params.
if got := q.Get("primary_release_date.gte"); got != "" {
t.Errorf("primary_release_date.gte should be empty for tv, got %q", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"page": 1,
"total_pages": 1,
"total_results": 1,
"results": [
{"id": 99, "name": "Some Show"}
]
}`))
}))
defer server.Close()
client := NewClient("test-key", 1000)
client.SetBaseURL(server.URL)
results, err := client.Discover(context.Background(), "tv", DiscoverParams{
SortBy: "vote_average.desc",
ReleaseDateGte: "2010-01-01",
ReleaseDateLte: "2020-01-01",
Limit: 5,
})
if err != nil {
t.Fatalf("Discover tv: %v", err)
}
if len(results) != 1 {
t.Fatalf("len(results) = %d, want 1", len(results))
}
if results[0] != (CollectionResult{ID: 99, MediaType: "tv", Title: "Some Show"}) {
t.Errorf("results[0] = %+v", results[0])
}
}
func TestDiscoverRejectsInvalidMediaType(t *testing.T) {
client := NewClient("test-key", 1000)
_, err := client.Discover(context.Background(), "all", DiscoverParams{SortBy: "popularity.desc"})
if err == nil {
t.Fatal("expected error for invalid media type")
}
}
func TestDiscoverRequiresSortBy(t *testing.T) {
client := NewClient("test-key", 1000)
_, err := client.Discover(context.Background(), "movie", DiscoverParams{})
if err == nil {
t.Fatal("expected error when sort_by is empty")
}
}
func TestGetCollection(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/collection/86311" {
http.NotFound(w, r)
return
}
if got := r.URL.Query().Get("api_key"); got != "test-key" {
t.Fatalf("api_key query = %q, want test-key", got)
}
w.Header().Set("Content-Type", "application/json")
// Trimmed MCU-style payload — parts ordered chronologically by
// release date, with one part omitting media_type to exercise the
// "default to movie" branch.
_, _ = w.Write([]byte(`{
"id": 86311,
"name": "The Avengers Collection",
"parts": [
{"id": 24428, "media_type": "movie", "title": "The Avengers", "release_date": "2012-04-25"},
{"id": 99861, "media_type": "movie", "title": "Avengers: Age of Ultron", "release_date": "2015-04-22"},
{"id": 299536, "title": "Avengers: Infinity War", "release_date": "2018-04-25"}
]
}`))
}))
defer server.Close()
client := NewClient("test-key", 1000)
client.SetBaseURL(server.URL)
got, err := client.GetCollection(context.Background(), 86311)
if err != nil {
t.Fatalf("GetCollection: %v", err)
}
if got == nil {
t.Fatal("GetCollection returned nil")
}
if got.ID != 86311 {
t.Errorf("ID = %d, want 86311", got.ID)
}
if got.Name != "The Avengers Collection" {
t.Errorf("Name = %q, want The Avengers Collection", got.Name)
}
if len(got.Parts) != 3 {
t.Fatalf("len(parts) = %d, want 3", len(got.Parts))
}
// Order assertion: TMDB returns parts in curated order; the client
// preserves that order so downstream sync writes items consistently.
wantOrder := []int{24428, 99861, 299536}
for i, want := range wantOrder {
if got.Parts[i].ID != want {
t.Errorf("parts[%d].ID = %d, want %d", i, got.Parts[i].ID, want)
}
}
// Media type defaulting: third part omitted media_type in the wire
// payload; client must default to "movie" so the resolver doesn't see
// an empty string.
if got.Parts[0].MediaType != "movie" {
t.Errorf("parts[0].MediaType = %q, want movie", got.Parts[0].MediaType)
}
if got.Parts[2].MediaType != "movie" {
t.Errorf("parts[2].MediaType = %q (omitted in payload), want movie default", got.Parts[2].MediaType)
}
if got.Parts[0].Title != "The Avengers" {
t.Errorf("parts[0].Title = %q, want The Avengers", got.Parts[0].Title)
}
if got.Parts[0].ReleaseDate != "2012-04-25" {
t.Errorf("parts[0].ReleaseDate = %q, want 2012-04-25", got.Parts[0].ReleaseDate)
}
}
func TestGetCollectionRejectsNonPositiveID(t *testing.T) {
client := NewClient("test-key", 1000)
if _, err := client.GetCollection(context.Background(), 0); err == nil {
t.Fatal("expected error on id=0")
}
if _, err := client.GetCollection(context.Background(), -7); err == nil {
t.Fatal("expected error on negative id")
}
}
func TestGetExternalIDs(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/movie/123" {
http.NotFound(w, r)
return
}
if got := r.URL.Query().Get("append_to_response"); got != "external_ids" {
t.Fatalf("append_to_response = %q, want external_ids", got)
}
if got := r.URL.Query().Get("api_key"); got != "test-key" {
t.Fatalf("api_key query = %q, want test-key", got)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"external_ids": {
"imdb_id": "tt0133093",
"tvdb_id": 12345
}
}`))
}))
defer server.Close()
client := NewClient("test-key", 1000)
client.SetBaseURL(server.URL)
ids, err := client.GetExternalIDs(context.Background(), "movie", 123)
if err != nil {
t.Fatalf("GetExternalIDs returned error: %v", err)
}
if ids == nil {
t.Fatal("GetExternalIDs returned nil ids")
}
if ids.IMDbID != "tt0133093" {
t.Fatalf("IMDbID = %q, want tt0133093", ids.IMDbID)
}
if ids.TVDBID != 12345 {
t.Fatalf("TVDBID = %d, want 12345", ids.TVDBID)
}
}