package tracker import ( "bytes" "context" "io" "net/http" "strings" "testing" "github.com/veggiedefender/torrent-client/internal/torrentfile" ) func TestParsePeers(t *testing.T) { t.Parallel() raw := []byte{127, 0, 0, 1, 0x1A, 0xE1} peers, err := parsePeers(raw) if err != nil { t.Fatalf("parsePeers returned error: %v", err) } if len(peers) != 1 { t.Fatalf("unexpected peer count: got %d", len(peers)) } if got, want := peers[0].IP.String(), "127.0.0.1"; got != want { t.Fatalf("unexpected peer IP: got %s want %s", got, want) } if got, want := peers[0].Port, uint16(6881); got != want { t.Fatalf("unexpected peer port: got %d want %d", got, want) } } func TestParsePeersRejectsInvalidLength(t *testing.T) { t.Parallel() _, err := parsePeers([]byte{1, 2, 3, 4, 5}) if err == nil { t.Fatal("expected parsePeers to fail for invalid compact peer data") } } func TestGetPeersBuildsAnnounceAndParsesResponse(t *testing.T) { t.Parallel() var infoHash [20]byte copy(infoHash[:], []byte("abcdefghijklmnopqrst")) var peerID [20]byte copy(peerID[:], []byte("-ZT0001-123456789012")) tf := &torrentfile.TorrentFile{ Announce: "http://tracker.test/announce", InfoHash: infoHash, Length: 1000, } var seenRequest *http.Request client := fakeClient{ do: func(req *http.Request) (*http.Response, error) { seenRequest = req payload := []byte("d8:intervali1800e5:peers6:\x7f\x00\x00\x01\x1a\xe1e") return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(payload)), }, nil }, } peers, err := getPeersWithClient(context.Background(), client, tf, AnnounceOptions{ PeerID: peerID, Port: 6881, Downloaded: 100, }) if err != nil { t.Fatalf("GetPeers returned error: %v", err) } if seenRequest == nil { t.Fatal("expected tracker request to be sent") } q := seenRequest.URL.Query() if got := q.Get("compact"); got != "1" { t.Fatalf("unexpected compact value: got %q", got) } if got := q.Get("port"); got != "6881" { t.Fatalf("unexpected port value: got %q", got) } if got := q.Get("left"); got != "900" { t.Fatalf("unexpected left value: got %q", got) } if got := q.Get("numwant"); got != "50" { t.Fatalf("unexpected numwant value: got %q", got) } if got := []byte(q.Get("info_hash")); !equalBytes(got, infoHash[:]) { t.Fatalf("unexpected info_hash value: got %x want %x", got, infoHash) } if got := []byte(q.Get("peer_id")); !equalBytes(got, peerID[:]) { t.Fatalf("unexpected peer_id value: got %x want %x", got, peerID) } if len(peers) != 1 { t.Fatalf("unexpected peer count: got %d", len(peers)) } if got, want := peers[0].Port, uint16(6881); got != want { t.Fatalf("unexpected peer port: got %d want %d", got, want) } } func TestBuildAnnounceURLEncodesBinaryValues(t *testing.T) { t.Parallel() var infoHash [20]byte copy(infoHash[:], []byte{0x00, 0xFF, 0x2F, 'A', 0x20}) var peerID [20]byte copy(peerID[:], []byte("-ZT0001-\x00\xFFabc")) tf := &torrentfile.TorrentFile{ Announce: "http://tracker.test/announce?token=abc", InfoHash: infoHash, Length: 10, } announceURL, err := buildAnnounceURL(tf, AnnounceOptions{ PeerID: peerID, Port: 6881, NumWant: 42, Uploaded: -5, Downloaded: -7, }) if err != nil { t.Fatalf("buildAnnounceURL returned error: %v", err) } if !strings.Contains(announceURL, "token=abc") { t.Fatalf("announce URL did not preserve existing query params: %s", announceURL) } if !strings.Contains(announceURL, "info_hash=%00%FF%2FA%20") { t.Fatalf("announce URL did not encode binary info hash correctly: %s", announceURL) } if !strings.Contains(announceURL, "peer_id=-ZT0001-%00%FFabc") { t.Fatalf("announce URL did not encode binary peer ID correctly: %s", announceURL) } if !strings.Contains(announceURL, "uploaded=0") { t.Fatalf("announce URL did not clamp uploaded value: %s", announceURL) } if !strings.Contains(announceURL, "downloaded=0") { t.Fatalf("announce URL did not clamp downloaded value: %s", announceURL) } if !strings.Contains(announceURL, "left=10") { t.Fatalf("announce URL did not compute left bytes correctly: %s", announceURL) } } func TestBuildAnnounceURLRejectsUnsupportedScheme(t *testing.T) { t.Parallel() _, err := buildAnnounceURL(&torrentfile.TorrentFile{ Announce: "udp://tracker.test:6969/announce", Length: 10, }, AnnounceOptions{}) if err == nil { t.Fatal("expected buildAnnounceURL to reject unsupported tracker scheme") } } func TestGetPeersReturnsTrackerFailure(t *testing.T) { t.Parallel() tf := &torrentfile.TorrentFile{ Announce: "http://tracker.test/announce", Length: 10, } client := fakeClient{ do: func(req *http.Request) (*http.Response, error) { payload := []byte("d14:failure reason11:bad requeste") return &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(payload)), }, nil }, } _, err := getPeersWithClient(context.Background(), client, tf, AnnounceOptions{}) if err == nil { t.Fatal("expected getPeersWithClient to return tracker failure error") } } type fakeClient struct { do func(req *http.Request) (*http.Response, error) } func (c fakeClient) Do(req *http.Request) (*http.Response, error) { return c.do(req) } func equalBytes(a, b []byte) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }