Skip to content

Commit

Permalink
Fix refresh bug
Browse files Browse the repository at this point in the history
  • Loading branch information
parafoxia committed Mar 16, 2022
1 parent 7d3ebb7 commit 5bf39e5
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 9 deletions.
2 changes: 1 addition & 1 deletion analytix/__init__.py
Expand Up @@ -37,7 +37,7 @@
)

__productname__ = "analytix"
__version__ = "3.0.0"
__version__ = "3.0.1"
__description__ = "A simple yet powerful wrapper for the YouTube Analytics API."
__url__ = "https://github.com/parafoxia/analytix"
__docs__ = "https://analytix.readthedocs.io"
Expand Down
13 changes: 10 additions & 3 deletions analytix/analytics.py
Expand Up @@ -137,7 +137,10 @@ def _retrieve_tokens(self) -> Tokens:
data, headers = oauth.access_data_and_headers(code, self.secrets)

r = self._session.post(self.secrets.token_uri, data=data, headers=headers)
r.raise_for_status()
if r.is_error:
error = r.json()["error"]
raise errors.AuthenticationError(error["code"], error["message"])

return Tokens.from_data(r.json())

def needs_refresh(self) -> bool:
Expand Down Expand Up @@ -174,8 +177,12 @@ def refresh_access_token(self) -> None:
)

r = self._session.post(self.secrets.token_uri, data=data, headers=headers)
r.raise_for_status()
self._tokens.update(r.json())
if not r.is_error:
self._tokens.update(r.json())
else:
log.info("Your refresh token has expired; you will need to reauthorise")
self._tokens = self._retrieve_tokens()

self._tokens.write(self._token_path)

def authorise( # nosec B107
Expand Down
13 changes: 10 additions & 3 deletions analytix/async_analytics.py
Expand Up @@ -138,7 +138,10 @@ async def _retrieve_tokens(self) -> Tokens:
data, headers = oauth.access_data_and_headers(code, self.secrets)

r = await self._session.post(self.secrets.token_uri, data=data, headers=headers)
r.raise_for_status()
if r.is_error:
error = r.json()["error"]
raise errors.AuthenticationError(error["code"], error["message"])

return Tokens.from_data(r.json())

async def needs_refresh(self) -> bool:
Expand Down Expand Up @@ -177,8 +180,12 @@ async def refresh_access_token(self) -> None:
)

r = await self._session.post(self.secrets.token_uri, data=data, headers=headers)
r.raise_for_status()
self._tokens.update(r.json())
if not r.is_error:
self._tokens.update(r.json())
else:
log.info("Your refresh token has expired; you will need to reauthorise")
self._tokens = await self._retrieve_tokens()

await self._tokens.awrite(self._token_path)

async def authorise( # nosec B107
Expand Down
8 changes: 8 additions & 0 deletions analytix/errors.py
Expand Up @@ -53,6 +53,14 @@ def __init__(self, code: str, message: str) -> None:
super().__init__(f"API returned {code}: {message}")


class AuthenticationError(AnalytixError):
"""Exception thrown when something goes wrong during the OAuth
authentication process."""

def __init__(self, code: str, message: str) -> None:
super().__init__(f"Authentication failure ({code}): {message}")


class DataFrameConversionError(AnalytixError):
"""Exception thrown when converting a report to a DataFrame
fails."""
Expand Down
22 changes: 21 additions & 1 deletion tests/test_analytics.py
Expand Up @@ -37,7 +37,7 @@
import pytest

from analytix import Analytics
from analytix.errors import APIError
from analytix.errors import APIError, AuthenticationError
from analytix.report_types import TimeBasedActivity
from analytix.secrets import Secrets
from analytix.tokens import Tokens
Expand Down Expand Up @@ -182,6 +182,26 @@ def test_refresh_access_tokens_with_no_tokens(client, caplog):
assert "There are no tokens to refresh" in caplog.text


def test_retrieve_token_refresh_token_failure(client, tokens):
with mock.patch.object(httpx.Client, "post") as mock_post:
mock_post.return_value = httpx.Response(
status_code=403,
request=mock.Mock(),
json={"error": {"code": 403, "message": "You suck"}},
)

client._tokens = tokens

with mock.patch.object(builtins, "input") as mock_input:
mock_input.return_value = "lol ecks dee"

# If we get here, refreshing failed, and we can also test
# retrieval failure.
with pytest.raises(AuthenticationError) as exc:
client.refresh_access_token()
assert str(exc.value) == "Authentication failure (403): You suck"


def test_needs_refresh_with_valid(client):
with mock.patch.object(httpx.Client, "get") as mock_get:
with mock.patch.object(Tokens, "write") as mock_write:
Expand Down
22 changes: 21 additions & 1 deletion tests/test_async_analytics.py
Expand Up @@ -41,7 +41,7 @@
import pytest_asyncio

from analytix import AsyncAnalytics
from analytix.errors import APIError
from analytix.errors import APIError, AuthenticationError
from analytix.report_types import TimeBasedActivity
from analytix.secrets import Secrets
from analytix.tokens import Tokens
Expand Down Expand Up @@ -185,6 +185,26 @@ async def test_refresh_access_tokens_with_no_tokens(client, caplog):
assert "There are no tokens to refresh" in caplog.text


async def test_retrieve_token_refresh_token_failure(client, tokens):
with mock.patch.object(httpx.AsyncClient, "post") as mock_post:
mock_post.return_value = httpx.Response(
status_code=403,
request=mock.Mock(),
json={"error": {"code": 403, "message": "You suck"}},
)

client._tokens = tokens

with mock.patch.object(builtins, "input") as mock_input:
mock_input.return_value = "lol ecks dee"

# If we get here, refreshing failed, and we can also test
# retrieval failure.
with pytest.raises(AuthenticationError) as exc:
await client.refresh_access_token()
assert str(exc.value) == "Authentication failure (403): You suck"


async def test_needs_refresh_with_valid(client):
with mock.patch.object(httpx.AsyncClient, "get") as mock_get:
mock_get.return_value = httpx.Response(
Expand Down

0 comments on commit 5bf39e5

Please sign in to comment.