1+ from ipaddress import IPv4Address
12from typing import NamedTuple
3+ from unittest .mock import MagicMock , patch
24
35import pytest
46from asgiref .sync import sync_to_async
911
1012class MockedAxonInfo (NamedTuple ):
1113 is_serving : bool
12- ip : str = ""
13- port : int = 0
14+ ip : IPv4Address = IPv4Address ( "0.0.0.0" )
15+ port : int = 8000
1416
1517
1618class MockedNeuron (NamedTuple ):
17- uid : int
1819 hotkey : str
1920 axon_info : MockedAxonInfo
20- stake : float
2121
2222
23- class MockedMetagraph :
24- def __init__ (self , neurons ):
25- self .neurons = neurons
26- self .total_stake = [n .stake for n in neurons ]
27-
28-
29- class MockedSubtensor :
30- def __init__ (self , metagraph : MockedMetagraph ):
31- self ._metagraph = metagraph
32-
33- def __call__ (self , * args , ** kwargs ):
34- return self
35-
36- def __enter__ (self ):
37- return self
38-
39- def __exit__ (self , * args , ** kwargs ):
40- pass
23+ class MockedValidator (NamedTuple ):
24+ hotkey : str
4125
42- def metagraph (self , * args , ** kwargs ):
43- return self ._metagraph
4426
27+ def configure_mock_pylon_client (
28+ mock_client : MagicMock ,
29+ validators : list [str ],
30+ neurons : dict [str , MockedNeuron ],
31+ ) -> None :
32+ validators_response = MagicMock ()
33+ validators_response .validators = [MockedValidator (hotkey = v ) for v in validators ]
34+ validators_response .block .number = 12345
4535
46- validator_params = dict (
47- axon_info = MockedAxonInfo (is_serving = False ),
48- stake = 1000.0 ,
49- )
36+ neurons_response = MagicMock ()
37+ neurons_response .neurons = neurons
5038
51- miner_params = dict (
52- axon_info = MockedAxonInfo (is_serving = True ),
53- stake = 0.0 ,
54- )
39+ mock_client .open_access .get_latest_validators .return_value = validators_response
40+ mock_client .open_access .get_neurons .return_value = neurons_response
5541
5642
5743@pytest .mark .django_db (transaction = True )
58- def test__sync_metagraph__activation (monkeypatch ):
59- import bittensor
60-
61- validators = Validator .objects .bulk_create (
44+ def test__sync_metagraph__activation (mock_pylon_client ):
45+ Validator .objects .bulk_create (
6246 [
6347 Validator (ss58_address = "remains_active" , is_active = True ),
6448 Validator (ss58_address = "is_deactivated" , is_active = True ),
@@ -67,56 +51,55 @@ def test__sync_metagraph__activation(monkeypatch):
6751 ]
6852 )
6953
70- metagraph = MockedMetagraph (
71- neurons = [
72- MockedNeuron (uid = 0 , hotkey = "remains_active" , ** validator_params ),
73- MockedNeuron (uid = 1 , hotkey = "is_deactivated" , ** miner_params ),
74- MockedNeuron (uid = 2 , hotkey = "remains_inactive" , ** miner_params ),
75- MockedNeuron (uid = 3 , hotkey = "is_activated" , ** validator_params ),
76- MockedNeuron (uid = 4 , hotkey = "new_validator" , ** validator_params ),
77- MockedNeuron (uid = 5 , hotkey = "new_miner" , ** miner_params ),
78- ]
79- )
80- subtensor = MockedSubtensor ( metagraph = metagraph )
54+ validator_hotkeys = [ "remains_active" , "is_activated" , "new_validator" ]
55+ neurons = {
56+ "remains_active" : MockedNeuron (hotkey = "remains_active" , axon_info = MockedAxonInfo ( is_serving = False ) ),
57+ "is_deactivated" : MockedNeuron (hotkey = "is_deactivated" , axon_info = MockedAxonInfo ( is_serving = True ) ),
58+ "remains_inactive" : MockedNeuron (hotkey = "remains_inactive" , axon_info = MockedAxonInfo ( is_serving = True ) ),
59+ "is_activated" : MockedNeuron (hotkey = "is_activated" , axon_info = MockedAxonInfo ( is_serving = False ) ),
60+ "new_validator" : MockedNeuron (hotkey = "new_validator" , axon_info = MockedAxonInfo ( is_serving = False ) ),
61+ "new_miner" : MockedNeuron (hotkey = "new_miner" , axon_info = MockedAxonInfo ( is_serving = True ) ),
62+ }
63+
64+ configure_mock_pylon_client ( mock_pylon_client , validator_hotkeys , neurons )
8165
82- with monkeypatch .context () as mp :
83- mp .setattr (bittensor , "subtensor" , subtensor )
66+ with patch ("project.core.tasks.pylon_client" , return_value = mock_pylon_client ):
8467 sync_metagraph ()
8568
8669 validators = Validator .objects .order_by ("id" ).values_list ("ss58_address" , "is_active" )
8770 assert list (validators ) == [
88- tuple (d .values ())
89- for d in [
90- dict (ss58_address = "remains_active" , is_active = True ),
91- dict (ss58_address = "is_deactivated" , is_active = False ),
92- dict (ss58_address = "remains_inactive" , is_active = False ),
93- dict (ss58_address = "is_activated" , is_active = True ),
94- dict (ss58_address = "new_validator" , is_active = True ),
95- ]
71+ ("remains_active" , True ),
72+ ("is_deactivated" , False ),
73+ ("remains_inactive" , False ),
74+ ("is_activated" , True ),
75+ ("new_validator" , True ),
9676 ]
9777
9878
9979@pytest .mark .asyncio
10080@pytest .mark .django_db (transaction = True )
10181async def test__websocket__disconnect_validator_if_become_inactive (
102- monkeypatch ,
82+ mock_pylon_client ,
10383 communicator ,
10484 authenticated ,
10585 validator ,
10686 job ,
10787 dummy_job_params ,
10888):
109- """Check that validator is disconnected if it becomes inactive"""
110- import bittensor
111-
11289 await communicator .receive_json_from ()
11390 assert await Channel .objects .filter (validator = validator ).aexists ()
11491
115- metagraph = MockedMetagraph (neurons = [MockedNeuron (uid = 0 , hotkey = validator .ss58_address , ** miner_params )])
116- subtensor = MockedSubtensor (metagraph = metagraph )
92+ validator_hotkeys = []
93+ neurons = {
94+ validator .ss58_address : MockedNeuron (
95+ hotkey = validator .ss58_address ,
96+ axon_info = MockedAxonInfo (is_serving = True ),
97+ ),
98+ }
99+
100+ configure_mock_pylon_client (mock_pylon_client , validator_hotkeys , neurons )
117101
118- with monkeypatch .context () as mp :
119- mp .setattr (bittensor , "subtensor" , subtensor )
102+ with patch ("project.core.tasks.pylon_client" , return_value = mock_pylon_client ):
120103 await sync_to_async (sync_metagraph )()
121104
122105 assert (await communicator .receive_output ())["type" ] == "websocket.close"
0 commit comments