-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Authenticator refactoring (preparation for app token refres…
…hing) (#281) **Note: In this PR I am not changing functionality but just refactoring to make functionality changes possible in a future PR.** Essentially: [make the change easy, then make the easy change.](https://x.com/KentBeck/status/250733358307500032?lang=en) My goal is to add github app token features, for example refreshing app tokens before they expire (they're only good for an hour and currently don't refresh), and allowing multiple app tokens Currently, personal and app tokens are stored in the same list and treated interchangeably, so some structural changes are needed in order to handle them differently. My plan is to develop PersonalTokenManager and AppTokenManager classes so the code for working with each token type can be built into its manager class. In this PR I start by proposing to convert the TokenRateLimit class to a more general TokenManager, and I move functionality common to both token types there. I've added unit tests for the methods in TokenManager. Follow up PRs will develop PersonalTokenManager and AppTokenManager and implement the new app token features. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Edgar Ramírez Mondragón <[email protected]>
- Loading branch information
1 parent
ef782ce
commit b009443
Showing
2 changed files
with
188 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from datetime import datetime, timedelta | ||
from unittest.mock import MagicMock, patch | ||
|
||
import pytest | ||
import requests | ||
|
||
from tap_github.authenticator import TokenManager | ||
|
||
|
||
class TestTokenManager: | ||
|
||
def test_default_rate_limits(self): | ||
token_manager = TokenManager("mytoken", rate_limit_buffer=700) | ||
|
||
assert token_manager.rate_limit == 5000 | ||
assert token_manager.rate_limit_remaining == 5000 | ||
assert token_manager.rate_limit_reset is None | ||
assert token_manager.rate_limit_used == 0 | ||
assert token_manager.rate_limit_buffer == 700 | ||
|
||
token_manager_2 = TokenManager("mytoken") | ||
assert token_manager_2.rate_limit_buffer == 1000 | ||
|
||
def test_update_rate_limit(self): | ||
mock_response_headers = { | ||
"X-RateLimit-Limit": "5000", | ||
"X-RateLimit-Remaining": "4999", | ||
"X-RateLimit-Reset": "1372700873", | ||
"X-RateLimit-Used": "1", | ||
} | ||
|
||
token_manager = TokenManager("mytoken") | ||
token_manager.update_rate_limit(mock_response_headers) | ||
|
||
assert token_manager.rate_limit == 5000 | ||
assert token_manager.rate_limit_remaining == 4999 | ||
assert token_manager.rate_limit_reset == 1372700873 | ||
assert token_manager.rate_limit_used == 1 | ||
|
||
def test_is_valid_token_successful(self): | ||
with patch("requests.get") as mock_get: | ||
mock_response = mock_get.return_value | ||
mock_response.raise_for_status.return_value = None | ||
|
||
token_manager = TokenManager("validtoken") | ||
|
||
assert token_manager.is_valid_token() | ||
mock_get.assert_called_once_with( | ||
url="https://api.github.com/rate_limit", | ||
headers={"Authorization": "token validtoken"}, | ||
) | ||
|
||
def test_is_valid_token_failure(self): | ||
with patch("requests.get") as mock_get: | ||
# Setup for a failed request | ||
mock_response = mock_get.return_value | ||
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError() | ||
mock_response.status_code = 401 | ||
mock_response.content = b"Unauthorized Access" | ||
mock_response.reason = "Unauthorized" | ||
|
||
token_manager = TokenManager("invalidtoken") | ||
token_manager.logger = MagicMock() | ||
|
||
assert not token_manager.is_valid_token() | ||
token_manager.logger.warning.assert_called_once() | ||
assert "401" in token_manager.logger.warning.call_args[0][0] | ||
|
||
def test_has_calls_remaining_succeeds_if_token_never_used(self): | ||
token_manager = TokenManager("mytoken") | ||
assert token_manager.has_calls_remaining() | ||
|
||
def test_has_calls_remaining_succeeds_if_lots_remaining(self): | ||
mock_response_headers = { | ||
"X-RateLimit-Limit": "5000", | ||
"X-RateLimit-Remaining": "4999", | ||
"X-RateLimit-Reset": "1372700873", | ||
"X-RateLimit-Used": "1", | ||
} | ||
|
||
token_manager = TokenManager("mytoken") | ||
token_manager.update_rate_limit(mock_response_headers) | ||
|
||
assert token_manager.has_calls_remaining() | ||
|
||
def test_has_calls_remaining_succeeds_if_reset_time_reached(self): | ||
mock_response_headers = { | ||
"X-RateLimit-Limit": "5000", | ||
"X-RateLimit-Remaining": "1", | ||
"X-RateLimit-Reset": "1372700873", | ||
"X-RateLimit-Used": "4999", | ||
} | ||
|
||
token_manager = TokenManager("mytoken", rate_limit_buffer=1000) | ||
token_manager.update_rate_limit(mock_response_headers) | ||
|
||
assert token_manager.has_calls_remaining() | ||
|
||
def test_has_calls_remaining_fails_if_few_calls_remaining_and_reset_time_not_reached( | ||
self, | ||
): | ||
mock_response_headers = { | ||
"X-RateLimit-Limit": "5000", | ||
"X-RateLimit-Remaining": "1", | ||
"X-RateLimit-Reset": str( | ||
int((datetime.now() + timedelta(days=100)).timestamp()) | ||
), | ||
"X-RateLimit-Used": "4999", | ||
} | ||
|
||
token_manager = TokenManager("mytoken", rate_limit_buffer=1000) | ||
token_manager.update_rate_limit(mock_response_headers) | ||
|
||
assert not token_manager.has_calls_remaining() |