mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 04:26:36 +08:00
fix: guard series metadata from episode identity pollution
This commit is contained in:
@@ -98,6 +98,16 @@ func TestPathHintMetadataDoesNotMarkMediaMatched(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathHintMetadataIgnoresEpisodeFileIDForSeries(t *testing.T) {
|
||||
meta, hints := pathHintMetadata("cloud://openlist/国漫/遮天 (2023)/Season 1/遮天.S01E01.{tmdb-4375419}.mkv", true)
|
||||
if meta == nil || meta.Title != "遮天" || meta.Year != 2023 {
|
||||
t.Fatalf("series path hint metadata = %+v", meta)
|
||||
}
|
||||
if hints.TMDbID != 0 || meta.TMDbID != 0 {
|
||||
t.Fatalf("episode filename tmdb id must not become series id: meta=%+v hints=%+v", meta, hints)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichOneCloudPathHintOverridesStaleTMDbID(t *testing.T) {
|
||||
var requested []string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -168,6 +178,124 @@ func TestEnrichOneCloudPathHintOverridesStaleTMDbID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichOneRejectsStaleEpisodeTMDbIDBySeriesTitle(t *testing.T) {
|
||||
var requested []string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requested = append(requested, r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch r.URL.Path {
|
||||
case "/tv/220269":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"id": 220269,
|
||||
"name": "错误剧集",
|
||||
"overview": "stale episode id resolved as another show",
|
||||
"first_air_date": "2024-01-01",
|
||||
})
|
||||
case "/search/tv":
|
||||
if r.URL.Query().Get("query") != "折腰" {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"results": []any{}})
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"results": []map[string]any{{
|
||||
"id": 296753,
|
||||
"name": "折腰",
|
||||
"overview": "正确的剧集条目",
|
||||
"poster_path": "/zheyao.jpg",
|
||||
"first_air_date": "2025-05-13",
|
||||
"origin_country": []string{"CN"},
|
||||
}},
|
||||
})
|
||||
case "/tv/296753":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"id": 296753,
|
||||
"name": "折腰",
|
||||
"overview": "正确的剧集条目",
|
||||
"poster_path": "/zheyao.jpg",
|
||||
"first_air_date": "2025-05-13",
|
||||
"origin_country": []string{"CN"},
|
||||
})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.Library{}, &model.Series{}, &model.Media{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repos := repository.New(db)
|
||||
cfg := &config.Config{}
|
||||
cfg.Secrets.TMDbAPIKey = "test-key"
|
||||
cfg.Secrets.TMDbAPIProxy = upstream.URL
|
||||
cfg.Secrets.TMDbImageProxy = upstream.URL + "/images"
|
||||
log := zap.NewNop()
|
||||
scraper := NewScraperService(cfg, log, repos, NewTMDbProvider(cfg, log, nil), nil, nil, nil, NewHub(log))
|
||||
|
||||
lib := model.Library{Name: "OpenList · 国产剧", Path: "cloud://openlist/国产剧", Type: "tv", Enabled: true}
|
||||
if err := repos.DB.Create(&lib).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
media := model.Media{
|
||||
LibraryID: lib.ID,
|
||||
Title: "折腰",
|
||||
Path: "cloud://openlist/国产剧/折腰 (2025)/Season 1/折腰.S01E01.mkv",
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
TMDbID: 220269,
|
||||
ScrapeStatus: "pending",
|
||||
}
|
||||
if err := repos.DB.Create(&media).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := scraper.EnrichOne(t.Context(), &media); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got model.Media
|
||||
if err := repos.DB.First(&got, "id = ?", media.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.ScrapeStatus != "matched" || got.TMDbID != 296753 || got.Title != "折腰" || got.PosterURL == "" {
|
||||
t.Fatalf("stale tmdb id should be rejected and repaired by title search: status=%q tmdb=%d title=%q poster=%q requests=%v",
|
||||
got.ScrapeStatus, got.TMDbID, got.Title, got.PosterURL, requested)
|
||||
}
|
||||
if firstIndexFunc(requested, func(path string) bool { return path == "/tv/220269" }) < 0 {
|
||||
t.Fatalf("test did not exercise stale id lookup: requests=%v", requested)
|
||||
}
|
||||
if firstIndexFunc(requested, func(path string) bool { return path == "/search/tv" }) < 0 {
|
||||
t.Fatalf("scraper did not fall back to title search after stale id rejection: requests=%v", requested)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaYearHintUsesSeriesFolderYearForEpisodes(t *testing.T) {
|
||||
media := &model.Media{
|
||||
Year: 2026,
|
||||
Path: "cloud://openlist/国产剧/折腰 (2025)/Season 1/折腰.S01E01.mkv",
|
||||
SeasonNum: 1,
|
||||
EpisodeNum: 1,
|
||||
}
|
||||
if got := mediaYearHint(media); got != 2025 {
|
||||
t.Fatalf("mediaYearHint = %d, want series folder year 2025", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaYearHintIgnoresEpisodeRowYearWithoutSeriesFolderYear(t *testing.T) {
|
||||
media := &model.Media{
|
||||
Year: 2026,
|
||||
Path: "cloud://openlist/综艺/哈哈哈哈哈/Season 6/哈哈哈哈哈 - S06E01.mkv",
|
||||
SeasonNum: 6,
|
||||
EpisodeNum: 1,
|
||||
}
|
||||
if got := mediaYearHint(media); got != 0 {
|
||||
t.Fatalf("mediaYearHint = %d, want no year when only episode row year is available", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichOneUsesLocalPathExternalIDHints(t *testing.T) {
|
||||
var requested []string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user