* fix(collections): repair broken builtin collection templates A live audit of the builtin template catalog (all 40 MDBList URLs and all 10 TMDB franchise IDs fetched) found two dead sources, a silent bundle-apply collision, and several templates whose defaults contradict their descriptions: - Repoint mdblist_misc_a24 and mdblist_misc_criterion_collection to live lists; the original irvingbeano/shtluck lists were deleted on MDBList (404), so every sync of those collections failed. - Retitle mdblist_charts_popular_movies to "IMDb MovieMeter Top 100". It shared the "popular-movies" title slug with tmdb_popular_movies, and bundle apply dedupes by slug per library, so applying all_defaults silently skipped it. Poster regenerated from the raw plate with the new title; new handler test asserts builtin title slugs stay unique. - Raise the shared default limit 50 -> 100, give the IMDb Top 250 templates an explicit 250 (limit*4 fetch trim previously never scanned entries 201-250), and drop the limit on catalog lists (Criterion, A24) so they hold every owned title. - Correct IFC Films to MediaMovie (live list is 100% movies; as MediaMixed it was offered to TV libraries where it always synced empty) and fix the Trakt Popular descriptions (ratings-based, not "most-watched"). - Update stale limit docs in collection-templates.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(collections): raise import limit caps above IMDb Top 250 default The IMDb Top 250 templates now default to 250 items, but the template config forms rendered their Max Items input with max=200 and the user import API rejected limits above 200, so applying those templates from the direct galleries failed native validation or got a 400. Raise the cap to 500 on both sides, wired to shared constants: sync's fetch trim (collectionSourceFetchMax) never scans more than 500 source entries, so a larger explicit limit could never be satisfied anyway. collectionutil.MaxExplicitItemLimit backs validateOptionalLimit, and COLLECTION_MAX_ITEMS in lib/collectionTemplates backs all seven Max Items inputs (gallery forms + admin import/editor dialogs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/collections/templates"
|
|
)
|
|
|
|
func TestCollectionTemplateHandlerReturnsBuiltinCatalog(t *testing.T) {
|
|
h := NewCollectionTemplateHandler(nil)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/collections/templates", nil)
|
|
rec := httptest.NewRecorder()
|
|
h.HandleListTemplates(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", rec.Code)
|
|
}
|
|
|
|
var body templates.Catalog
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if len(body.Categories) == 0 {
|
|
t.Fatal("expected at least one category in response")
|
|
}
|
|
totalTemplates := 0
|
|
for _, group := range body.Categories {
|
|
totalTemplates += len(group.Templates)
|
|
}
|
|
if totalTemplates < 5 {
|
|
t.Errorf("expected several templates, got %d", totalTemplates)
|
|
}
|
|
}
|
|
|
|
// Bundle apply dedupes collections by slugified title per library
|
|
// (applyTemplateBundle's slug-match adoption), so two builtin templates with
|
|
// the same title slug can never coexist: whichever applies second is silently
|
|
// skipped as already_exists.
|
|
func TestBuiltinTemplateTitleSlugsAreUnique(t *testing.T) {
|
|
bySlug := make(map[string]string)
|
|
for _, tmpl := range templates.List() {
|
|
slug := slugifyCollectionName(tmpl.Title)
|
|
if other, exists := bySlug[slug]; exists {
|
|
t.Errorf("templates %q and %q share title slug %q; bundle apply would silently skip one", other, tmpl.ID, slug)
|
|
continue
|
|
}
|
|
bySlug[slug] = tmpl.ID
|
|
}
|
|
}
|
|
|
|
func TestCollectionTemplateHandlerHonoursInjectedRegistry(t *testing.T) {
|
|
registry := templates.NewRegistry()
|
|
registry.Register(templates.Template{
|
|
ID: "test_only",
|
|
Title: "Test only",
|
|
Category: templates.CategoryTrending,
|
|
Source: templates.SourceTMDB,
|
|
MediaKind: templates.MediaMovie,
|
|
TMDB: &templates.TMDBSpec{Preset: "popular", MediaType: "movie"},
|
|
})
|
|
|
|
h := NewCollectionTemplateHandler(registry)
|
|
rec := httptest.NewRecorder()
|
|
h.HandleListTemplates(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
|
|
var body templates.Catalog
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if len(body.Categories) != 1 || len(body.Categories[0].Templates) != 1 {
|
|
t.Fatalf("unexpected catalog shape: %+v", body)
|
|
}
|
|
if body.Categories[0].Templates[0].ID != "test_only" {
|
|
t.Errorf("got %q, want test_only", body.Categories[0].Templates[0].ID)
|
|
}
|
|
}
|
|
|
|
func TestLibraryCollectionHandlerListsTemplateBundles(t *testing.T) {
|
|
registry := templates.NewRegistry()
|
|
registry.Register(templates.Template{
|
|
ID: "test_template",
|
|
Title: "Test template",
|
|
Category: templates.CategoryTrending,
|
|
Source: templates.SourceTMDB,
|
|
MediaKind: templates.MediaMovie,
|
|
TMDB: &templates.TMDBSpec{Preset: "popular", MediaType: "movie"},
|
|
})
|
|
registry.RegisterBundle(templates.Bundle{
|
|
ID: "test_bundle",
|
|
Title: "Test bundle",
|
|
Description: "Test description",
|
|
TemplateIDs: []string{"test_template"},
|
|
})
|
|
h := &LibraryCollectionHandler{TemplateRegistry: registry}
|
|
|
|
rec := httptest.NewRecorder()
|
|
h.HandleListTemplateBundles(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
|
|
var body templates.BundleCatalog
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if len(body.Bundles) != 1 || body.Bundles[0].ID != "test_bundle" {
|
|
t.Fatalf("unexpected bundle catalog: %+v", body)
|
|
}
|
|
}
|