3030from libp2p .kad_dht .peer_routing import (
3131 ALPHA ,
3232 MAX_PEER_LOOKUP_ROUNDS ,
33+ MIN_PEERS_THRESHOLD ,
3334 PROTOCOL_ID ,
3435 PeerRouting ,
3536)
@@ -145,18 +146,134 @@ async def test_find_peer_not_found(self, peer_routing, mock_host):
145146 assert result is None
146147
147148 @pytest .mark .trio
148- async def test_find_closest_peers_network_empty_start (self , peer_routing ):
149- """Test network search with no local peers."""
149+ async def test_find_closest_peers_network_empty_start (
150+ self , peer_routing , mock_host
151+ ):
152+ """Test network search with no local peers and no fallback peers."""
150153 target_key = b"target_key"
151154
152155 # Mock routing table to return empty list
153156 with patch .object (
154157 peer_routing .routing_table , "find_local_closest_peers" , return_value = []
155158 ):
159+ # Mock no connected peers and empty peerstore
160+ mock_host .get_connected_peers .return_value = []
161+ mock_host .get_peerstore ().peer_ids .return_value = []
156162 result = await peer_routing .find_closest_peers_network (target_key )
157163
158164 assert result == []
159165
166+ @pytest .mark .trio
167+ async def test_find_closest_peers_network_fallback_to_connected_peers (
168+ self , peer_routing , mock_host
169+ ):
170+ """
171+ Test that network search falls back to connected peers when routing
172+ table has insufficient peers.
173+ """
174+ target_key = b"target_key"
175+
176+ # Create few local peers (less than MIN_PEERS_THRESHOLD)
177+ local_peers = [create_valid_peer_id (f"local{ i } " ) for i in range (2 )]
178+
179+ # Create connected peers
180+ connected_peers = [create_valid_peer_id (f"connected{ i } " ) for i in range (3 )]
181+
182+ # Mock routing table to return insufficient peers
183+ with patch .object (
184+ peer_routing .routing_table ,
185+ "find_local_closest_peers" ,
186+ return_value = local_peers ,
187+ ):
188+ # Mock host to return connected peers
189+ mock_host .get_connected_peers .return_value = connected_peers
190+ # Mock peerstore to return empty (connected peers should be enough)
191+ mock_host .get_peerstore ().peer_ids .return_value = []
192+
193+ # Mock _query_peer_for_closest to return empty results
194+ with patch .object (peer_routing , "_query_peer_for_closest" , return_value = []):
195+ result = await peer_routing .find_closest_peers_network (
196+ target_key , count = 10
197+ )
198+
199+ # Should include both local and connected peers
200+ assert len (result ) > 0
201+ # Verify host.get_connected_peers was called
202+ mock_host .get_connected_peers .assert_called ()
203+
204+ @pytest .mark .trio
205+ async def test_find_closest_peers_network_fallback_to_peerstore (
206+ self , peer_routing , mock_host
207+ ):
208+ """
209+ Test that network search falls back to peerstore when connected peers
210+ are insufficient.
211+ """
212+ target_key = b"target_key"
213+
214+ # Create few local peers (less than MIN_PEERS_THRESHOLD)
215+ local_peers = [create_valid_peer_id (f"local{ i } " ) for i in range (2 )]
216+
217+ # Create few connected peers (not enough to reach count)
218+ connected_peers = [create_valid_peer_id (f"connected{ i } " ) for i in range (2 )]
219+
220+ # Create peerstore peers
221+ peerstore_peers = [create_valid_peer_id (f"peerstore{ i } " ) for i in range (5 )]
222+
223+ # Mock routing table to return insufficient peers
224+ with patch .object (
225+ peer_routing .routing_table ,
226+ "find_local_closest_peers" ,
227+ return_value = local_peers ,
228+ ):
229+ # Mock host to return connected peers
230+ mock_host .get_connected_peers .return_value = connected_peers
231+ # Mock peerstore to return additional peers
232+ mock_host .get_peerstore ().peer_ids .return_value = peerstore_peers
233+
234+ # Mock _query_peer_for_closest to return empty results
235+ with patch .object (peer_routing , "_query_peer_for_closest" , return_value = []):
236+ result = await peer_routing .find_closest_peers_network (
237+ target_key , count = 20
238+ )
239+
240+ # Should include peers from all sources
241+ assert len (result ) > 0
242+ # Verify peerstore.peer_ids was called
243+ mock_host .get_peerstore ().peer_ids .assert_called ()
244+
245+ @pytest .mark .trio
246+ async def test_find_closest_peers_network_no_fallback_when_sufficient_peers (
247+ self , peer_routing , mock_host
248+ ):
249+ """
250+ Test that fallback is not triggered when routing table has
251+ sufficient peers.
252+ """
253+ target_key = b"target_key"
254+
255+ # Create enough local peers (>= MIN_PEERS_THRESHOLD)
256+ local_peers = [
257+ create_valid_peer_id (f"local{ i } " ) for i in range (MIN_PEERS_THRESHOLD + 1 )
258+ ]
259+
260+ # Mock routing table to return sufficient peers
261+ with patch .object (
262+ peer_routing .routing_table ,
263+ "find_local_closest_peers" ,
264+ return_value = local_peers ,
265+ ):
266+ # Mock _query_peer_for_closest to return empty results
267+ with patch .object (peer_routing , "_query_peer_for_closest" , return_value = []):
268+ result = await peer_routing .find_closest_peers_network (
269+ target_key , count = 10
270+ )
271+
272+ # Should have peers from routing table
273+ assert len (result ) > 0
274+ # Verify host.get_connected_peers was NOT called
275+ mock_host .get_connected_peers .assert_not_called ()
276+
160277 @pytest .mark .trio
161278 async def test_find_closest_peers_network_with_peers (self , peer_routing , mock_host ):
162279 """Test network search with some initial peers."""
@@ -171,7 +288,10 @@ async def test_find_closest_peers_network_with_peers(self, peer_routing, mock_ho
171288 "find_local_closest_peers" ,
172289 return_value = initial_peers ,
173290 ):
174- # Mock _query_peer_for_closest to return empty results (no new peers found)
291+ # Mock get_connected_peers and peerstore to return empty
292+ mock_host .get_connected_peers .return_value = []
293+ mock_host .get_peerstore ().peer_ids .return_value = []
294+ # Mock _query_peer_for_closest to return empty results
175295 with patch .object (peer_routing , "_query_peer_for_closest" , return_value = []):
176296 result = await peer_routing .find_closest_peers_network (
177297 target_key , count = 5
@@ -182,7 +302,7 @@ async def test_find_closest_peers_network_with_peers(self, peer_routing, mock_ho
182302 assert all (peer in initial_peers for peer in result )
183303
184304 @pytest .mark .trio
185- async def test_find_closest_peers_convergence (self , peer_routing ):
305+ async def test_find_closest_peers_convergence (self , peer_routing , mock_host ):
186306 """Test that network search converges properly."""
187307 target_key = b"target_key"
188308
@@ -195,6 +315,9 @@ async def test_find_closest_peers_convergence(self, peer_routing):
195315 "find_local_closest_peers" ,
196316 return_value = initial_peers ,
197317 ):
318+ # Mock get_connected_peers and peerstore to return empty
319+ mock_host .get_connected_peers .return_value = []
320+ mock_host .get_peerstore ().peer_ids .return_value = []
198321 with patch .object (peer_routing , "_query_peer_for_closest" , return_value = []):
199322 with patch (
200323 "libp2p.kad_dht.peer_routing.sort_peer_ids_by_distance" ,
@@ -430,7 +553,7 @@ def test_constants(self):
430553 assert PROTOCOL_ID == "/ipfs/kad/1.0.0"
431554
432555 @pytest .mark .trio
433- async def test_edge_case_max_rounds_reached (self , peer_routing ):
556+ async def test_edge_case_max_rounds_reached (self , peer_routing , mock_host ):
434557 """Test that lookup stops after maximum rounds."""
435558 target_key = b"target_key"
436559 initial_peers = [create_valid_peer_id ("peer1" )]
@@ -444,6 +567,9 @@ def mock_query_side_effect(peer, key):
444567 "find_local_closest_peers" ,
445568 return_value = initial_peers ,
446569 ):
570+ # Mock get_connected_peers and peerstore to return empty
571+ mock_host .get_connected_peers .return_value = []
572+ mock_host .get_peerstore ().peer_ids .return_value = []
447573 with patch .object (
448574 peer_routing ,
449575 "_query_peer_for_closest" ,
0 commit comments