def fixture_datafeed_loop_v1(datafeed_api, session_service, auth_session, config, datafeed_repository): df_loop = auto_stopping_datafeed_loop_v1(datafeed_api, session_service, auth_session, config, datafeed_repository) df_loop._retry_config = minimal_retry_config_with_attempts(1) return df_loop
async def test_unexpected_api_exception_is_raised(self): self._retry_config = minimal_retry_config_with_attempts(1) thing = NoApiExceptionAfterCount(2, status=404) with pytest.raises(ApiException): await self._retryable_coroutine(thing) assert thing.call_count == 1
async def test_unauthorized_error_refreshes_session_and_and_tries_again(self): self._retry_config = minimal_retry_config_with_attempts(2) self._auth_session = Mock() self._auth_session.refresh = AsyncMock() thing = NoApiExceptionAfterCount(1, status=401) value = await self._retryable_coroutine(thing) self._auth_session.refresh.assert_called_once() assert value is True
async def test_client_error_recreates_datafeed_and_and_tries_again(self): self._retry_config = minimal_retry_config_with_attempts(2) self._auth_session = Mock() self.recreate_datafeed = AsyncMock() thing = NoApiExceptionAfterCount(1, status=400) value = await self._retryable_coroutine(thing) self.recreate_datafeed.assert_called_once() assert value is True
async def test_400_should_call_recreate_df_and_retry(datafeed_loop, datafeed_api): datafeed_loop._retry_config = minimal_retry_config_with_attempts(2) datafeed_loop.recreate_datafeed = AsyncMock() datafeed_api.read_datafeed.side_effect = [ApiException(status=400), ApiException(status=500)] with pytest.raises(ApiException) as exception: await datafeed_loop.start() assert exception.value.status == 500 datafeed_loop.recreate_datafeed.assert_called_once() assert datafeed_api.read_datafeed.call_count == 2
async def test_start_datafeed_stale_datafeed(datafeed_loop, datafeed_api, message_sent_event): datafeed_loop._retry_config = minimal_retry_config_with_attempts(2) datafeed_api.list_datafeed.return_value = [V5Datafeed(id="fault_datafeed_id")] datafeed_api.create_datafeed.return_value = V5Datafeed(id="test_id") # This is done this way because side_effect with a list containing coroutines is not behaving as expected async def raise_and_return_event(**kwargs): if raise_and_return_event.first: raise_and_return_event.first = False raise ApiException(400) await asyncio.sleep(SLEEP_SECONDS) # to force the switching of tasks return message_sent_event raise_and_return_event.first = True datafeed_api.read_datafeed.side_effect = raise_and_return_event await datafeed_loop.start() datafeed_api.list_datafeed.assert_called_with( session_token="session_token", key_manager_token="km_token", tag=BOT_USER ) datafeed_api.delete_datafeed.assert_called_with( session_token="session_token", key_manager_token="km_token", datafeed_id="fault_datafeed_id" ) datafeed_api.create_datafeed.assert_called_with( session_token="session_token", key_manager_token="km_token", body=V5DatafeedCreateBody(tag=BOT_USER) ) datafeed_api.read_datafeed.assert_has_calls([ call( session_token="session_token", key_manager_token="km_token", datafeed_id="fault_datafeed_id", ack_id=AckId(ack_id="") ), call( session_token="session_token", key_manager_token="km_token", datafeed_id="test_id", ack_id=AckId(ack_id="") ) ]) assert datafeed_loop._datafeed_id == "test_id" assert datafeed_loop._ack_id == "ack_id"
async def test_should_retry(): strategies = [TestAuthenticationStrategy(), TestRefreshSessionStrategy(), TestReadDatafeedStrategy()] connection_key = Mock() connection_key.ssl = "ssl" exception_from_a_timeout = ClientConnectorError(connection_key, TimeoutError()) exception_from_a_timeout.__cause__ = TimeoutError() thing = FixedChainedExceptions([ApiException(429), ApiException(500), exception_from_a_timeout]) for s in strategies: s._retry_config = minimal_retry_config_with_attempts(4) value = await s._retryable_coroutine(thing) assert value is True assert thing.call_count == 4 thing.reset() # Reset the counters
class TestRefreshSessionStrategy: """Testing refresh_session_if_unauthorized strategy""" _retry_config = minimal_retry_config_with_attempts(2) @retry(retry=strategy.refresh_session_if_unauthorized) async def _retryable_coroutine(self, thing): await asyncio.sleep(0.00001) return thing.go() @pytest.mark.asyncio async def test_unauthorized_error_refreshes_session_and_tries_again(self): self._auth_session = Mock() self._auth_session.refresh = AsyncMock() thing = NoApiExceptionAfterCount(1, status=401) value = await self._retryable_coroutine(thing) self._auth_session.refresh.assert_called_once() assert value is True
async def test_400_should_call_recreate_df_return_and_retry(datafeed_loop, datafeed_api, message_sent_event): async def read_df(**kwargs): if read_df.first_time: read_df.first_time = False raise ApiException(status=400, reason="") await asyncio.sleep(0.00001) # to force the switching of tasks return message_sent_event read_df.first_time = True datafeed_api.read_datafeed.side_effect = read_df datafeed_loop._retry_config = minimal_retry_config_with_attempts(2) datafeed_loop.prepare_datafeed = AsyncMock() datafeed_loop.recreate_datafeed = AsyncMock() await datafeed_loop.start() datafeed_loop.recreate_datafeed.assert_called_once() assert datafeed_api.read_datafeed.call_count >= 2
class TestAuthenticationStrategy: """Testing authentication_retry strategy""" _retry_config = minimal_retry_config_with_attempts(1) @retry(retry=strategy.authentication_retry) async def _retryable_coroutine(self, thing): await asyncio.sleep(0.00001) return thing.go() @pytest.mark.asyncio async def test_unauthorized_is_raised(self): thing = NoApiExceptionAfterCount(2, status=401) with pytest.raises(AuthUnauthorizedError): await self._retryable_coroutine(thing) assert thing.call_count == 1 @pytest.mark.asyncio async def test_unexpected_api_exception_is_raised(self): thing = NoApiExceptionAfterCount(2, status=404) with pytest.raises(ApiException): await self._retryable_coroutine(thing) assert thing.call_count == 1
class TestDecoratorWrapper: """Testing the default decorator configuration taken from the class attribute _retry_config""" _retry_config = minimal_retry_config_with_attempts(10) @retry async def _retryable_coroutine(self, thing): await asyncio.sleep(0.00001) return thing.go() @retry(retry=tenacity.retry_if_result(lambda x: False)) async def _non_retryable_coroutine(self, thing): return thing.go() @retry(stop=tenacity.stop_after_attempt(2)) async def _retryable_coroutine_with_2_attempts(self, thing): await asyncio.sleep(0.00001) thing.go() @pytest.mark.asyncio async def test_max_attempts_reached_should_fail(self): thing = NoApiExceptionAfterCount(11, status=500) with pytest.raises(ApiException): await self._retryable_coroutine(thing) assert thing.counter == 10 assert thing.call_count == 10 @pytest.mark.asyncio async def test_exception_not_matching_default_predicate_should_fail(self): thing = NoApiExceptionAfterCount(1, status=400) with pytest.raises(ApiException): await self._retryable_coroutine(thing) assert thing.counter == 1 assert thing.call_count == 1