summaryrefslogtreecommitdiff
path: root/tests/integration/test_announce.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration/test_announce.py')
-rw-r--r--tests/integration/test_announce.py129
1 files changed, 129 insertions, 0 deletions
diff --git a/tests/integration/test_announce.py b/tests/integration/test_announce.py
new file mode 100644
index 0000000..ffbc07b
--- /dev/null
+++ b/tests/integration/test_announce.py
@@ -0,0 +1,129 @@
1"""What a peer's announce does to the swarm it joins."""
2
3import os
4import socket
5import sys
6import unittest
7
8sys.path.insert(0, os.path.dirname(__file__))
9from harness import TrackerTestCase
10
11
12def peers_of(answer, key=b"peers"):
13 """Compact peer list as a list of (address, port)."""
14 blob, out = answer.get(key, b""), []
15 size = 6 if key == b"peers" else 18
16 for offset in range(0, len(blob), size):
17 entry = blob[offset : offset + size]
18 address = socket.inet_ntop(socket.AF_INET if size == 6 else socket.AF_INET6, entry[:-2])
19 out.append((address, entry[-2] << 8 | entry[-1]))
20 return out
21
22
23class AnnounceLifetime(TrackerTestCase):
24 config = ""
25
26 def test_a_leecher_is_counted_as_incomplete(self):
27 info_hash = self.hash_for("leecher")
28 answer = self.tracker.announce(info_hash, left=100)
29 self.assertEqual(answer[b"complete"], 0)
30 self.assertEqual(answer[b"incomplete"], 1)
31
32 def test_no_bytes_left_means_seeding(self):
33 info_hash = self.hash_for("seeder")
34 answer = self.tracker.announce(info_hash, left=0)
35 self.assertEqual(answer[b"complete"], 1)
36 self.assertEqual(answer[b"incomplete"], 0)
37
38 def test_a_peer_that_finishes_moves_from_one_column_to_the_other(self):
39 info_hash = self.hash_for("finishes")
40 peer_id = b"-OT0001-finisher0001"
41 self.tracker.announce(info_hash, peer_id=peer_id, left=100)
42 self.assertEqual(self.tracker.announce(info_hash, peer_id=peer_id, left=100)[b"incomplete"], 1)
43
44 answer = self.tracker.announce(info_hash, peer_id=peer_id, left=0, event="completed")
45 self.assertEqual(answer[b"complete"], 1, "the peer should now count as a seeder")
46 self.assertEqual(answer[b"incomplete"], 0, "and no longer as a leecher")
47
48 def test_stopped_removes_the_peer(self):
49 info_hash = self.hash_for("stopped")
50 peer_id = b"-OT0001-leaves000001"
51 self.tracker.announce(info_hash, peer_id=peer_id, left=100)
52 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"incomplete"], 1)
53
54 self.tracker.announce(info_hash, peer_id=peer_id, left=100, event="stopped")
55 self.assertEqual(
56 self.tracker.scrape(info_hash)[b"files"][info_hash][b"incomplete"], 0, "the peer should be gone"
57 )
58
59 def test_the_same_peer_announcing_twice_is_still_one_peer(self):
60 info_hash = self.hash_for("twice")
61 peer_id = b"-OT0001-repeats00001"
62 for _ in range(5):
63 answer = self.tracker.announce(info_hash, peer_id=peer_id, left=100)
64 self.assertEqual(answer[b"incomplete"], 1, "re-announcing must not duplicate the peer")
65
66 def test_a_peer_is_its_address_and_port_not_its_peer_id(self):
67 """Two announces from one address and port are one peer, whatever
68 peer_id they carry, because that pair is the whole identity."""
69 info_hash = self.hash_for("identity")
70 self.tracker.announce(info_hash, peer_id=b"-OT0001-a00000000001", port=6881, left=100)
71 answer = self.tracker.announce(info_hash, peer_id=b"-OT0001-b00000000002", port=6881, left=100)
72 self.assertEqual(answer[b"incomplete"], 1, "same address and port, so still one peer")
73
74 answer = self.tracker.announce(info_hash, peer_id=b"-OT0001-c00000000003", port=6882, left=100)
75 self.assertEqual(answer[b"incomplete"], 2, "a different port makes a different peer")
76
77 def test_a_downloaded_count_is_kept(self):
78 info_hash = self.hash_for("downloaded")
79 self.tracker.announce(info_hash, port=7001, left=0, event="completed")
80 self.tracker.announce(info_hash, port=7002, left=0, event="completed")
81 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"downloaded"], 2)
82
83 def test_a_peer_is_given_the_whole_swarm_including_itself(self):
84 """opentracker does not filter the announcing peer out of its own
85 answer. Clients are expected to ignore themselves."""
86 info_hash = self.hash_for("sees")
87 self.tracker.announce(info_hash, port=1111, left=100)
88 answer = self.tracker.announce(info_hash, port=2222, left=100)
89
90 ports = sorted(port for _, port in peers_of(answer))
91 self.assertEqual(ports, [1111, 2222])
92
93 def test_an_unknown_torrent_scrapes_as_empty(self):
94 answer = self.tracker.scrape(self.hash_for("never-seen"))
95 self.assertEqual(answer[b"files"], {}, "a torrent nobody announced has no entry")
96
97
98class AnnounceRejects(TrackerTestCase):
99 def assert_rejected(self, query):
100 import urllib.error
101
102 with self.assertRaises(urllib.error.HTTPError) as caught:
103 self.tracker.get("/announce?" + query)
104 self.assertEqual(caught.exception.code, 400)
105
106 def test_a_short_info_hash_is_rejected(self):
107 self.assert_rejected("info_hash=tooshort&peer_id=" + "b" * 20 + "&port=6881&left=0")
108
109 def test_a_missing_info_hash_answers_a_failure_reason(self):
110 """Not a 400: the tracker answers 200 with a bencoded complaint, so
111 that a client shows the message to its user."""
112 from harness import bdecode
113
114 answer = bdecode(self.tracker.get("/announce?peer_id=" + "b" * 20 + "&port=6881&left=0"))
115 self.assertIn(b"failure reason", answer)
116 self.assertIn(b"info_hash", answer[b"failure reason"])
117
118 def test_a_port_out_of_range_is_rejected(self):
119 self.assert_rejected("info_hash=" + "a" * 20 + "&peer_id=" + "b" * 20 + "&port=65536&left=0")
120
121 def test_a_port_that_is_not_a_number_is_rejected(self):
122 self.assert_rejected("info_hash=" + "a" * 20 + "&peer_id=" + "b" * 20 + "&port=abc&left=0")
123
124 def test_two_info_hashes_are_rejected(self):
125 self.assert_rejected("info_hash=" + "a" * 20 + "&info_hash=" + "c" * 20 + "&peer_id=" + "b" * 20 + "&port=6881&left=0")
126
127
128if __name__ == "__main__":
129 unittest.main()