Реализовал выбор источника и предпросмотр в ревью (review-source-selection)

Экран ревью теперь показывает единый список источников совпадения: строка
«распознано нейронкой» наравне с кандидатами баз; выбор/переключение/снятие
в пользу нейронки; ручное добавление по id или URL (TMDB/IMDb — по URL,
TVDB — по числовому id); предпросмотр полей и целевых путей каждого источника
до применения (место под режиссёра зарезервировано). «Раскладка» осталась
отдельной секцией для активного источника, инлайн-превью неактивных — по клику.

Ядро: единая деривация «источник → overrides» (sourcePins), общая для превью
и коммита → preview == apply; заодно чинит латентный залипший override
title/year при переключении источника. Превью считается эфемерно, без записи
в БД; пользовательский URL только парсится (SSRF нет).

Ревью дизайна и кода пройдены; правки ревьюеров учтены (сообщение об ошибке
ручного ввода доходит до пользователя, URL без схемы принимается).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-03 10:29:27 +03:00
co-authored by Claude Opus 4.8
parent 3d3448d050
commit aac3ced262
10 changed files with 688 additions and 104 deletions
+39
View File
@@ -32,6 +32,45 @@ func TestProviderURL(t *testing.T) {
}
}
func TestParseManualSource(t *testing.T) {
cases := []struct {
name string
provider string
raw string
wantProvider string
wantID string
wantErr bool
}{
{"id с провайдером", "tmdb", "60622", "tmdb", "60622", false},
{"id обрезается позже воркером", "TVDB", "269613", "tvdb", "269613", false},
{"URL TMDB со slug", "", "https://www.themoviedb.org/tv/60622-fargo", "tmdb", "60622", false},
{"URL TMDB без схемы", "", "themoviedb.org/tv/60622", "tmdb", "60622", false},
{"URL TMDB movie", "", "https://www.themoviedb.org/movie/693134", "tmdb", "693134", false},
{"URL IMDb", "", "https://www.imdb.com/title/tt0111161/", "imdb", "tt0111161", false},
{"URL TVDB dereferrer", "", "https://www.thetvdb.com/dereferrer/series/269613", "tvdb", "269613", false},
{"URL TVDB slug — без id → ошибка", "", "https://www.thetvdb.com/series/fargo", "", "", true},
{"мусорный URL → ошибка", "", "https://example.com/foo/bar", "", "", true},
{"пустой ввод → ошибка", "tmdb", " ", "", "", true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
p, id, err := parseManualSource(c.provider, c.raw)
if c.wantErr {
if err == nil {
t.Fatalf("ожидалась ошибка, получено %q:%q", p, id)
}
return
}
if err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if p != c.wantProvider || id != c.wantID {
t.Errorf("parseManualSource(%q,%q) = %q:%q, want %q:%q", c.provider, c.raw, p, id, c.wantProvider, c.wantID)
}
})
}
}
// TestMatchURLNoLinkWhenUnbuildable — эффективный провайдер, для которого URL не
// строится и совпадающего кандидата нет, даёт пустую ссылку (транспорт покажет
// матч текстом — сценарий «URL записи неизвестен»).