unarr/internal/engine/usenet_test.go

137 lines
4.3 KiB
Go

package engine
import (
"context"
"strings"
"sync"
"testing"
"time"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/usenet/download"
"github.com/torrentclaw/unarr/internal/usenet/nzb"
)
// emptyNZB returns a minimal NZB with no files, suitable for test tracker creation.
func emptyNZB() *nzb.NZB { return &nzb.NZB{} }
// TestUsenetDownloader_Cancel_NoRace verifies that Cancel() reads tracker and taskDir
// under the mutex, avoiding a data race with Download() which writes them under the same lock.
// Run with -race to detect the race if it regresses.
func TestUsenetDownloader_Cancel_NoRace(t *testing.T) {
u := NewUsenetDownloader(agent.NewClient("http://localhost", "", "test"))
const taskID = "race-test-taskid-123456"
// Inject a fake activeDownload without tracker/taskDir set yet.
// We only need the cancel func; discard the context itself.
_, cancel := context.WithCancel(context.Background())
dl := &activeDownload{cancel: cancel}
u.mu.Lock()
u.active[taskID] = dl
u.mu.Unlock()
var wg sync.WaitGroup
// Goroutine 1: simulates Download() setting tracker and taskDir under lock.
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 50; i++ {
tracker := download.NewProgressTracker(taskID, emptyNZB(), t.TempDir())
u.mu.Lock()
dl.tracker = tracker
dl.taskDir = t.TempDir()
u.mu.Unlock()
time.Sleep(time.Microsecond)
}
}()
// Goroutine 2: calls Cancel() concurrently — must read under lock.
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 50; i++ {
u.Cancel(taskID) //nolint:errcheck
time.Sleep(time.Microsecond)
}
}()
wg.Wait()
}
// TestUsenetDownloader_Cancel_NonExistent verifies Cancel on unknown task returns nil.
func TestUsenetDownloader_Cancel_NonExistent(t *testing.T) {
u := NewUsenetDownloader(agent.NewClient("http://localhost", "", "test"))
if err := u.Cancel("no-such-task"); err != nil {
t.Errorf("Cancel non-existent task = %v, want nil", err)
}
}
// TestUsenetDownloader_Pause_NonExistent verifies Pause on unknown task returns nil.
func TestUsenetDownloader_Pause_NonExistent(t *testing.T) {
u := NewUsenetDownloader(agent.NewClient("http://localhost", "", "test"))
if err := u.Pause("no-such-task"); err != nil {
t.Errorf("Pause non-existent task = %v, want nil", err)
}
}
func TestUsenetDownloader_MethodAndAvailable(t *testing.T) {
u := NewUsenetDownloader(agent.NewClient("http://localhost", "", "test"))
if got := u.Method(); got != MethodUsenet {
t.Errorf("Method = %v, want %v", got, MethodUsenet)
}
// Disabled → never available, no error.
u.SetEnabled(false)
ok, err := u.Available(context.Background(), &Task{Title: "Foo"})
if err != nil || ok {
t.Errorf("disabled Available = (%v,%v), want (false,nil)", ok, err)
}
u.SetEnabled(true)
// No IMDb / no title → not available, no error.
ok, err = u.Available(context.Background(), &Task{})
if err != nil || ok {
t.Errorf("empty task Available = (%v,%v), want (false,nil)", ok, err)
}
// Pre-resolved NzbID → available immediately.
ok, err = u.Available(context.Background(), &Task{NzbID: "preresolved", Title: "Bar"})
if err != nil || !ok {
t.Errorf("preresolved NzbID Available = (%v,%v), want (true,nil)", ok, err)
}
}
func TestUsenetDownloader_Shutdown(t *testing.T) {
u := NewUsenetDownloader(agent.NewClient("http://localhost", "", "test"))
// Inject a fake active download — Shutdown should cancel it and clear the map.
_, cancel := context.WithCancel(context.Background())
u.active["t1"] = &activeDownload{cancel: cancel}
if err := u.Shutdown(context.Background()); err != nil {
t.Errorf("Shutdown = %v, want nil", err)
}
if len(u.active) != 0 {
t.Errorf("Shutdown should clear active downloads, got %d", len(u.active))
}
}
func TestSanitizeDir(t *testing.T) {
cases := map[string]string{
"": "usenet_download",
"normal_name": "normal_name",
"path/with/slashes": "path_with_slashes",
`win\\bad:name*?"<>|`: "win__bad_name______",
"con:tains/all\\bad?chars*": "con_tains_all_bad_chars_",
}
for in, want := range cases {
if got := sanitizeDir(in); got != want {
t.Errorf("sanitizeDir(%q) = %q, want %q", in, got, want)
}
}
long := strings.Repeat("a", 300)
if got := sanitizeDir(long); len(got) != 200 {
t.Errorf("expected sanitizeDir to truncate to 200, got %d", len(got))
}
}