mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 15:49:49 +00:00
369 lines
9.4 KiB
Go
369 lines
9.4 KiB
Go
package bt
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
gohttp "net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/GopeedLab/gopeed/internal/controller"
|
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
|
"github.com/GopeedLab/gopeed/internal/test"
|
|
"github.com/GopeedLab/gopeed/pkg/base"
|
|
"github.com/GopeedLab/gopeed/pkg/protocol/bt"
|
|
)
|
|
|
|
func TestFetcher_Resolve_Torrent(t *testing.T) {
|
|
doResolve(t, buildFetcher())
|
|
}
|
|
|
|
func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) {
|
|
fetcher := buildFetcher()
|
|
buf, err := os.ReadFile("./testdata/ubuntu-22.04-live-server-amd64.iso.torrent")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// convert to data uri
|
|
dataUri := "data:application/x-bittorrent;base64," + base64.StdEncoding.EncodeToString(buf)
|
|
err = fetcher.Resolve(&base.Request{
|
|
URL: dataUri,
|
|
}, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
want := &base.Resource{
|
|
Size: 1466714112,
|
|
Range: true,
|
|
Files: []*base.FileInfo{
|
|
{
|
|
Name: "ubuntu-22.04-live-server-amd64.iso",
|
|
Size: 1466714112,
|
|
},
|
|
},
|
|
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
}
|
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
|
t.Errorf("Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
|
}
|
|
}
|
|
|
|
func TestFetcher_Config(t *testing.T) {
|
|
doResolve(t, buildConfigFetcher(nil))
|
|
}
|
|
|
|
func TestFetcher_ResolveWithProxy(t *testing.T) {
|
|
usr, pwd := "admin", "123"
|
|
proxyListener := test.StartSocks5Server(usr, pwd)
|
|
defer proxyListener.Close()
|
|
|
|
doResolve(t, buildConfigFetcher(&base.DownloaderProxyConfig{
|
|
Enable: true,
|
|
System: false,
|
|
Scheme: "socks5",
|
|
Host: proxyListener.Addr().String(),
|
|
Usr: usr,
|
|
Pwd: pwd,
|
|
}))
|
|
}
|
|
|
|
func doResolve(t *testing.T, fetcher fetcher.Fetcher) {
|
|
t.Run("Resolve Single File", func(t *testing.T) {
|
|
err := fetcher.Resolve(&base.Request{
|
|
URL: "./testdata/ubuntu-22.04-live-server-amd64.iso.torrent",
|
|
Extra: bt.ReqExtra{
|
|
Trackers: []string{
|
|
"udp://tracker.birkenwald.de:6969/announce",
|
|
"udp://tracker.bitsearch.to:1337/announce",
|
|
},
|
|
},
|
|
}, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
want := &base.Resource{
|
|
Size: 1466714112,
|
|
Range: true,
|
|
Files: []*base.FileInfo{
|
|
{
|
|
Name: "ubuntu-22.04-live-server-amd64.iso",
|
|
Size: 1466714112,
|
|
},
|
|
},
|
|
Hash: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
}
|
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
|
t.Errorf("Resolve Single File Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
|
}
|
|
})
|
|
|
|
t.Run("Resolve Multi Files", func(t *testing.T) {
|
|
err := fetcher.Resolve(&base.Request{
|
|
URL: "./testdata/test.torrent",
|
|
Extra: bt.ReqExtra{
|
|
Trackers: []string{
|
|
"udp://tracker.birkenwald.de:6969/announce",
|
|
"udp://tracker.bitsearch.to:1337/announce",
|
|
},
|
|
},
|
|
}, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
want := &base.Resource{
|
|
Name: "test",
|
|
Size: 107484864,
|
|
Range: true,
|
|
Files: []*base.FileInfo{
|
|
{
|
|
Name: "c.txt",
|
|
Path: "path",
|
|
Size: 98501754,
|
|
},
|
|
{
|
|
Name: "b.txt",
|
|
Size: 8904996,
|
|
},
|
|
{
|
|
Name: "a.txt",
|
|
Size: 78114,
|
|
},
|
|
},
|
|
Hash: "ccbc92b0cd8deec16a2ef4be242a8c9243b1cedb",
|
|
}
|
|
if !reflect.DeepEqual(want, fetcher.Meta().Res) {
|
|
t.Errorf("Resolve Multi Files Resolve() got = %v, want %v", fetcher.Meta().Res, want)
|
|
}
|
|
})
|
|
|
|
t.Run("Resolve Unclean Torrent", func(t *testing.T) {
|
|
err := fetcher.Resolve(&base.Request{
|
|
URL: "./testdata/test.unclean.torrent",
|
|
}, nil)
|
|
if err != nil {
|
|
t.Errorf("Resolve Unclean Torrent Resolve() got = %v, want nil", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Resolve file scheme Torrent", func(t *testing.T) {
|
|
file, _ := filepath.Abs("./testdata/test.unclean.torrent")
|
|
uri := "file:///" + file
|
|
err := fetcher.Resolve(&base.Request{
|
|
URL: uri,
|
|
}, nil)
|
|
if err != nil {
|
|
t.Errorf("Resolve file scheme Torrent Resolve() got = %v, want nil", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFetcherManager_ParseName(t *testing.T) {
|
|
type args struct {
|
|
u string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{
|
|
name: "broken url",
|
|
args: args{
|
|
u: "magnet://!@#%github.com",
|
|
},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "dn",
|
|
args: args{
|
|
u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed&dn=ubuntu-22.04-live-server-amd64.iso",
|
|
},
|
|
want: "ubuntu-22.04-live-server-amd64.iso",
|
|
},
|
|
{
|
|
name: "no dn",
|
|
args: args{
|
|
u: "magnet:?xt=urn:btih:8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
},
|
|
want: "8a55cfbd5ca5d11507364765936c4f9e55b253ed",
|
|
},
|
|
{
|
|
name: "non standard magnet",
|
|
args: args{
|
|
u: "magnet:?xxt=abcd",
|
|
},
|
|
want: "",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
fm := &FetcherManager{}
|
|
if got := fm.ParseName(tt.args.u); got != tt.want {
|
|
t.Errorf("ParseName() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func buildFetcher() fetcher.Fetcher {
|
|
fb := new(FetcherManager)
|
|
fetcher := fb.Build()
|
|
newController := controller.NewController()
|
|
newController.GetConfig = func(v any) {
|
|
json.Unmarshal([]byte(test.ToJson(fb.DefaultConfig())), v)
|
|
}
|
|
fetcher.Setup(newController)
|
|
return fetcher
|
|
}
|
|
|
|
func buildConfigFetcher(proxyConfig *base.DownloaderProxyConfig) fetcher.Fetcher {
|
|
fetcher := new(FetcherManager).Build()
|
|
newController := controller.NewController()
|
|
mockCfg := config{
|
|
Trackers: []string{
|
|
"udp://tracker.birkenwald.de:6969/announce",
|
|
"udp://tracker.bitsearch.to:1337/announce",
|
|
}}
|
|
newController.GetConfig = func(v any) {
|
|
json.Unmarshal([]byte(test.ToJson(mockCfg)), v)
|
|
}
|
|
newController.GetProxy = func(requestProxy *base.RequestProxy) func(*gohttp.Request) (*url.URL, error) {
|
|
return proxyConfig.ToHandler()
|
|
}
|
|
fetcher.Setup(newController)
|
|
return fetcher
|
|
}
|
|
|
|
// TestFetcher_Patch tests the Patch functionality for BT fetcher.
|
|
// It tests modifying selected files after Resolve (without downloading).
|
|
func TestFetcher_Patch(t *testing.T) {
|
|
f := buildFetcher()
|
|
|
|
// Resolve a multi-file torrent
|
|
err := f.Resolve(&base.Request{
|
|
URL: "./testdata/test.torrent",
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatalf("Resolve failed: %v", err)
|
|
}
|
|
|
|
// Verify initial state: 3 files, all selected by default
|
|
meta := f.Meta()
|
|
if len(meta.Res.Files) != 3 {
|
|
t.Fatalf("Expected 3 files, got %d", len(meta.Res.Files))
|
|
}
|
|
if len(meta.Opts.SelectFiles) != 3 {
|
|
t.Fatalf("Expected 3 selected files, got %d", len(meta.Opts.SelectFiles))
|
|
}
|
|
|
|
// Total size of all files: 107484864
|
|
totalSize := int64(107484864)
|
|
if meta.Res.Size != totalSize {
|
|
t.Fatalf("Expected total size %d, got %d", totalSize, meta.Res.Size)
|
|
}
|
|
|
|
t.Run("Patch with valid indices", func(t *testing.T) {
|
|
// Select only file 0 and 2 (c.txt and a.txt)
|
|
err := f.Patch(nil, &base.Options{
|
|
SelectFiles: []int{0, 2},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Patch failed: %v", err)
|
|
}
|
|
|
|
meta := f.Meta()
|
|
if !reflect.DeepEqual(meta.Opts.SelectFiles, []int{0, 2}) {
|
|
t.Errorf("Expected SelectFiles [0, 2], got %v", meta.Opts.SelectFiles)
|
|
}
|
|
|
|
// Size should be recalculated: c.txt (98501754) + a.txt (78114) = 98579868
|
|
expectedSize := int64(98501754 + 78114)
|
|
if meta.Res.Size != expectedSize {
|
|
t.Errorf("Expected size %d, got %d", expectedSize, meta.Res.Size)
|
|
}
|
|
})
|
|
|
|
t.Run("Patch with invalid indices are silently ignored", func(t *testing.T) {
|
|
// Mix of valid (0, 1) and invalid (-1, 5, 100) indices
|
|
err := f.Patch(nil, &base.Options{
|
|
SelectFiles: []int{-1, 0, 5, 1, 100},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Patch should not return error for invalid indices: %v", err)
|
|
}
|
|
|
|
meta := f.Meta()
|
|
// Only valid indices 0 and 1 should remain
|
|
if !reflect.DeepEqual(meta.Opts.SelectFiles, []int{0, 1}) {
|
|
t.Errorf("Expected SelectFiles [0, 1], got %v", meta.Opts.SelectFiles)
|
|
}
|
|
|
|
// Size should be: c.txt (98501754) + b.txt (8904996) = 107406750
|
|
expectedSize := int64(98501754 + 8904996)
|
|
if meta.Res.Size != expectedSize {
|
|
t.Errorf("Expected size %d, got %d", expectedSize, meta.Res.Size)
|
|
}
|
|
})
|
|
|
|
t.Run("Patch with all invalid indices results in empty selection", func(t *testing.T) {
|
|
err := f.Patch(nil, &base.Options{
|
|
SelectFiles: []int{-5, 10, 999},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Patch should not return error: %v", err)
|
|
}
|
|
|
|
meta := f.Meta()
|
|
if len(meta.Opts.SelectFiles) != 0 {
|
|
t.Errorf("Expected empty SelectFiles, got %v", meta.Opts.SelectFiles)
|
|
}
|
|
|
|
// Note: CalcSize with empty selectFiles calculates total size of all files
|
|
// This is by design - empty selection in CalcSize means "all files"
|
|
// But SelectFiles being empty means no files are selected for download
|
|
if meta.Res.Size != totalSize {
|
|
t.Errorf("Expected size %d (CalcSize with empty slice = all files), got %d", totalSize, meta.Res.Size)
|
|
}
|
|
})
|
|
|
|
t.Run("Patch with nil opts does nothing", func(t *testing.T) {
|
|
// First set a known state
|
|
f.Patch(nil, &base.Options{SelectFiles: []int{1}})
|
|
prevSelectFiles := f.Meta().Opts.SelectFiles
|
|
|
|
// Patch with nil opts
|
|
err := f.Patch(nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("Patch with nil opts should not fail: %v", err)
|
|
}
|
|
|
|
// Should remain unchanged
|
|
if !reflect.DeepEqual(f.Meta().Opts.SelectFiles, prevSelectFiles) {
|
|
t.Errorf("SelectFiles should remain unchanged after nil opts Patch")
|
|
}
|
|
})
|
|
|
|
t.Run("Patch progress array is resized", func(t *testing.T) {
|
|
btFetcher := f.(*Fetcher)
|
|
// Initialize progress array
|
|
btFetcher.data.Progress = make(fetcher.Progress, 3)
|
|
|
|
err := f.Patch(nil, &base.Options{
|
|
SelectFiles: []int{0, 2},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Patch failed: %v", err)
|
|
}
|
|
|
|
if len(btFetcher.data.Progress) != 2 {
|
|
t.Errorf("Expected Progress length 2, got %d", len(btFetcher.data.Progress))
|
|
}
|
|
})
|
|
}
|