@@ -993,350 +993,3 @@ def test_encode_array(self):
993993
994994 assert ("foo[0][dob][month]" , 1 ) in values
995995 assert ("foo[0][name]" , "bat" ) in values
996-
997-
998- class ClientTestBaseAsync (object ):
999- REQUEST_CLIENT : Type [_http_client .HTTPClientAsync ]
1000-
1001- @pytest .fixture
1002- def request_mock (self , request_mocks ):
1003- return request_mocks [self .REQUEST_CLIENT .name ]
1004-
1005- @property
1006- def valid_url (self , path = "/foo" ):
1007- return "https://api.stripe.com%s" % (path ,)
1008-
1009- def make_request_async (self , method , url , headers , post_data ):
1010- client = self .REQUEST_CLIENT (verify_ssl_certs = True )
1011- return client .request_with_retries_async (
1012- method , url , headers , post_data
1013- )
1014-
1015- def make_request_stream (self , method , url , headers , post_data ):
1016- client = self .REQUEST_CLIENT (verify_ssl_certs = True )
1017- return client .request_stream_with_retries_async (
1018- method , url , headers , post_data
1019- )
1020-
1021- @pytest .fixture
1022- def mock_response (self ):
1023- def mock_response (mock , body , code ):
1024- raise NotImplementedError (
1025- "You must implement this in your test subclass"
1026- )
1027-
1028- return mock_response
1029-
1030- @pytest .fixture
1031- def mock_error (self ):
1032- def mock_error (mock , error ):
1033- raise NotImplementedError (
1034- "You must implement this in your test subclass"
1035- )
1036-
1037- return mock_error
1038-
1039- @pytest .fixture
1040- def check_call (self ):
1041- def check_call (
1042- mock , method , abs_url , headers , params , is_streaming = False
1043- ):
1044- raise NotImplementedError (
1045- "You must implement this in your test subclass"
1046- )
1047-
1048- return check_call
1049-
1050-
1051- class HTTPXVerify (object ):
1052- def __eq__ (self , other ):
1053- return other and other .endswith ("stripe/data/ca-certificates.crt" )
1054-
1055-
1056- class TestHTTPXClient (StripeClientTestCase , ClientTestBaseAsync ):
1057- REQUEST_CLIENT : Type [_http_client .HTTPXClient ] = _http_client .HTTPXClient
1058-
1059- @pytest .fixture
1060- def mock_response (self , mocker , request_mock ):
1061- def mock_response (body = {}, code = 200 ):
1062- result = mocker .Mock ()
1063- result .content = body
1064- result .status_code = code
1065- result .headers = {}
1066-
1067- async def do (* args , ** kwargs ):
1068- return result
1069-
1070- async_mock = mocker .AsyncMock (side_effect = do )
1071-
1072- request_mock .AsyncClient ().request = async_mock
1073- return result
1074-
1075- return mock_response
1076-
1077- @pytest .fixture
1078- def mock_error (self , mocker , request_mock ):
1079- def mock_error (mock ):
1080- # The first kind of request exceptions we catch
1081- mock .exceptions .SSLError = Exception
1082- request_mock .AsyncClient ().request .side_effect = (
1083- mock .exceptions .SSLError ()
1084- )
1085-
1086- return mock_error
1087-
1088- @pytest .fixture
1089- def check_call (self , request_mock , mocker ):
1090- def check_call (
1091- mock ,
1092- method ,
1093- url ,
1094- post_data ,
1095- headers ,
1096- is_streaming = False ,
1097- timeout = 80 ,
1098- times = None ,
1099- ):
1100- times = times or 1
1101- args = (method , url )
1102- kwargs = {
1103- "headers" : headers ,
1104- "data" : post_data ,
1105- "verify" : HTTPXVerify (),
1106- "timeout" : timeout ,
1107- "proxies" : {"http" : "http://slap/" , "https" : "http://slap/" },
1108- }
1109-
1110- if is_streaming :
1111- kwargs ["stream" ] = True
1112-
1113- calls = [mocker .call (* args , ** kwargs ) for _ in range (times )]
1114- request_mock .AsyncClient ().request .assert_has_calls (calls )
1115-
1116- return check_call
1117-
1118- async def make_request_async (
1119- self , method , url , headers , post_data , timeout = 80
1120- ):
1121- client = self .REQUEST_CLIENT (
1122- verify_ssl_certs = True , proxy = "http://slap/" , timeout = timeout
1123- )
1124- return await client .request_with_retries_async (
1125- method , url , headers , post_data
1126- )
1127-
1128- async def make_request_stream_async (
1129- self , method , url , headers , post_data , timeout = 80
1130- ):
1131- client = self .REQUEST_CLIENT (
1132- verify_ssl_certs = True , proxy = "http://slap/"
1133- )
1134- return await client .request_stream_with_retries_async (
1135- method , url , headers , post_data
1136- )
1137-
1138- @pytest .mark .asyncio
1139- async def test_request (self , request_mock , mock_response , check_call ):
1140-
1141- mock_response ('{"foo": "baz"}' , 200 )
1142-
1143- for method in VALID_API_METHODS :
1144- abs_url = self .valid_url
1145- data = {}
1146-
1147- if method != "post" :
1148- abs_url = "%s?%s" % (abs_url , data )
1149- data = {}
1150-
1151- headers = {"my-header" : "header val" }
1152- body , code , _ = await self .make_request_async (
1153- method , abs_url , headers , data
1154- )
1155- assert code == 200
1156- assert body == '{"foo": "baz"}'
1157-
1158- check_call (request_mock , method , abs_url , data , headers )
1159-
1160- @pytest .mark .asyncio
1161- async def test_request_stream (
1162- self , mocker , request_mock , mock_response , check_call
1163- ):
1164- # TODO
1165- pass
1166-
1167- async def test_exception (self , request_mock , mock_error ):
1168- mock_error (request_mock )
1169- with pytest .raises (APIConnectionError ):
1170- await self .make_request_async ("get" , self .valid_url , {}, None )
1171-
1172- @pytest .mark .asyncio
1173- async def test_timeout (self , request_mock , mock_response , check_call ):
1174- headers = {"my-header" : "header val" }
1175- data = {}
1176- mock_response ('{"foo": "baz"}' , 200 )
1177- await self .make_request_async (
1178- "POST" , self .valid_url , headers , data , timeout = 5
1179- )
1180-
1181- check_call (None , "POST" , self .valid_url , data , headers , timeout = 5 )
1182-
1183- @pytest .mark .asyncio
1184- async def test_request_stream_forwards_stream_param (
1185- self , mocker , request_mock , mock_response , check_call
1186- ):
1187- # TODO
1188- pass
1189-
1190-
1191- class TestHTTPXClientRetryBehavior (TestHTTPXClient ):
1192- responses = None
1193-
1194- @pytest .fixture
1195- def mock_retry (self , mocker , request_mock ):
1196- def mock_retry (
1197- retry_error_num = 0 , no_retry_error_num = 0 , responses = None
1198- ):
1199- if responses is None :
1200- responses = []
1201- # Mocking classes of exception we catch. Any group of exceptions
1202- # with the same inheritance pattern will work
1203- request_root_error_class = Exception
1204- request_mock .exceptions .RequestException = request_root_error_class
1205-
1206- no_retry_parent_class = LookupError
1207- no_retry_child_class = KeyError
1208- request_mock .exceptions .SSLError = no_retry_parent_class
1209- no_retry_errors = [no_retry_child_class ()] * no_retry_error_num
1210-
1211- retry_parent_class = EnvironmentError
1212- retry_child_class = IOError
1213- request_mock .exceptions .Timeout = retry_parent_class
1214- request_mock .exceptions .ConnectionError = retry_parent_class
1215- retry_errors = [retry_child_class ()] * retry_error_num
1216- # Include mock responses as possible side-effects
1217- # to simulate returning proper results after some exceptions
1218-
1219- results = retry_errors + no_retry_errors + responses
1220-
1221- request_mock .AsyncClient ().request = mocker .AsyncMock (
1222- side_effect = results
1223- )
1224- self .responses = results
1225-
1226- return request_mock
1227-
1228- return mock_retry
1229-
1230- @pytest .fixture
1231- def check_call_numbers (self , check_call ):
1232- valid_url = self .valid_url
1233-
1234- def check_call_numbers (times , is_streaming = False ):
1235- check_call (
1236- None ,
1237- "GET" ,
1238- valid_url ,
1239- {},
1240- {},
1241- times = times ,
1242- is_streaming = is_streaming ,
1243- )
1244-
1245- return check_call_numbers
1246-
1247- def max_retries (self ):
1248- return 3
1249-
1250- def make_client (self ):
1251- client = self .REQUEST_CLIENT (
1252- verify_ssl_certs = True , timeout = 80 , proxy = "http://slap/"
1253- )
1254- # Override sleep time to speed up tests
1255- client ._sleep_time_seconds = lambda num_retries , response = None : 0.0001
1256- # Override configured max retries
1257- client ._max_network_retries = lambda : self .max_retries ()
1258- return client
1259-
1260- async def make_request (self , * args , ** kwargs ):
1261- client = self .make_client ()
1262- return await client .request_with_retries_async (
1263- "GET" , self .valid_url , {}, None
1264- )
1265-
1266- async def make_request_stream (self , * args , ** kwargs ):
1267- client = self .make_client ()
1268- return await client .request_stream_with_retries_async (
1269- "GET" , self .valid_url , {}, None
1270- )
1271-
1272- @pytest .mark .asyncio
1273- async def test_retry_error_until_response_yo (
1274- self , mock_retry , mock_response , check_call_numbers , mocker
1275- ):
1276- mock_retry (retry_error_num = 1 , responses = [mock_response (code = 202 )])
1277- _ , code , _ = await self .make_request ()
1278- assert code == 202
1279- check_call_numbers (2 )
1280-
1281- @pytest .mark .asyncio
1282- async def test_retry_error_until_exceeded (
1283- self , mock_retry , mock_response , check_call_numbers
1284- ):
1285- mock_retry (retry_error_num = self .max_retries ())
1286- with pytest .raises (APIConnectionError ):
1287- await self .make_request ()
1288-
1289- check_call_numbers (self .max_retries ())
1290-
1291- @pytest .mark .asyncio
1292- async def test_no_retry_error (
1293- self , mock_retry , mock_response , check_call_numbers
1294- ):
1295- mock_retry (no_retry_error_num = self .max_retries ())
1296- with pytest .raises (APIConnectionError ):
1297- await self .make_request ()
1298- check_call_numbers (1 )
1299-
1300- @pytest .mark .asyncio
1301- async def test_retry_codes (
1302- self , mock_retry , mock_response , check_call_numbers
1303- ):
1304- mock_retry (
1305- responses = [mock_response (code = 409 ), mock_response (code = 202 )]
1306- )
1307- _ , code , _ = await self .make_request ()
1308- assert code == 202
1309- check_call_numbers (2 )
1310-
1311- @pytest .mark .asyncio
1312- async def test_retry_codes_until_exceeded (
1313- self , mock_retry , mock_response , check_call_numbers
1314- ):
1315- mock_retry (
1316- responses = [mock_response (code = 409 )] * (self .max_retries () + 1 )
1317- )
1318- _ , code , _ = await self .make_request ()
1319- assert code == 409
1320- check_call_numbers (self .max_retries () + 1 )
1321-
1322- @pytest .fixture
1323- def connection_error (self ):
1324- client = self .REQUEST_CLIENT ()
1325-
1326- def connection_error (given_exception ):
1327- with pytest .raises (APIConnectionError ) as error :
1328- client ._handle_request_error (given_exception )
1329- return error .value
1330-
1331- return connection_error
1332-
1333- def test_handle_request_error_should_retry (
1334- self , connection_error , mock_retry
1335- ):
1336- request_mock = mock_retry ()
1337-
1338- error = connection_error (request_mock .exceptions .Timeout ())
1339- assert error .should_retry
1340-
1341- error = connection_error (request_mock .exceptions .ConnectionError ())
1342- assert error .should_retry
0 commit comments