* feat(sections): fix broken home section templates and add six new ones Fixes templates that silently produced nothing: - award_winners: hide from gallery (resolver is a stub until award data exists); saved sections keep resolving - seasonal_themed: christmas/st_patricks/thanksgiving get an interim title-keyword resolver, and multi-theme selection skips themes without an executable query so a data-less theme can no longer black out the section during its own window (previously killed the section all of December) - taste_match: empty genre now auto-picks the profile's strongest taste cluster (fallback: server top genre); the default preset was permanently empty - because_you_watched: honor the recipe's anchor_item_id key (fetcher only read legacy source_item_id, so pinning an anchor did nothing) - editorial_spotlight: reject subject_type=franchise (validated but could never resolve); fix drawer misrepresenting pinned presets as auto-rotate - admin_curated_list: add a catalog-search item picker so Editor's Picks is actually addable; block saving an empty list; hide admin_only recipes from profile-facing galleries - discovery fetchers (hidden_gems, forgotten_favorites, critically_acclaimed): honor single/multi library scope, intersected with viewer access; implement hidden_gems max_play_count New templates: returning_shows (new season of shows you've watched), genre_roulette (rotating top-genre spotlight with title override), anniversaries (milestone release anniversaries this month), short_watches (well-rated movies under a runtime cap), family_movie_night seasonal theme (Fri/Sat evenings), and a "New in 4K" format_showcase preset via a new sort=recent param. Adds a blanket test asserting every visible gallery preset's defaults pass its own recipe validation — the gap that let taste_match and Editor's Picks ship broken. New SQL shapes validated with EXPLAIN against the dev database. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(sections): address PR #332 review findings Codex review: - returning_shows: the new-season file check now applies the effective library scope (section scope ∩ viewer-allowed, minus disabled) to media_files.media_folder_id, so an episode file that only exists in an out-of-scope folder can no longer surface the series - buildLibraryScope: replaced the media_item_libraries row join with EXISTS / NOT EXISTS semi-joins. An item in several in-scope libraries now yields exactly one row in the non-GROUP BY rails (short_watches, anniversaries, seasonal keyword, format_showcase, new_to_library, ...), and the disabled-library check is item-level, closing the join-row leak where membership in an allowed library masked membership in a disabled one. Deny-only mode keeps the positive-membership guard, mirroring catalog's appendDiscoveryLibraryScope. CodeRabbit review: - recommendations reader: a taste cluster whose cached items are entirely filtered out now falls through to the next cluster / global fallback instead of returning an empty row - genre_roulette: multi-library scopes get distinct rotation seeds - returning_shows: reject negative lookback_days at validation - shared oneOf() enum validator replaces per-recipe switch duplication - SeasonalTitleOverride usable-filter contract covered by a direct test - web NumberParamField: integer-only guard + step=1 (backend fields are Go ints; fractional values failed unmarshalling at save) - curated list picker: search failures show an error instead of a misleading "No matches."; pre-existing item_ids hydrate display titles via the watch-detail endpoint instead of rendering raw ids Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
53 lines
1.8 KiB
Go
53 lines
1.8 KiB
Go
package recipes
|
|
|
|
import "testing"
|
|
|
|
// presetsRequiringInput lists preset keys whose DefaultParams are intentionally
|
|
// incomplete: the add-section drawer blocks saving until the admin supplies
|
|
// the missing value (a collection id, curated item list, ...). Every other
|
|
// preset must ship defaults that pass its own recipe's Validate — a preset
|
|
// that fails validation out of the box is un-addable from the gallery.
|
|
var presetsRequiringInput = map[string]bool{
|
|
"collection_pick": true, // collection: requires picking a collection
|
|
"trakt_trending_movies": true, // collection: Trakt sync target chosen/created at add time
|
|
"trakt_trending_shows": true,
|
|
"trakt_popular_movies": true,
|
|
"trakt_popular_shows": true,
|
|
"trakt_recommended_movies": true,
|
|
"trakt_recommended_shows": true,
|
|
"acl_blank": true, // admin_curated_list: drawer requires at least one item
|
|
}
|
|
|
|
func TestAllGalleryPresetDefaultsValidate(t *testing.T) {
|
|
for _, rec := range List() {
|
|
def := rec.Definition()
|
|
if def.Hidden {
|
|
continue
|
|
}
|
|
for _, preset := range def.Presets {
|
|
if presetsRequiringInput[preset.Key] {
|
|
continue
|
|
}
|
|
if err := rec.Validate(preset.DefaultParams); err != nil {
|
|
t.Errorf("recipe %s preset %s: default params fail validation: %v", def.Type, preset.Key, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestHiddenRecipesStayResolvable pins the contract that Hidden recipes remain
|
|
// registered (existing saved sections keep resolving) while the gallery omits
|
|
// them.
|
|
func TestHiddenRecipesStayResolvable(t *testing.T) {
|
|
for _, typ := range []string{"award_winners", "genre"} {
|
|
rec, ok := Get(typ)
|
|
if !ok {
|
|
t.Errorf("hidden recipe %s must stay registered", typ)
|
|
continue
|
|
}
|
|
if !rec.Definition().Hidden {
|
|
t.Errorf("recipe %s expected Hidden", typ)
|
|
}
|
|
}
|
|
}
|