326 lines
11 KiB
Python
326 lines
11 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from unittest.mock import AsyncMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from yuxi.channels.adapters.twitch.auth_provider import (
|
||
|
|
AuthProvider,
|
||
|
|
RefreshingAuthProvider,
|
||
|
|
StaticAuthProvider,
|
||
|
|
create_auth_provider,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestStaticAuthProvider:
|
||
|
|
def test_returns_access_token(self):
|
||
|
|
provider = StaticAuthProvider(client_id="cid", access_token="tok")
|
||
|
|
result = provider._access_token
|
||
|
|
assert result == "tok"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_access_token(self):
|
||
|
|
provider = StaticAuthProvider(client_id="cid", access_token="tok")
|
||
|
|
token = await provider.get_access_token()
|
||
|
|
assert token == "tok"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_client_id(self):
|
||
|
|
provider = StaticAuthProvider(client_id="cid", access_token="tok")
|
||
|
|
cid = await provider.get_client_id()
|
||
|
|
assert cid == "cid"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_with_valid_token(self):
|
||
|
|
provider = StaticAuthProvider(client_id="cid", access_token="tok")
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.validate_token") as mock_validate:
|
||
|
|
mock_validate.return_value = {"id": "123"}
|
||
|
|
result = await provider.validate()
|
||
|
|
assert result is True
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_with_invalid_token(self):
|
||
|
|
provider = StaticAuthProvider(client_id="cid", access_token="tok")
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.validate_token") as mock_validate:
|
||
|
|
mock_validate.return_value = None
|
||
|
|
result = await provider.validate()
|
||
|
|
assert result is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_missing_credentials(self):
|
||
|
|
provider = StaticAuthProvider(client_id="", access_token="")
|
||
|
|
result = await provider.validate()
|
||
|
|
assert result is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_access_token_returns_none_when_empty(self):
|
||
|
|
provider = StaticAuthProvider(client_id="cid", access_token="")
|
||
|
|
token = await provider.get_access_token()
|
||
|
|
assert token is None
|
||
|
|
|
||
|
|
|
||
|
|
class TestRefreshingAuthProvider:
|
||
|
|
def test_initial_state(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
assert provider._access_token == "tok"
|
||
|
|
assert provider._refresh_token == "rtok"
|
||
|
|
assert provider._client_id == "cid"
|
||
|
|
assert provider._client_secret == "secret"
|
||
|
|
assert provider._token_expires_in == 0
|
||
|
|
assert provider._token_expires_at is None
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_access_token_not_expired(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_at = None
|
||
|
|
token = await provider.get_access_token()
|
||
|
|
assert token == "tok"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_client_id(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
cid = await provider.get_client_id()
|
||
|
|
assert cid == "cid"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_with_valid_token(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.validate_token") as mock_validate:
|
||
|
|
mock_validate.return_value = {"id": "123"}
|
||
|
|
result = await provider.validate()
|
||
|
|
assert result is True
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_validate_with_empty_credentials(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
result = await provider.validate()
|
||
|
|
assert result is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_refresh_successful(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="old_tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.refresh_access_token") as mock_refresh:
|
||
|
|
mock_refresh.return_value = {
|
||
|
|
"access_token": "new_tok",
|
||
|
|
"refresh_token": "new_rtok",
|
||
|
|
"expires_in": 14400,
|
||
|
|
}
|
||
|
|
result = await provider._refresh()
|
||
|
|
assert result is True
|
||
|
|
assert provider._access_token == "new_tok"
|
||
|
|
assert provider._refresh_token == "new_rtok"
|
||
|
|
assert provider._token_expires_in == 14400
|
||
|
|
assert provider._token_expires_at is not None
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_refresh_no_new_access_token(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="old_tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.refresh_access_token") as mock_refresh:
|
||
|
|
mock_refresh.return_value = {
|
||
|
|
"refresh_token": "new_rtok",
|
||
|
|
"expires_in": 14400,
|
||
|
|
}
|
||
|
|
result = await provider._refresh()
|
||
|
|
assert result is True
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_refresh_failure_single_attempt(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="old_tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.refresh_access_token") as mock_refresh:
|
||
|
|
mock_refresh.return_value = None
|
||
|
|
result = await provider._refresh()
|
||
|
|
assert result is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_refresh_missing_client_secret(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="",
|
||
|
|
access_token="old_tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
result = await provider._refresh()
|
||
|
|
assert result is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_refresh_missing_refresh_token(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="old_tok",
|
||
|
|
refresh_token="",
|
||
|
|
)
|
||
|
|
result = await provider._refresh()
|
||
|
|
assert result is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_is_expired_when_none(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_at = None
|
||
|
|
assert provider._is_expired() is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_is_expired_when_future(self):
|
||
|
|
import time
|
||
|
|
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_at = time.time() + 3600
|
||
|
|
assert provider._is_expired() is False
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_is_expired_when_past(self):
|
||
|
|
import time
|
||
|
|
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_at = time.time() - 1
|
||
|
|
assert provider._is_expired() is True
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_access_token_expired_and_refreshes(self):
|
||
|
|
import time
|
||
|
|
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="old_tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_at = time.time() - 1
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.refresh_access_token") as mock_refresh:
|
||
|
|
mock_refresh.return_value = {
|
||
|
|
"access_token": "new_tok",
|
||
|
|
"refresh_token": "new_rtok",
|
||
|
|
"expires_in": 14400,
|
||
|
|
}
|
||
|
|
token = await provider.get_access_token()
|
||
|
|
assert token == "new_tok"
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_get_access_token_expired_refresh_fails(self):
|
||
|
|
import time
|
||
|
|
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="old_tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_at = time.time() - 1
|
||
|
|
with patch("yuxi.channels.adapters.twitch.auth_provider.refresh_access_token") as mock_refresh:
|
||
|
|
mock_refresh.return_value = None
|
||
|
|
token = await provider.get_access_token()
|
||
|
|
assert token is None
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_expires_at_property(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_at = 12345.0
|
||
|
|
assert provider.expires_at == 12345.0
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_expires_in_property(self):
|
||
|
|
provider = RefreshingAuthProvider(
|
||
|
|
client_id="cid",
|
||
|
|
client_secret="secret",
|
||
|
|
access_token="tok",
|
||
|
|
refresh_token="rtok",
|
||
|
|
)
|
||
|
|
provider._token_expires_in = 3600
|
||
|
|
assert provider.expires_in == 3600
|
||
|
|
|
||
|
|
|
||
|
|
class TestCreateAuthProvider:
|
||
|
|
def test_creates_refreshing_when_secret_and_refresh_present(self):
|
||
|
|
config = {
|
||
|
|
"client_id": "cid",
|
||
|
|
"client_secret": "secret",
|
||
|
|
"access_token": "tok",
|
||
|
|
"refresh_token": "rtok",
|
||
|
|
}
|
||
|
|
provider = create_auth_provider(config)
|
||
|
|
assert isinstance(provider, RefreshingAuthProvider)
|
||
|
|
|
||
|
|
def test_creates_static_when_no_secret(self):
|
||
|
|
config = {
|
||
|
|
"client_id": "cid",
|
||
|
|
"access_token": "tok",
|
||
|
|
}
|
||
|
|
provider = create_auth_provider(config)
|
||
|
|
assert isinstance(provider, StaticAuthProvider)
|
||
|
|
|
||
|
|
def test_creates_static_when_no_refresh_token(self):
|
||
|
|
config = {
|
||
|
|
"client_id": "cid",
|
||
|
|
"client_secret": "secret",
|
||
|
|
"access_token": "tok",
|
||
|
|
}
|
||
|
|
provider = create_auth_provider(config)
|
||
|
|
assert isinstance(provider, StaticAuthProvider)
|
||
|
|
|
||
|
|
def test_creates_static_when_empty_config(self):
|
||
|
|
config = {}
|
||
|
|
provider = create_auth_provider(config)
|
||
|
|
assert isinstance(provider, StaticAuthProvider)
|
||
|
|
|
||
|
|
def test_auth_provider_is_abstract(self):
|
||
|
|
assert hasattr(AuthProvider, "__abstractmethods__")
|