Files
jellybit/internal/worker/displayname_test.go
T
avandClaude Opus 4.8 02d4ecc2aa display_name: слоистое разрешение полей + сохранение режиссёра из контекста
Единый источник полей отображаемого имени и один рендер полного ярлыка на
всех путях (старт и «Обновить имя»/авто-перелив). Раньше старт давал полный
«Название (режиссёр, год). Сезон N» но выбрасывал структуру, а перелив по
распознаванию — усечённый «Title (Year)».

- Слоистое разрешение скаляров имени: override → recognition(+match) →
  новый базовый слой «контекст» (download.parsed_context, JSON naming.Fields).
- naming: публичные Fields/Label/Derive, вынесен единый рендер; удалён
  FormatTitleYear. Сводка сезонов вынесена в recognize.SeasonSummary.
- Режиссёр из метабазы (решение A2): TMDB/TVDB credits через опциональный
  metadata.DirectorProvider; авто-матч кладёт в plan.Director, ручной выбор
  кандидата тянет credits и пиннит ovrDirector. Метабаза бьёт контекст.
- refreshDisplayNameLocked строит полный ярлык из эффективных полей;
  инфо-панель ревью показывает загруженного режиссёра.
- Миграция 0011_parsed_context + ER-схема. Всё косметика: на пути/раскладку
  не влияет, приём/вывод имени не валятся (best-effort).

Закрывает беклог-задачу «Кнопка „Обновить имя“: полный формат ярлыка».
OpenSpec: archive/2026-07-11-field-resolution-display-name (ingest,
recognition, metadata-match, review).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 11:50:08 +03:00

94 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package worker
import (
"encoding/json"
"testing"
"git.vakhrushev.me/av/jellybit/internal/naming"
"git.vakhrushev.me/av/jellybit/internal/recognize"
"git.vakhrushev.me/av/jellybit/internal/store"
)
func parsedContext(t *testing.T, f naming.Fields) string {
t.Helper()
b, err := json.Marshal(f)
if err != nil {
t.Fatalf("marshal fields: %v", err)
}
return string(b)
}
func episode(season int) recognize.PlanFile {
s := season
return recognize.PlanFile{Role: recognize.RoleEpisode, Season: &s}
}
// effectiveDisplayName: слоистое разрешение полей ярлыка (план → контекст) и
// сводка сезонов из плана/контекста.
func TestEffectiveDisplayName(t *testing.T) {
cases := []struct {
name string
parsed naming.Fields
plan recognize.Plan
want string
}{
{
name: "матч даёт режиссёра и год",
plan: recognize.Plan{Type: recognize.MediaMovie, Title: "Дюна", Director: "Дени Вильнёв", Year: 2024},
want: "Дюна (Дени Вильнёв, 2024)",
},
{
name: "режиссёр из контекста переживает распознавание без матча",
parsed: naming.Fields{Type: "movie", Title: "Дюна", Director: "Дени Вильнёв", Year: 2021},
plan: recognize.Plan{Type: recognize.MediaMovie, Title: "Дюна", Year: 2024}, // без director
want: "Дюна (Дени Вильнёв, 2024)", // год из плана, режиссёр из контекста
},
{
name: "режиссёр матча бьёт контекстного",
parsed: naming.Fields{Type: "movie", Title: "Дюна", Director: "Кто-то из контекста"},
plan: recognize.Plan{Type: recognize.MediaMovie, Title: "Дюна", Director: "Дени Вильнёв", Year: 2024},
want: "Дюна (Дени Вильнёв, 2024)",
},
{
name: "год из контекста, когда плана нет",
parsed: naming.Fields{Type: "movie", Title: "Брат", Year: 1997},
plan: recognize.Plan{Type: recognize.MediaMovie, Title: "Брат"}, // year 0
want: "Брат (1997)",
},
{
name: "сериал: сводка сезонов из плана",
plan: recognize.Plan{Type: recognize.MediaSeries, Title: "Фарго",
Files: []recognize.PlanFile{episode(1), episode(2), episode(3)}},
want: "Фарго. Сезоны 13",
},
{
name: "сериал: сезон из контекста, когда в плане нет эпизодов",
parsed: naming.Fields{Type: "series", Title: "Сёгун", Season: ptrInt(2)},
plan: recognize.Plan{Type: recognize.MediaSeries, Title: "Сёгун", Year: 2024},
want: "Сёгун (2024). Сезон 2",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
d := store.Download{}
if c.parsed.Title != "" {
d.ParsedContext = parsedContext(t, c.parsed)
}
if got := effectiveDisplayName(d, c.plan); got != c.want {
t.Errorf("effectiveDisplayName = %q, want %q", got, c.want)
}
})
}
}
// Битый parsed_context не валит рендер (best-effort): используется только план.
func TestEffectiveDisplayNameBrokenParsedContext(t *testing.T) {
d := store.Download{ParsedContext: "{не json"}
plan := recognize.Plan{Type: recognize.MediaMovie, Title: "Дюна", Year: 2024}
if got := effectiveDisplayName(d, plan); got != "Дюна (2024)" {
t.Errorf("effectiveDisplayName = %q, want «Дюна (2024)»", got)
}
}
func ptrInt(v int) *int { return &v }