184 lines
6.6 KiB
Go
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)
|
|
}
|
|
}
|