feat(app): add internal components batch 2
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user