Files
ggjuku/internal/app/download_directories_test.go
T
2026-09-30 10:19:54 +08:00

184 lines
6.6 KiB
Go

package app
import (
"os"
"path/filepath"
"strings"
"sync"
"testing"
)
func TestDownloadGroupingKeepsExistingDirectoriesAcrossChanges(t *testing.T) {
cfg := defaultConfig()
cfg.OutputDir = t.TempDir()
d := &Downloader{cfg: cfg}
old := Drama{ID: historyFixtureDramaID, Source: sourceHongguo, Title: "同名剧"}
flat, err := d.downloadDramaDirectory(old, old.Title, "")
if err != nil || flat != filepath.Join(cfg.OutputDir, old.Title) {
t.Fatal("default layout changed", flat, err)
}
sentinel := filepath.Join(flat, "001.mp4")
if err := os.WriteFile(sentinel, []byte("original episode"), 0600); err != nil {
t.Fatal(err)
}
grouped := true
d.downloadGrouping = &grouped
old.Title = "更新后的剧名"
if same, err := d.downloadDramaDirectory(old, old.Title, ""); err != nil || same != flat {
t.Fatal("enabling grouping or renaming moved an existing drama", same, err)
}
var groupedPaths []string
for index, id := range []string{"hongguo:7000000000000000002", "hongguo:7000000000000000003"} {
drama := Drama{ID: id, Source: sourceHongguo, Title: "同名剧"}
directory, err := d.downloadDramaDirectory(drama, drama.Title, "")
want := filepath.Join(cfg.OutputDir, "红果", drama.Title)
if index > 0 {
want += "_" + hashShort(drama.ID)
}
if err != nil || directory != want {
t.Fatal("new drama did not use its source directory", directory, err)
}
groupedPaths = append(groupedPaths, directory)
}
if groupedPaths[0] == groupedPaths[1] {
t.Fatal("same title from different drama IDs collided")
}
d = &Downloader{cfg: cfg}
if same, err := d.downloadDramaDirectory(old, old.Title, ""); err != nil || same != flat {
t.Fatal("restart lost the original flat directory", same, err)
}
for index, id := range []string{"hongguo:7000000000000000002", "hongguo:7000000000000000003"} {
drama := Drama{ID: id, Source: sourceHongguo}
if same, err := d.downloadDramaDirectory(drama, "再次改名", ""); err != nil || same != groupedPaths[index] {
t.Fatal("disabling grouping moved an existing grouped drama", same, err)
}
}
if body, _ := os.ReadFile(sentinel); string(body) != "original episode" {
t.Fatal("directory changes touched existing media")
}
}
func TestDownloadGroupingRejectsSourceSymlinksAndDramaCollision(t *testing.T) {
for _, kind := range []string{"symlink", "existing-drama"} {
t.Run(kind, func(t *testing.T) {
cfg := defaultConfig()
cfg.OutputDir, cfg.GroupBySource = t.TempDir(), true
drama := Drama{ID: historyFixtureDramaID, Source: sourceHongguo}
group := filepath.Join(cfg.OutputDir, dramaSourceFolder(drama))
outside := t.TempDir()
if kind == "symlink" {
if err := os.Symlink(outside, group); err != nil {
t.Skip("symlinks unavailable")
}
} else {
if ok, err := claimDownloadDirectory(group, "older-drama", false); err != nil || !ok {
t.Fatal(err)
}
}
if _, err := (&Downloader{cfg: cfg}).downloadDramaDirectory(drama, "新剧", ""); err == nil {
t.Fatal("unsafe source parent accepted")
}
if entries, _ := os.ReadDir(outside); len(entries) != 0 {
t.Fatal("wrote through source symlink")
}
if _, err := os.Stat(filepath.Join(group, "新剧")); !os.IsNotExist(err) {
t.Fatal("created new drama inside an existing drama")
}
})
}
}
func TestDownloadDirectoryOwnershipAndConcurrentAllocation(t *testing.T) {
cfg := defaultConfig()
cfg.OutputDir, cfg.GroupBySource = t.TempDir(), true
d := &Downloader{cfg: cfg}
drama := Drama{ID: historyFixtureDramaID, Source: sourceHongguo}
results := make(chan string, 12)
var workers sync.WaitGroup
for i := 0; i < cap(results); i++ {
workers.Add(1)
go func() {
defer workers.Done()
directory, err := d.downloadDramaDirectory(drama, "并发同一剧", "")
if err != nil {
t.Error(err)
}
results <- directory
}()
}
workers.Wait()
close(results)
want := filepath.Join(cfg.OutputDir, "红果", "并发同一剧")
for directory := range results {
if directory != want {
t.Fatal("concurrent enqueue allocated different directories", directory)
}
}
duplicate := filepath.Join(cfg.OutputDir, "重复副本")
if ok, err := claimDownloadDirectory(duplicate, drama.ID, false); err != nil || !ok {
t.Fatal(err)
}
if _, err := (&Downloader{cfg: cfg}).downloadDramaDirectory(drama, "第三份", ""); err == nil || !strings.Contains(err.Error(), "多个") {
t.Fatal("ambiguous ownership created a third copy", err)
}
if same, err := (&Downloader{cfg: cfg}).downloadDramaDirectory(drama, "第三份", want); err != nil || same != want {
t.Fatal("persisted task location did not resolve ambiguity", same, err)
}
}
func TestSourceGroupingConfigAndTasklessDirectorySurviveRestart(t *testing.T) {
cfg := defaultConfig()
if cfg.GroupBySource || documentFromConfig(cfg).Download.GroupBySource {
t.Fatal("grouping must be opt-in")
}
directory := t.TempDir()
enabled := true
settings := runtimeSettings{Concurrency: 2, RequestConcurrency: 2, RequestIntervalMS: 500, GroupBySource: &enabled}
if err := saveRuntimeSettings(directory, settings); err != nil {
t.Fatal(err)
}
settings.GroupBySource = nil
settings.Concurrency = 3
if err := saveRuntimeSettings(directory, settings); err != nil {
t.Fatal(err)
}
document, err := readConfigDocument(runtimeSettingsPath(directory))
if err != nil {
t.Fatal(err)
}
loaded, err := document.config()
if err != nil || !loaded.GroupBySource || loaded.Concurrency != 3 {
t.Fatal("older settings request reset grouping", err)
}
savedPath := filepath.Join(t.TempDir(), "原目录")
app := &UIApp{cfg: cfg, statePath: filepath.Join(directory, "ui-state.json"),
dramaDirectories: map[string]string{historyFixtureDramaID: savedPath}}
if err := app.saveStateLocked(); err != nil {
t.Fatal(err)
}
restarted := &UIApp{cfg: cfg, statePath: app.statePath, tasks: map[string]*UITask{}}
restarted.loadState()
if got := restarted.dramaDirectoryLocked(historyFixtureDramaID); got != savedPath || len(restarted.tasks) != 0 {
t.Fatal("cleaning task records lost the stable download directory", got)
}
}
func TestDownloadDirectoryRejectsUnsupportedSources(t *testing.T) {
cfg := defaultConfig()
cfg.OutputDir, cfg.GroupBySource = t.TempDir(), true
d := &Downloader{cfg: cfg}
for _, drama := range []Drama{
{ID: "huangdou:7000000000000000002", Source: "huangdou"},
{ID: historyFixtureDramaID, Source: "huangdou"},
{ID: "huangguo_video:123", Source: sourceHongguo},
} {
if _, err := d.downloadDramaDirectory(drama, "不支持的来源", ""); err == nil {
t.Fatal("accepted a non-Hongguo drama", drama.ID)
}
}
if entries, err := os.ReadDir(cfg.OutputDir); err != nil || len(entries) != 0 {
t.Fatal("unsupported source created download directories", err)
}
}