From 47cd20f2ea476a99b7cab18cceed54a222072a88 Mon Sep 17 00:00:00 2001 From: ShukeBta <272197458+ShukeBta@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:45:23 +0800 Subject: [PATCH] refactor: isolate download organize tests --- internal/service/downloads_organize_test.go | 195 ++++++++++++++++++++ internal/service/downloads_test.go | 183 ------------------ 2 files changed, 195 insertions(+), 183 deletions(-) create mode 100644 internal/service/downloads_organize_test.go diff --git a/internal/service/downloads_organize_test.go b/internal/service/downloads_organize_test.go new file mode 100644 index 0000000..a1a8ebf --- /dev/null +++ b/internal/service/downloads_organize_test.go @@ -0,0 +1,195 @@ +package service + +import ( + "os" + "path/filepath" + "testing" + + "go.uber.org/zap" + + "github.com/ShukeBta/MediaStationGo/internal/config" + "github.com/ShukeBta/MediaStationGo/internal/model" +) + +func TestDownloadCompleteAutoOrganizesContentPath(t *testing.T) { + root := t.TempDir() + src := filepath.Join(root, "downloads", "国产剧", "狂飙.S01E01.2023.1080p.mkv") + dest := filepath.Join(root, "media") + if err := os.MkdirAll(filepath.Dir(src), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(src, []byte("episode"), 0o644); err != nil { + t.Fatal(err) + } + + repos := newOrganizerTestRepo(t) + if err := repos.DB.AutoMigrate(&model.DownloadTask{}); err != nil { + t.Fatal(err) + } + for key, value := range map[string]string{ + "organizer.auto_after_download": "true", + "organize.target_dir": dest, + "organize.transfer_mode": "copy", + } { + if err := repos.Setting.Set(t.Context(), key, value); err != nil { + t.Fatal(err) + } + } + org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos) + svc := NewDownloadService(zap.NewNop(), repos, NewHub(zap.NewNop()), org) + svc.onTorrentComplete(t.Context(), QBitTorrent{ + Hash: "done123", + Name: "狂飙.S01E01.2023.1080p", + Progress: 1, + SavePath: filepath.Join(root, "downloads", "国产剧"), + ContentPath: src, + }) + + want := filepath.Join(dest, "电视剧", "国产剧", "狂飙", "Season 01", "狂飙 - S01E01.mkv") + if _, err := os.Stat(want); err != nil { + t.Fatalf("auto organized file missing at %q: %v", want, err) + } + if _, err := os.Stat(src); err != nil { + t.Fatalf("copy mode should keep source: %v", err) + } +} + +func TestDownloadCompleteAutoOrganizeUsesTaskMediaCategory(t *testing.T) { + root := t.TempDir() + src := filepath.Join(root, "downloads", "Motherhood.of.Taihang.S01E01.2026.1080p.mkv") + dest := filepath.Join(root, "media") + writeOrgFile(t, src, "episode") + + repos := newOrganizerTestRepo(t) + if err := repos.DB.AutoMigrate(&model.DownloadTask{}); err != nil { + t.Fatal(err) + } + for key, value := range map[string]string{ + "organizer.auto_after_download": "true", + "organize.target_dir": dest, + "organize.transfer_mode": "copy", + } { + if err := repos.Setting.Set(t.Context(), key, value); err != nil { + t.Fatal(err) + } + } + task := &model.DownloadTask{ + UserID: "u1", + Source: "qbittorrent", + URL: "magnet:?xt=urn:btih:motherhood", + Title: "Motherhood.of.Taihang.S01E01.2026.1080p", + SavePath: filepath.Join(root, "downloads"), + MediaType: "tv", + MediaCategory: "国产剧", + Status: "completed", + Progress: 1, + } + if err := repos.Download.Create(t.Context(), task); err != nil { + t.Fatal(err) + } + + org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos) + svc := NewDownloadService(zap.NewNop(), repos, NewHub(zap.NewNop()), org) + svc.onTorrentComplete(t.Context(), QBitTorrent{ + Hash: "done-category", + Name: "Motherhood.of.Taihang.S01E01.2026.1080p", + Progress: 1, + SavePath: filepath.Join(root, "downloads"), + ContentPath: src, + }) + + categoryRoot := filepath.Join(dest, "电视剧", "国产剧") + var organized string + err := filepath.WalkDir(categoryRoot, func(path string, entry os.DirEntry, err error) error { + if err != nil || entry.IsDir() { + return err + } + if filepath.Ext(path) == ".mkv" { + organized = path + } + return nil + }) + if err != nil { + t.Fatalf("walk organized category: %v", err) + } + if organized == "" { + t.Fatalf("expected organized file under %q", categoryRoot) + } + wrongRoot := filepath.Join(dest, "电视剧", "Motherhood Of Taihang") + if _, err := os.Stat(wrongRoot); !os.IsNotExist(err) { + t.Fatalf("unexpected uncategorized organize root %q, err=%v", wrongRoot, err) + } +} + +func TestDownloadCompleteOnlyReplacesExistingWhenTaskAllowsWash(t *testing.T) { + for _, tc := range []struct { + name string + allowWash bool + wantContent string + }{ + {name: "wash disabled keeps existing", allowWash: false, wantContent: "inception-1080p"}, + {name: "wash enabled replaces existing", allowWash: true, wantContent: "inception-2160p"}, + } { + t.Run(tc.name, func(t *testing.T) { + root := t.TempDir() + src := filepath.Join(root, "downloads", "Inception 2010 2160p BluRay.mkv") + dest := filepath.Join(root, "media") + existing := filepath.Join(dest, "电影", "Inception (2010)", "Inception (2010).mkv") + writeOrgFile(t, src, "inception-2160p") + writeOrgFile(t, existing, "inception-1080p") + + repos := newOrganizerTestRepo(t) + if err := repos.DB.AutoMigrate(&model.DownloadTask{}); err != nil { + t.Fatal(err) + } + for key, value := range map[string]string{ + "organizer.auto_after_download": "true", + "organize.target_dir": dest, + "organize.transfer_mode": "copy", + } { + if err := repos.Setting.Set(t.Context(), key, value); err != nil { + t.Fatal(err) + } + } + if err := repos.Media.Upsert(t.Context(), &model.Media{ + Title: "Inception", + Path: existing, + Year: 2010, + Container: "mkv", + Width: 1920, + Height: 1080, + }); err != nil { + t.Fatal(err) + } + if err := repos.Download.Create(t.Context(), &model.DownloadTask{ + Source: "qbittorrent", + URL: "magnet:?xt=urn:btih:inception", + Title: "Inception 2010 2160p BluRay", + SavePath: filepath.Dir(src), + Status: "completed", + Progress: 1, + AllowExistingLibrary: tc.allowWash, + }); err != nil { + t.Fatal(err) + } + + org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos) + svc := NewDownloadService(zap.NewNop(), repos, NewHub(zap.NewNop()), org) + svc.onTorrentComplete(t.Context(), QBitTorrent{ + Hash: "inception", + Name: "Inception 2010 2160p BluRay", + Progress: 1, + SavePath: filepath.Dir(src), + ContentPath: src, + }) + + got, err := os.ReadFile(existing) + if err != nil { + t.Fatal(err) + } + if string(got) != tc.wantContent { + t.Fatalf("existing content = %q, want %q", string(got), tc.wantContent) + } + }) + } +} diff --git a/internal/service/downloads_test.go b/internal/service/downloads_test.go index bcef269..34c9db2 100644 --- a/internal/service/downloads_test.go +++ b/internal/service/downloads_test.go @@ -232,189 +232,6 @@ func TestProcessDownloadSnapshotSkipsUntrackedCompletedTorrentOnFirstSnapshot(t } } -func TestDownloadCompleteAutoOrganizesContentPath(t *testing.T) { - root := t.TempDir() - src := filepath.Join(root, "downloads", "国产剧", "狂飙.S01E01.2023.1080p.mkv") - dest := filepath.Join(root, "media") - if err := os.MkdirAll(filepath.Dir(src), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(src, []byte("episode"), 0o644); err != nil { - t.Fatal(err) - } - - repos := newOrganizerTestRepo(t) - if err := repos.DB.AutoMigrate(&model.DownloadTask{}); err != nil { - t.Fatal(err) - } - for key, value := range map[string]string{ - "organizer.auto_after_download": "true", - "organize.target_dir": dest, - "organize.transfer_mode": "copy", - } { - if err := repos.Setting.Set(t.Context(), key, value); err != nil { - t.Fatal(err) - } - } - org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos) - svc := NewDownloadService(zap.NewNop(), repos, NewHub(zap.NewNop()), org) - svc.onTorrentComplete(t.Context(), QBitTorrent{ - Hash: "done123", - Name: "狂飙.S01E01.2023.1080p", - Progress: 1, - SavePath: filepath.Join(root, "downloads", "国产剧"), - ContentPath: src, - }) - - want := filepath.Join(dest, "电视剧", "国产剧", "狂飙", "Season 01", "狂飙 - S01E01.mkv") - if _, err := os.Stat(want); err != nil { - t.Fatalf("auto organized file missing at %q: %v", want, err) - } - if _, err := os.Stat(src); err != nil { - t.Fatalf("copy mode should keep source: %v", err) - } -} - -func TestDownloadCompleteAutoOrganizeUsesTaskMediaCategory(t *testing.T) { - root := t.TempDir() - src := filepath.Join(root, "downloads", "Motherhood.of.Taihang.S01E01.2026.1080p.mkv") - dest := filepath.Join(root, "media") - writeOrgFile(t, src, "episode") - - repos := newOrganizerTestRepo(t) - if err := repos.DB.AutoMigrate(&model.DownloadTask{}); err != nil { - t.Fatal(err) - } - for key, value := range map[string]string{ - "organizer.auto_after_download": "true", - "organize.target_dir": dest, - "organize.transfer_mode": "copy", - } { - if err := repos.Setting.Set(t.Context(), key, value); err != nil { - t.Fatal(err) - } - } - task := &model.DownloadTask{ - UserID: "u1", - Source: "qbittorrent", - URL: "magnet:?xt=urn:btih:motherhood", - Title: "Motherhood.of.Taihang.S01E01.2026.1080p", - SavePath: filepath.Join(root, "downloads"), - MediaType: "tv", - MediaCategory: "国产剧", - Status: "completed", - Progress: 1, - } - if err := repos.Download.Create(t.Context(), task); err != nil { - t.Fatal(err) - } - - org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos) - svc := NewDownloadService(zap.NewNop(), repos, NewHub(zap.NewNop()), org) - svc.onTorrentComplete(t.Context(), QBitTorrent{ - Hash: "done-category", - Name: "Motherhood.of.Taihang.S01E01.2026.1080p", - Progress: 1, - SavePath: filepath.Join(root, "downloads"), - ContentPath: src, - }) - - categoryRoot := filepath.Join(dest, "电视剧", "国产剧") - var organized string - err := filepath.WalkDir(categoryRoot, func(path string, entry os.DirEntry, err error) error { - if err != nil || entry.IsDir() { - return err - } - if filepath.Ext(path) == ".mkv" { - organized = path - } - return nil - }) - if err != nil { - t.Fatalf("walk organized category: %v", err) - } - if organized == "" { - t.Fatalf("expected organized file under %q", categoryRoot) - } - wrongRoot := filepath.Join(dest, "电视剧", "Motherhood Of Taihang") - if _, err := os.Stat(wrongRoot); !os.IsNotExist(err) { - t.Fatalf("unexpected uncategorized organize root %q, err=%v", wrongRoot, err) - } -} - -func TestDownloadCompleteOnlyReplacesExistingWhenTaskAllowsWash(t *testing.T) { - for _, tc := range []struct { - name string - allowWash bool - wantContent string - }{ - {name: "wash disabled keeps existing", allowWash: false, wantContent: "inception-1080p"}, - {name: "wash enabled replaces existing", allowWash: true, wantContent: "inception-2160p"}, - } { - t.Run(tc.name, func(t *testing.T) { - root := t.TempDir() - src := filepath.Join(root, "downloads", "Inception 2010 2160p BluRay.mkv") - dest := filepath.Join(root, "media") - existing := filepath.Join(dest, "电影", "Inception (2010)", "Inception (2010).mkv") - writeOrgFile(t, src, "inception-2160p") - writeOrgFile(t, existing, "inception-1080p") - - repos := newOrganizerTestRepo(t) - if err := repos.DB.AutoMigrate(&model.DownloadTask{}); err != nil { - t.Fatal(err) - } - for key, value := range map[string]string{ - "organizer.auto_after_download": "true", - "organize.target_dir": dest, - "organize.transfer_mode": "copy", - } { - if err := repos.Setting.Set(t.Context(), key, value); err != nil { - t.Fatal(err) - } - } - if err := repos.Media.Upsert(t.Context(), &model.Media{ - Title: "Inception", - Path: existing, - Year: 2010, - Container: "mkv", - Width: 1920, - Height: 1080, - }); err != nil { - t.Fatal(err) - } - if err := repos.Download.Create(t.Context(), &model.DownloadTask{ - Source: "qbittorrent", - URL: "magnet:?xt=urn:btih:inception", - Title: "Inception 2010 2160p BluRay", - SavePath: filepath.Dir(src), - Status: "completed", - Progress: 1, - AllowExistingLibrary: tc.allowWash, - }); err != nil { - t.Fatal(err) - } - - org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos) - svc := NewDownloadService(zap.NewNop(), repos, NewHub(zap.NewNop()), org) - svc.onTorrentComplete(t.Context(), QBitTorrent{ - Hash: "inception", - Name: "Inception 2010 2160p BluRay", - Progress: 1, - SavePath: filepath.Dir(src), - ContentPath: src, - }) - - got, err := os.ReadFile(existing) - if err != nil { - t.Fatal(err) - } - if string(got) != tc.wantContent { - t.Fatalf("existing content = %q, want %q", string(got), tc.wantContent) - } - }) - } -} - func TestCompletedTorrentSourceDoesNotFallbackToSavePath(t *testing.T) { root := t.TempDir() savePath := filepath.Join(root, "downloads", "日番")