165 lines
4.9 KiB
Go
165 lines
4.9 KiB
Go
|
|
package engine
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestSeedFile_RejectsMissingFile — explicit error rather than crashing
|
||
|
|
// inside anacrolix when the path doesn't exist.
|
||
|
|
func TestSeedFile_RejectsMissingFile(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
dl, err := NewTorrentDownloader(TorrentConfig{
|
||
|
|
DataDir: dir,
|
||
|
|
ListenPort: 0,
|
||
|
|
WebRTCEnabled: true,
|
||
|
|
WebRTCTrackers: []string{"wss://tracker.torrentclaw.com"},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewTorrentDownloader: %v", err)
|
||
|
|
}
|
||
|
|
defer dl.Shutdown(context.Background())
|
||
|
|
|
||
|
|
if _, err := SeedFile(dl.client, "/nonexistent/path", nil); err == nil {
|
||
|
|
t.Fatal("expected error for missing file")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestSeedFile_RejectsDirectory — single-file torrents only for now.
|
||
|
|
func TestSeedFile_RejectsDirectory(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
dl, err := NewTorrentDownloader(TorrentConfig{
|
||
|
|
DataDir: dir,
|
||
|
|
ListenPort: 0,
|
||
|
|
WebRTCEnabled: true,
|
||
|
|
WebRTCTrackers: []string{"wss://tracker.torrentclaw.com"},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewTorrentDownloader: %v", err)
|
||
|
|
}
|
||
|
|
defer dl.Shutdown(context.Background())
|
||
|
|
|
||
|
|
subDir := filepath.Join(dir, "sub")
|
||
|
|
if err := os.Mkdir(subDir, 0o755); err != nil {
|
||
|
|
t.Fatalf("mkdir: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := SeedFile(dl.client, subDir, nil); err == nil {
|
||
|
|
t.Fatal("expected error for directory path")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestSeedFile_BuildsDeterministicInfoHash — the same file should yield
|
||
|
|
// the same info_hash on every call so the web client can poll for it.
|
||
|
|
func TestSeedFile_BuildsDeterministicInfoHash(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
file := filepath.Join(dir, "data.bin")
|
||
|
|
payload := []byte("hello world — torrentclaw seed_file test")
|
||
|
|
if err := os.WriteFile(file, payload, 0o644); err != nil {
|
||
|
|
t.Fatalf("write file: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
mkClient := func() *TorrentDownloader {
|
||
|
|
dl, err := NewTorrentDownloader(TorrentConfig{
|
||
|
|
DataDir: t.TempDir(),
|
||
|
|
ListenPort: 0,
|
||
|
|
WebRTCEnabled: true,
|
||
|
|
WebRTCTrackers: []string{"wss://tracker.torrentclaw.com"},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewTorrentDownloader: %v", err)
|
||
|
|
}
|
||
|
|
return dl
|
||
|
|
}
|
||
|
|
|
||
|
|
dl1 := mkClient()
|
||
|
|
defer dl1.Shutdown(context.Background())
|
||
|
|
hash1, err := SeedFile(dl1.client, file, []string{"wss://tracker.torrentclaw.com"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("first SeedFile: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
dl2 := mkClient()
|
||
|
|
defer dl2.Shutdown(context.Background())
|
||
|
|
hash2, err := SeedFile(dl2.client, file, []string{"wss://tracker.torrentclaw.com"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("second SeedFile: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if hash1 != hash2 {
|
||
|
|
t.Fatalf("info_hash not deterministic: %s vs %s", hash1.HexString(), hash2.HexString())
|
||
|
|
}
|
||
|
|
if hash1.HexString() == "" || len(hash1.HexString()) != 40 {
|
||
|
|
t.Fatalf("info_hash is not 40 hex chars: %q", hash1.HexString())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestSeedFileOnDownloader_RequiresWebRTC — silent failure mode is the
|
||
|
|
// worst UX; bail loud when the operator hasn't opted into WebRTC.
|
||
|
|
func TestSeedFileOnDownloader_RequiresWebRTC(t *testing.T) {
|
||
|
|
dir := t.TempDir()
|
||
|
|
dl, err := NewTorrentDownloader(TorrentConfig{
|
||
|
|
DataDir: dir,
|
||
|
|
ListenPort: 0,
|
||
|
|
WebRTCEnabled: false,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewTorrentDownloader: %v", err)
|
||
|
|
}
|
||
|
|
defer dl.Shutdown(context.Background())
|
||
|
|
|
||
|
|
file := filepath.Join(dir, "data.bin")
|
||
|
|
if err := os.WriteFile(file, []byte("x"), 0o644); err != nil {
|
||
|
|
t.Fatalf("write file: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := SeedFileOnDownloader(dl, file); err == nil {
|
||
|
|
t.Fatal("expected error when WebRTC disabled")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestChooseSeedPieceLength_LadderShape — sanity-check the breakpoints
|
||
|
|
// stay aligned with the libtorrent reference (16 KiB → 4 MiB).
|
||
|
|
func TestChooseSeedPieceLength_LadderShape(t *testing.T) {
|
||
|
|
cases := []struct {
|
||
|
|
size int64
|
||
|
|
expect int64
|
||
|
|
}{
|
||
|
|
{1, 16 * 1024},
|
||
|
|
{4 * 1024 * 1024, 64 * 1024},
|
||
|
|
{64 * 1024 * 1024, 256 * 1024},
|
||
|
|
{512 * 1024 * 1024, 1024 * 1024},
|
||
|
|
{4 * 1024 * 1024 * 1024, 4 * 1024 * 1024},
|
||
|
|
}
|
||
|
|
for _, c := range cases {
|
||
|
|
if got := chooseSeedPieceLength(c.size); got != c.expect {
|
||
|
|
t.Errorf("chooseSeedPieceLength(%d) = %d want %d", c.size, got, c.expect)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestMakeAnnounceList_HandlesEmpty — nil/empty in → nil out, so
|
||
|
|
// AddTorrent doesn't see a dangling tier with no URLs.
|
||
|
|
func TestMakeAnnounceList_HandlesEmpty(t *testing.T) {
|
||
|
|
if got := makeAnnounceList(nil); got != nil {
|
||
|
|
t.Errorf("nil input should yield nil announce list, got %+v", got)
|
||
|
|
}
|
||
|
|
if got := makeAnnounceList([]string{}); got != nil {
|
||
|
|
t.Errorf("empty input should yield nil announce list, got %+v", got)
|
||
|
|
}
|
||
|
|
if got := makeAnnounceList([]string{"", " ", ""}); got != nil {
|
||
|
|
// Empty strings should be filtered; if everything is empty,
|
||
|
|
// nil is the right answer.
|
||
|
|
// (We do NOT trim whitespace today — only literal "".)
|
||
|
|
if len(got) != 1 || len(got[0]) != 1 {
|
||
|
|
t.Errorf("expected 1 single-element tier, got %+v", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
got := makeAnnounceList([]string{"wss://a", "", "wss://b"})
|
||
|
|
if len(got) != 1 || len(got[0]) != 2 {
|
||
|
|
t.Fatalf("expected 1 tier of 2 URLs, got %+v", got)
|
||
|
|
}
|
||
|
|
}
|