Files
MeBox/internal/service/cloud_path_repair_test.go
T

170 lines
6.0 KiB
Go

package service
import (
"testing"
"go.uber.org/zap"
"github.com/ShukeBta/MediaStationGo/internal/model"
"github.com/ShukeBta/MediaStationGo/internal/repository"
)
func TestRepairRescrapeOptionsDefaultSkipsEpisodeArtwork(t *testing.T) {
options := repairRescrapeOptions()
if !options.RetryNoMatch {
t.Fatal("repair rescrape should retry no_match rows")
}
if !options.IncludeMatched {
t.Fatal("repair rescrape should refresh already matched rows")
}
if options.EpisodeArtwork == nil {
t.Fatal("repair rescrape should set an explicit episode artwork option")
}
if *options.EpisodeArtwork {
t.Fatal("repair rescrape should skip episode artwork by default")
}
}
func TestRepairRescrapeOptionsCanEnableEpisodeArtwork(t *testing.T) {
episodeArtwork := true
options := repairRescrapeOptions(ScrapeOptions{EpisodeArtwork: &episodeArtwork})
if !options.RetryNoMatch {
t.Fatal("repair rescrape should force retry no_match rows")
}
if !options.IncludeMatched {
t.Fatal("repair rescrape should force refreshing already matched rows")
}
if options.EpisodeArtwork == nil || !*options.EpisodeArtwork {
t.Fatal("repair rescrape should keep explicit episode artwork=true")
}
}
func TestRepairRescrapeOptionsKeepsExplicitEpisodeArtworkFalse(t *testing.T) {
episodeArtwork := false
options := repairRescrapeOptions(ScrapeOptions{EpisodeArtwork: &episodeArtwork})
if !options.RetryNoMatch {
t.Fatal("repair rescrape should force retry no_match rows")
}
if !options.IncludeMatched {
t.Fatal("repair rescrape should force refreshing already matched rows")
}
if options.EpisodeArtwork == nil {
t.Fatal("repair rescrape should keep explicit episode artwork option")
}
if *options.EpisodeArtwork {
t.Fatal("repair rescrape should keep explicit episode artwork=false")
}
}
// TestResetEpisodicMatchedForRescrape 验证「修复+重刮」会把脏的 matched 剧集行
// 重置为 pending(让 EnrichLibrary 能重新刮削),而电影行与其它库不受影响。
func TestResetEpisodicMatchedForRescrape(t *testing.T) {
db := newServiceTestDB(t, &model.Library{}, &model.Media{})
container := &Container{Repo: repository.New(db), Log: zap.NewNop()}
rows := []model.Media{
// 目标库的剧集行(matched, 有季集号)→ 应被重置。
{Base: model.Base{ID: "ep1"}, LibraryID: "lib-a", SeasonNum: 1, EpisodeNum: 1, ScrapeStatus: "matched", Path: "/a/show/S01/ep1.mkv"},
{Base: model.Base{ID: "ep2"}, LibraryID: "lib-a", SeasonNum: 1, EpisodeNum: 2, ScrapeStatus: "matched", Path: "/a/show/S01/ep2.mkv"},
// 目标库的电影行(无季集号)→ 不应被重置。
{Base: model.Base{ID: "movie1"}, LibraryID: "lib-a", ScrapeStatus: "matched", Path: "/a/movie.mkv"},
// 目标库已是 pending 的剧集行 → 不计入重置数。
{Base: model.Base{ID: "ep3"}, LibraryID: "lib-a", SeasonNum: 1, EpisodeNum: 3, ScrapeStatus: "pending", Path: "/a/show/S01/ep3.mkv"},
// 其它库的剧集行(matched)→ 单库重置时不应受影响。
{Base: model.Base{ID: "ep-other"}, LibraryID: "lib-b", SeasonNum: 1, EpisodeNum: 1, ScrapeStatus: "matched", Path: "/b/show/S01/ep1.mkv"},
}
for i := range rows {
if err := db.Create(&rows[i]).Error; err != nil {
t.Fatalf("create media %s: %v", rows[i].ID, err)
}
}
reset, err := container.resetEpisodicMatchedForRescrape(t.Context(), "lib-a")
if err != nil {
t.Fatalf("reset: %v", err)
}
if reset != 2 {
t.Fatalf("reset = %d, want 2 (only matched episodic rows in lib-a)", reset)
}
status := func(id string) string {
var m model.Media
if err := db.First(&m, "id = ?", id).Error; err != nil {
t.Fatalf("load %s: %v", id, err)
}
return m.ScrapeStatus
}
if status("ep1") != "pending" || status("ep2") != "pending" {
t.Fatalf("episodic matched rows should be pending, got ep1=%q ep2=%q", status("ep1"), status("ep2"))
}
if status("movie1") != "matched" {
t.Fatalf("movie row should stay matched, got %q", status("movie1"))
}
if status("ep-other") != "matched" {
t.Fatalf("other library row should stay matched, got %q", status("ep-other"))
}
}
func TestRepairAndRescrapeLibraryExpandsMergedCloudLibraries(t *testing.T) {
db := newServiceTestDB(t, &model.Library{}, &model.Media{})
container := &Container{Repo: repository.New(db), Log: zap.NewNop()}
local := model.Library{Name: "国产剧", Path: "/media/电视剧/国产剧", Type: "tv", Enabled: true}
cloud := model.Library{
Name: "OpenList · 国产剧",
Path: BuildCloudLibraryPath("openlist", "/国产剧", "/国产剧"),
Type: "tv",
Enabled: true,
}
if err := container.Repo.Library.Create(t.Context(), &local); err != nil {
t.Fatal(err)
}
if err := container.Repo.Library.Create(t.Context(), &cloud); err != nil {
t.Fatal(err)
}
repairMedia := model.Media{
LibraryID: cloud.ID,
Title: "主角",
Path: "cloud://openlist/国产剧/主角 (2026) {tmdb-284110}/Season 1/主角.S01E01.mkv",
SeasonNum: 1,
EpisodeNum: 1,
ScrapeStatus: "matched",
}
resetMedia := model.Media{
LibraryID: cloud.ID,
Title: "无占位符剧集",
Path: "cloud://openlist/国产剧/无占位符剧集/Season 1/无占位符剧集.S01E01.mkv",
SeasonNum: 1,
EpisodeNum: 1,
ScrapeStatus: "matched",
}
if err := db.Create(&repairMedia).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&resetMedia).Error; err != nil {
t.Fatal(err)
}
result, err := container.RepairAndRescrapeLibrary(t.Context(), local.ID)
if err != nil {
t.Fatal(err)
}
if result.Repaired != 1 || result.Reset != 1 {
t.Fatalf("result = %+v, want repaired/reset for merged cloud row", result)
}
var repaired model.Media
if err := db.First(&repaired, "id = ?", repairMedia.ID).Error; err != nil {
t.Fatal(err)
}
if repaired.TMDbID != 284110 || repaired.ScrapeStatus != "pending" {
t.Fatalf("merged cloud row not repaired/reset: tmdb=%d status=%q", repaired.TMDbID, repaired.ScrapeStatus)
}
var reset model.Media
if err := db.First(&reset, "id = ?", resetMedia.ID).Error; err != nil {
t.Fatal(err)
}
if reset.ScrapeStatus != "pending" {
t.Fatalf("merged cloud row not reset: status=%q", reset.ScrapeStatus)
}
}