A Python port of the Invisible Internet Project (I2P)
1"""Tests for peer profile organizer."""
2
3import os
4
5import pytest
6
7
8def _make_profile(capacity: float = 0.0, speed: float = 0.0,
9 integration: float = 0.0, is_banned: bool = False,
10 tunnel_builds: int = 0, sends: int = 0):
11 """Helper to create a profile with preset scores."""
12 from i2p_peer.profile import PeerProfile
13
14 p = PeerProfile(os.urandom(32))
15 p.capacity = capacity
16 p.speed = speed
17 p.integration = integration
18 p.is_banned = is_banned
19 # Fake some history for is_established checks
20 p.tunnel_builds_succeeded = tunnel_builds
21 p.send_success_count = sends
22 return p
23
24
25class TestProfileOrganizer:
26 def test_classify_fast(self):
27 from i2p_peer.organizer import PeerTier, ProfileOrganizer
28
29 org = ProfileOrganizer()
30 p = _make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10)
31 org.add_profile(p)
32 assert org.get_tier(p.router_hash) == PeerTier.FAST
33
34 def test_classify_high_capacity(self):
35 from i2p_peer.organizer import PeerTier, ProfileOrganizer
36
37 org = ProfileOrganizer()
38 p = _make_profile(capacity=0.7, speed=0.3, tunnel_builds=15, sends=10)
39 org.add_profile(p)
40 assert org.get_tier(p.router_hash) == PeerTier.HIGH_CAPACITY
41
42 def test_classify_standard(self):
43 from i2p_peer.organizer import PeerTier, ProfileOrganizer
44
45 org = ProfileOrganizer()
46 p = _make_profile(capacity=0.4, speed=0.3, tunnel_builds=5, sends=5)
47 org.add_profile(p)
48 assert org.get_tier(p.router_hash) == PeerTier.STANDARD
49
50 def test_classify_failing(self):
51 from i2p_peer.organizer import PeerTier, ProfileOrganizer
52
53 org = ProfileOrganizer()
54 p = _make_profile(capacity=0.1, speed=0.1)
55 org.add_profile(p)
56 assert org.get_tier(p.router_hash) == PeerTier.FAILING
57
58 def test_classify_banned(self):
59 from i2p_peer.organizer import PeerTier, ProfileOrganizer
60
61 org = ProfileOrganizer()
62 p = _make_profile(capacity=0.9, speed=0.9, is_banned=True)
63 p.ban(3600.0) # ban for an hour
64 org.add_profile(p)
65 assert org.get_tier(p.router_hash) == PeerTier.BANNED
66
67 def test_select_fast_peers(self):
68 from i2p_peer.organizer import PeerTier, ProfileOrganizer
69
70 org = ProfileOrganizer()
71 fast_hashes = set()
72 for _ in range(3):
73 p = _make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10)
74 org.add_profile(p)
75 fast_hashes.add(p.router_hash)
76 # Add some non-fast peers
77 for _ in range(3):
78 p = _make_profile(capacity=0.2, speed=0.1)
79 org.add_profile(p)
80
81 result = org.select_fast_peers(count=5)
82 assert len(result) == 3
83 for h in result:
84 assert h in fast_hashes
85
86 def test_select_with_exclude(self):
87 from i2p_peer.organizer import ProfileOrganizer
88
89 org = ProfileOrganizer()
90 profiles = []
91 for _ in range(5):
92 p = _make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10)
93 org.add_profile(p)
94 profiles.append(p)
95
96 exclude = {profiles[0].router_hash, profiles[1].router_hash}
97 result = org.select_fast_peers(count=10, exclude=exclude)
98 assert len(result) == 3
99 for h in result:
100 assert h not in exclude
101
102 def test_select_peers_min_tier(self):
103 from i2p_peer.organizer import PeerTier, ProfileOrganizer
104
105 org = ProfileOrganizer()
106 # Add peers at different tiers
107 fast = _make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10)
108 org.add_profile(fast)
109 hicap = _make_profile(capacity=0.7, speed=0.3, tunnel_builds=15, sends=10)
110 org.add_profile(hicap)
111 standard = _make_profile(capacity=0.4, speed=0.3, tunnel_builds=5, sends=5)
112 org.add_profile(standard)
113 failing = _make_profile(capacity=0.1, speed=0.1)
114 org.add_profile(failing)
115
116 # Select with min_tier=HIGH_CAPACITY should get fast + hicap
117 result = org.select_peers(count=10, min_tier=PeerTier.HIGH_CAPACITY)
118 assert len(result) == 2
119 assert fast.router_hash in result
120 assert hicap.router_hash in result
121
122 def test_select_peers_min_tier_standard(self):
123 from i2p_peer.organizer import PeerTier, ProfileOrganizer
124
125 org = ProfileOrganizer()
126 fast = _make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10)
127 org.add_profile(fast)
128 standard = _make_profile(capacity=0.4, speed=0.3, tunnel_builds=5, sends=5)
129 org.add_profile(standard)
130 failing = _make_profile(capacity=0.1, speed=0.1)
131 org.add_profile(failing)
132
133 result = org.select_peers(count=10, min_tier=PeerTier.STANDARD)
134 assert len(result) == 2
135 assert failing.router_hash not in result
136
137 def test_reclassify_on_update(self):
138 from i2p_peer.organizer import PeerTier, ProfileOrganizer
139
140 org = ProfileOrganizer()
141 p = _make_profile(capacity=0.1, speed=0.1)
142 org.add_profile(p)
143 assert org.get_tier(p.router_hash) == PeerTier.FAILING
144
145 # Update the profile scores
146 p.capacity = 0.9
147 p.speed = 0.8
148 p.tunnel_builds_succeeded = 15
149 p.send_success_count = 10
150 org.add_profile(p) # Re-add triggers reclassification
151 assert org.get_tier(p.router_hash) == PeerTier.FAST
152
153 def test_add_remove_profile(self):
154 from i2p_peer.organizer import ProfileOrganizer
155
156 org = ProfileOrganizer()
157 p = _make_profile(capacity=0.5, speed=0.5, tunnel_builds=5, sends=5)
158 org.add_profile(p)
159 assert org.total_count == 1
160 assert org.get_profile(p.router_hash) is not None
161
162 org.remove_profile(p.router_hash)
163 assert org.total_count == 0
164 assert org.get_profile(p.router_hash) is None
165
166 def test_tier_counts(self):
167 from i2p_peer.organizer import ProfileOrganizer
168
169 org = ProfileOrganizer()
170 org.add_profile(_make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10))
171 org.add_profile(_make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10))
172 org.add_profile(_make_profile(capacity=0.7, speed=0.3, tunnel_builds=15, sends=10))
173 org.add_profile(_make_profile(capacity=0.4, speed=0.3, tunnel_builds=5, sends=5))
174 org.add_profile(_make_profile(capacity=0.1, speed=0.1))
175
176 assert org.fast_count == 2
177 assert org.high_capacity_count == 1
178 assert org.standard_count == 1
179 assert org.failing_count == 1
180 assert org.total_count == 5
181
182 def test_reclassify_all(self):
183 from i2p_peer.organizer import PeerTier, ProfileOrganizer
184
185 org = ProfileOrganizer()
186 p = _make_profile(capacity=0.1, speed=0.1)
187 org.add_profile(p)
188 assert org.get_tier(p.router_hash) == PeerTier.FAILING
189
190 # Mutate directly without re-adding
191 p.capacity = 0.9
192 p.speed = 0.8
193 p.tunnel_builds_succeeded = 15
194 p.send_success_count = 10
195 org.reclassify_all()
196 assert org.get_tier(p.router_hash) == PeerTier.FAST
197
198 def test_select_high_capacity_peers(self):
199 from i2p_peer.organizer import ProfileOrganizer
200
201 org = ProfileOrganizer()
202 hicap_hashes = set()
203 for _ in range(3):
204 p = _make_profile(capacity=0.7, speed=0.3, tunnel_builds=15, sends=10)
205 org.add_profile(p)
206 hicap_hashes.add(p.router_hash)
207 # Add a fast peer (should not be returned by select_high_capacity)
208 fast = _make_profile(capacity=0.9, speed=0.8, tunnel_builds=15, sends=10)
209 org.add_profile(fast)
210
211 result = org.select_high_capacity_peers(count=10)
212 assert len(result) == 3
213 for h in result:
214 assert h in hicap_hashes
215
216 def test_remove_nonexistent(self):
217 from i2p_peer.organizer import ProfileOrganizer
218
219 org = ProfileOrganizer()
220 # Should not raise
221 org.remove_profile(os.urandom(32))
222
223 def test_get_tier_unknown(self):
224 from i2p_peer.organizer import PeerTier, ProfileOrganizer
225
226 org = ProfileOrganizer()
227 # Unknown peer defaults to FAILING
228 assert org.get_tier(os.urandom(32)) == PeerTier.FAILING