|
10 | 10 | from airbyte_cdk.utils import AirbyteTracedException
|
11 | 11 | from airbyte_protocol.models import Status
|
12 | 12 | from source_google_analytics_data_api import SourceGoogleAnalyticsDataApi
|
13 |
| -from source_google_analytics_data_api.source import MetadataDescriptor |
| 13 | +from source_google_analytics_data_api.api_quota import GoogleAnalyticsApiQuotaBase |
| 14 | +from source_google_analytics_data_api.source import GoogleAnalyticsDatApiErrorHandler, MetadataDescriptor |
14 | 15 | from source_google_analytics_data_api.utils import NO_DIMENSIONS, NO_METRICS, NO_NAME, WRONG_CUSTOM_REPORT_CONFIG, WRONG_JSON_SYNTAX
|
15 | 16 |
|
16 | 17 |
|
@@ -108,7 +109,53 @@ def test_check_failure_throws_exception(requests_mock, config_gen, error_code):
|
108 | 109 | assert e.value.failure_type == FailureType.config_error
|
109 | 110 | assert "Access was denied to the property ID entered." in e.value.message
|
110 | 111 |
|
111 |
| -@pytest.mark.parametrize("error_code", (402,404, 405)) |
| 112 | + |
| 113 | +def test_exhausted_quota_recovers_after_two_retries(requests_mock, config_gen): |
| 114 | + """ |
| 115 | + If the account runs out of quota the api will return a message asking us to back off for one hour. |
| 116 | + We have set backoff time for this scenario to 30 minutes to check if quota is already recovered, if not |
| 117 | + it will backoff again 30 minutes and quote should be reestablished by then. |
| 118 | + Now, we don't want wait one hour to test out this retry behavior so we will fix time dividing by 600 the quota |
| 119 | + recovery time and also the backoff time. |
| 120 | + """ |
| 121 | + requests_mock.register_uri( |
| 122 | + "POST", "https://oauth2.googleapis.com/token", json={"access_token": "access_token", "expires_in": 3600, "token_type": "Bearer"} |
| 123 | + ) |
| 124 | + error_response = {"error": {"message":"Exhausted potentially thresholded requests quota. This quota will refresh in under an hour. To learn more, see"}} |
| 125 | + requests_mock.register_uri( |
| 126 | + "GET", |
| 127 | + "https://analyticsdata.googleapis.com/v1beta/properties/UA-11111111/metadata", |
| 128 | + # first try we get 429 t=~0 |
| 129 | + [{"json": error_response, "status_code": 429}, |
| 130 | + # first retry we get 429 t=~1800 |
| 131 | + {"json": error_response, "status_code": 429}, |
| 132 | + # second retry quota is recovered, t=~3600 |
| 133 | + {"json": { |
| 134 | + "dimensions": [{"apiName": "date"}, {"apiName": "country"}, {"apiName": "language"}, {"apiName": "browser"}], |
| 135 | + "metrics": [{"apiName": "totalUsers"}, {"apiName": "screenPageViews"}, {"apiName": "sessions"}], |
| 136 | + }, "status_code": 200} |
| 137 | + ] |
| 138 | + ) |
| 139 | + def fix_time(time): |
| 140 | + return int(time / 600 ) |
| 141 | + source = SourceGoogleAnalyticsDataApi() |
| 142 | + logger = MagicMock() |
| 143 | + max_time_fixed = fix_time(GoogleAnalyticsDatApiErrorHandler.QUOTA_RECOVERY_TIME) |
| 144 | + potentially_thresholded_requests_per_hour_mapping = GoogleAnalyticsApiQuotaBase.quota_mapping["potentiallyThresholdedRequestsPerHour"] |
| 145 | + threshold_backoff_time = potentially_thresholded_requests_per_hour_mapping["backoff"] |
| 146 | + fixed_threshold_backoff_time = fix_time(threshold_backoff_time) |
| 147 | + potentially_thresholded_requests_per_hour_mapping_fixed = { |
| 148 | + **potentially_thresholded_requests_per_hour_mapping, |
| 149 | + "backoff": fixed_threshold_backoff_time, |
| 150 | + } |
| 151 | + with ( |
| 152 | + patch.object(GoogleAnalyticsDatApiErrorHandler, 'QUOTA_RECOVERY_TIME', new=max_time_fixed), |
| 153 | + patch.object(GoogleAnalyticsApiQuotaBase, 'quota_mapping', new={**GoogleAnalyticsApiQuotaBase.quota_mapping,"potentiallyThresholdedRequestsPerHour": potentially_thresholded_requests_per_hour_mapping_fixed})): |
| 154 | + output = source.check(logger, config_gen(property_ids=["UA-11111111"])) |
| 155 | + assert output == AirbyteConnectionStatus(status=Status.SUCCEEDED, message=None) |
| 156 | + |
| 157 | + |
| 158 | +@pytest.mark.parametrize("error_code", (402, 404, 405)) |
112 | 159 | def test_check_failure(requests_mock, config_gen, error_code):
|
113 | 160 | requests_mock.register_uri(
|
114 | 161 | "POST", "https://oauth2.googleapis.com/token", json={"access_token": "access_token", "expires_in": 3600, "token_type": "Bearer"}
|
|
0 commit comments