@@ -684,3 +684,73 @@ async def test_async_sleeps_retry_interval_between_retries(
684684 assert sleep_call == mocker .call (
685685 fake_async_api_call .config .retry_interval_seconds ,
686686 )
687+
688+
689+ def test_round_robin_visits_each_node_in_turn (fake_api_call : ApiCall ) -> None :
690+ """Test that successful requests advance the round-robin by one node each."""
691+ fake_api_call .config .nearest_node = None
692+
693+ with respx .mock :
694+ for host in ("node0" , "node1" , "node2" ):
695+ respx .get (f"http://{ host } :8108/" ).mock (
696+ return_value = httpx .Response (200 , json = {"key" : "value" }),
697+ )
698+
699+ for _ in range (6 ):
700+ fake_api_call .get ("/" , entity_type = typing .Dict [str , str ])
701+
702+ assert [str (call .request .url ) for call in respx .calls ] == [
703+ "http://node0:8108/" ,
704+ "http://node1:8108/" ,
705+ "http://node2:8108/" ,
706+ "http://node0:8108/" ,
707+ "http://node1:8108/" ,
708+ "http://node2:8108/" ,
709+ ]
710+
711+
712+ async def test_async_round_robin_visits_each_node_in_turn (
713+ fake_async_api_call : AsyncApiCall ,
714+ ) -> None :
715+ """Test that successful requests advance the round-robin by one node each (async)."""
716+ fake_async_api_call .config .nearest_node = None
717+
718+ with respx .mock :
719+ for host in ("node0" , "node1" , "node2" ):
720+ respx .get (f"http://{ host } :8108/" ).mock (
721+ return_value = httpx .Response (200 , json = {"key" : "value" }),
722+ )
723+
724+ for _ in range (6 ):
725+ await fake_async_api_call .get ("/" , entity_type = typing .Dict [str , str ])
726+
727+ assert [str (call .request .url ) for call in respx .calls ] == [
728+ "http://node0:8108/" ,
729+ "http://node1:8108/" ,
730+ "http://node2:8108/" ,
731+ "http://node0:8108/" ,
732+ "http://node1:8108/" ,
733+ "http://node2:8108/" ,
734+ ]
735+
736+
737+ def test_success_marks_only_the_answering_node_healthy (
738+ fake_api_call : ApiCall ,
739+ ) -> None :
740+ """Test that a success refreshes the node that answered and no other."""
741+ fake_api_call .config .nearest_node = None
742+ answering_node , unhealthy_node , _ = fake_api_call .node_manager .nodes
743+ answering_node .last_access_ts = 0
744+ unhealthy_node .healthy = False
745+ unhealthy_node .last_access_ts = int (time .time ())
746+
747+ with respx .mock :
748+ respx .get ("http://node0:8108/" ).mock (
749+ return_value = httpx .Response (200 , json = {"key" : "value" }),
750+ )
751+
752+ fake_api_call .get ("/" , entity_type = typing .Dict [str , str ])
753+
754+ assert answering_node .healthy is True
755+ assert answering_node .last_access_ts > 0
756+ assert unhealthy_node .healthy is False
0 commit comments