mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Refactor Twitch tests (#114330)
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
This commit is contained in:
parent
21b9a4ef2e
commit
3d2ecd6a28
@ -39,7 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
access_token = entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN]
|
access_token = entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN]
|
||||||
client = await Twitch(
|
client = Twitch(
|
||||||
app_id=implementation.client_id,
|
app_id=implementation.client_id,
|
||||||
authenticate_app=False,
|
authenticate_app=False,
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,7 @@ class OAuth2FlowHandler(
|
|||||||
self.flow_impl,
|
self.flow_impl,
|
||||||
)
|
)
|
||||||
|
|
||||||
client = await Twitch(
|
client = Twitch(
|
||||||
app_id=implementation.client_id,
|
app_id=implementation.client_id,
|
||||||
authenticate_app=False,
|
authenticate_app=False,
|
||||||
)
|
)
|
||||||
|
@ -1,246 +1,55 @@
|
|||||||
"""Tests for the Twitch component."""
|
"""Tests for the Twitch component."""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from collections.abc import AsyncGenerator, AsyncIterator
|
from collections.abc import AsyncGenerator, AsyncIterator
|
||||||
from dataclasses import dataclass
|
from typing import Any, Generic, TypeVar
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from twitchAPI.object.api import FollowedChannelsResult, TwitchUser
|
from twitchAPI.object.base import TwitchObject
|
||||||
from twitchAPI.twitch import (
|
|
||||||
InvalidTokenException,
|
|
||||||
MissingScopeException,
|
|
||||||
TwitchAPIException,
|
|
||||||
TwitchAuthorizationException,
|
|
||||||
TwitchResourceNotFound,
|
|
||||||
)
|
|
||||||
from twitchAPI.type import AuthScope, AuthType
|
|
||||||
|
|
||||||
|
from homeassistant.components.twitch import DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, load_json_array_fixture
|
||||||
|
|
||||||
|
|
||||||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||||
"""Fixture for setting up the component."""
|
"""Fixture for setting up the component."""
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
def _get_twitch_user(user_id: str = "123") -> TwitchUser:
|
TwitchType = TypeVar("TwitchType", bound=TwitchObject)
|
||||||
return TwitchUser(
|
|
||||||
id=user_id,
|
|
||||||
display_name="channel123",
|
|
||||||
offline_image_url="logo.png",
|
|
||||||
profile_image_url="logo.png",
|
|
||||||
view_count=42,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_iterator(iterable) -> AsyncIterator:
|
class TwitchIterObject(Generic[TwitchType]):
|
||||||
"""Return async iterator."""
|
"""Twitch object iterator."""
|
||||||
for i in iterable:
|
|
||||||
yield i
|
|
||||||
|
|
||||||
|
def __init__(self, fixture: str, target_type: type[TwitchType]) -> None:
|
||||||
|
"""Initialize object."""
|
||||||
|
self.raw_data = load_json_array_fixture(fixture, DOMAIN)
|
||||||
|
self.data = [target_type(**item) for item in self.raw_data]
|
||||||
|
self.total = len(self.raw_data)
|
||||||
|
self.target_type = target_type
|
||||||
|
|
||||||
@dataclass
|
async def __aiter__(self) -> AsyncIterator[TwitchType]:
|
||||||
class UserSubscriptionMock:
|
|
||||||
"""User subscription mock."""
|
|
||||||
|
|
||||||
broadcaster_id: str
|
|
||||||
is_gift: bool
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class FollowedChannelMock:
|
|
||||||
"""Followed channel mock."""
|
|
||||||
|
|
||||||
broadcaster_login: str
|
|
||||||
followed_at: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ChannelFollowerMock:
|
|
||||||
"""Channel follower mock."""
|
|
||||||
|
|
||||||
user_id: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class StreamMock:
|
|
||||||
"""Stream mock."""
|
|
||||||
|
|
||||||
game_name: str
|
|
||||||
title: str
|
|
||||||
thumbnail_url: str
|
|
||||||
|
|
||||||
|
|
||||||
class TwitchUserFollowResultMock:
|
|
||||||
"""Mock for twitch user follow result."""
|
|
||||||
|
|
||||||
def __init__(self, follows: list[FollowedChannelMock]) -> None:
|
|
||||||
"""Initialize mock."""
|
|
||||||
self.total = len(follows)
|
|
||||||
self.data = follows
|
|
||||||
|
|
||||||
def __aiter__(self):
|
|
||||||
"""Return async iterator."""
|
"""Return async iterator."""
|
||||||
return async_iterator(self.data)
|
async for item in get_generator_from_data(self.raw_data, self.target_type):
|
||||||
|
yield item
|
||||||
|
|
||||||
|
|
||||||
class ChannelFollowersResultMock:
|
async def get_generator(
|
||||||
"""Mock for twitch channel follow result."""
|
fixture: str, target_type: type[TwitchType]
|
||||||
|
) -> AsyncGenerator[TwitchType, None]:
|
||||||
def __init__(self, follows: list[ChannelFollowerMock]) -> None:
|
"""Return async generator."""
|
||||||
"""Initialize mock."""
|
data = load_json_array_fixture(fixture, DOMAIN)
|
||||||
self.total = len(follows)
|
async for item in get_generator_from_data(data, target_type):
|
||||||
self.data = follows
|
yield item
|
||||||
|
|
||||||
def __aiter__(self):
|
|
||||||
"""Return async iterator."""
|
|
||||||
return async_iterator(self.data)
|
|
||||||
|
|
||||||
|
|
||||||
STREAMS = StreamMock(
|
async def get_generator_from_data(
|
||||||
game_name="Good game", title="Title", thumbnail_url="stream-medium.png"
|
items: list[dict[str, Any]], target_type: type[TwitchType]
|
||||||
)
|
) -> AsyncGenerator[TwitchType, None]:
|
||||||
|
"""Return async generator."""
|
||||||
|
for item in items:
|
||||||
class TwitchMock:
|
yield target_type(**item)
|
||||||
"""Mock for the twitch object."""
|
|
||||||
|
|
||||||
is_streaming = True
|
|
||||||
is_gifted = False
|
|
||||||
is_subscribed = False
|
|
||||||
is_following = True
|
|
||||||
different_user_id = False
|
|
||||||
|
|
||||||
def __await__(self):
|
|
||||||
"""Add async capabilities to the mock."""
|
|
||||||
t = asyncio.create_task(self._noop())
|
|
||||||
yield from t
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def _noop(self):
|
|
||||||
"""Fake function to create task."""
|
|
||||||
|
|
||||||
async def get_users(
|
|
||||||
self, user_ids: list[str] | None = None, logins: list[str] | None = None
|
|
||||||
) -> AsyncGenerator[TwitchUser, None]:
|
|
||||||
"""Get list of mock users."""
|
|
||||||
users = [_get_twitch_user("234" if self.different_user_id else "123")]
|
|
||||||
for user in users:
|
|
||||||
yield user
|
|
||||||
|
|
||||||
def has_required_auth(
|
|
||||||
self, required_type: AuthType, required_scope: list[AuthScope]
|
|
||||||
) -> bool:
|
|
||||||
"""Return if auth required."""
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def check_user_subscription(
|
|
||||||
self, broadcaster_id: str, user_id: str
|
|
||||||
) -> UserSubscriptionMock:
|
|
||||||
"""Check if the user is subscribed."""
|
|
||||||
if self.is_subscribed:
|
|
||||||
return UserSubscriptionMock(
|
|
||||||
broadcaster_id=broadcaster_id, is_gift=self.is_gifted
|
|
||||||
)
|
|
||||||
raise TwitchResourceNotFound
|
|
||||||
|
|
||||||
async def set_user_authentication(
|
|
||||||
self,
|
|
||||||
token: str,
|
|
||||||
scope: list[AuthScope],
|
|
||||||
refresh_token: str | None = None,
|
|
||||||
validate: bool = True,
|
|
||||||
) -> None:
|
|
||||||
"""Set user authentication."""
|
|
||||||
|
|
||||||
async def get_followed_channels(
|
|
||||||
self, user_id: str, broadcaster_id: str | None = None
|
|
||||||
) -> FollowedChannelsResult:
|
|
||||||
"""Get followed channels."""
|
|
||||||
if self.is_following:
|
|
||||||
return TwitchUserFollowResultMock(
|
|
||||||
[
|
|
||||||
FollowedChannelMock(
|
|
||||||
followed_at=datetime(year=2023, month=8, day=1),
|
|
||||||
broadcaster_login="internetofthings",
|
|
||||||
),
|
|
||||||
FollowedChannelMock(
|
|
||||||
followed_at=datetime(year=2023, month=8, day=1),
|
|
||||||
broadcaster_login="homeassistant",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return TwitchUserFollowResultMock([])
|
|
||||||
|
|
||||||
async def get_channel_followers(
|
|
||||||
self, broadcaster_id: str
|
|
||||||
) -> ChannelFollowersResultMock:
|
|
||||||
"""Get channel followers."""
|
|
||||||
return ChannelFollowersResultMock([ChannelFollowerMock(user_id="abc")])
|
|
||||||
|
|
||||||
async def get_streams(
|
|
||||||
self, user_id: list[str], first: int
|
|
||||||
) -> AsyncGenerator[StreamMock, None]:
|
|
||||||
"""Get streams for the user."""
|
|
||||||
streams = []
|
|
||||||
if self.is_streaming:
|
|
||||||
streams = [STREAMS]
|
|
||||||
for stream in streams:
|
|
||||||
yield stream
|
|
||||||
|
|
||||||
|
|
||||||
class TwitchUnauthorizedMock(TwitchMock):
|
|
||||||
"""Twitch mock to test if the client is unauthorized."""
|
|
||||||
|
|
||||||
def __await__(self):
|
|
||||||
"""Add async capabilities to the mock."""
|
|
||||||
raise TwitchAuthorizationException
|
|
||||||
|
|
||||||
|
|
||||||
class TwitchMissingScopeMock(TwitchMock):
|
|
||||||
"""Twitch mock to test missing scopes."""
|
|
||||||
|
|
||||||
async def set_user_authentication(
|
|
||||||
self, token: str, scope: list[AuthScope], validate: bool = True
|
|
||||||
) -> None:
|
|
||||||
"""Set user authentication."""
|
|
||||||
raise MissingScopeException
|
|
||||||
|
|
||||||
|
|
||||||
class TwitchInvalidTokenMock(TwitchMock):
|
|
||||||
"""Twitch mock to test invalid token."""
|
|
||||||
|
|
||||||
async def set_user_authentication(
|
|
||||||
self, token: str, scope: list[AuthScope], validate: bool = True
|
|
||||||
) -> None:
|
|
||||||
"""Set user authentication."""
|
|
||||||
raise InvalidTokenException
|
|
||||||
|
|
||||||
|
|
||||||
class TwitchInvalidUserMock(TwitchMock):
|
|
||||||
"""Twitch mock to test invalid user."""
|
|
||||||
|
|
||||||
async def get_users(
|
|
||||||
self, user_ids: list[str] | None = None, logins: list[str] | None = None
|
|
||||||
) -> AsyncGenerator[TwitchUser, None]:
|
|
||||||
"""Get list of mock users."""
|
|
||||||
if user_ids is not None or logins is not None:
|
|
||||||
async for user in super().get_users(user_ids, logins):
|
|
||||||
yield user
|
|
||||||
else:
|
|
||||||
for user in []:
|
|
||||||
yield user
|
|
||||||
|
|
||||||
|
|
||||||
class TwitchAPIExceptionMock(TwitchMock):
|
|
||||||
"""Twitch mock to test when twitch api throws unknown exception."""
|
|
||||||
|
|
||||||
async def check_user_subscription(
|
|
||||||
self, broadcaster_id: str, user_id: str
|
|
||||||
) -> UserSubscriptionMock:
|
|
||||||
"""Check if the user is subscribed."""
|
|
||||||
raise TwitchAPIException
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""Configure tests for the Twitch integration."""
|
"""Configure tests for the Twitch integration."""
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable, Generator
|
from collections.abc import Generator
|
||||||
import time
|
import time
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from twitchAPI.object.api import FollowedChannel, Stream, TwitchUser, UserSubscription
|
||||||
|
|
||||||
from homeassistant.components.application_credentials import (
|
from homeassistant.components.application_credentials import (
|
||||||
ClientCredential,
|
ClientCredential,
|
||||||
@ -14,11 +15,10 @@ from homeassistant.components.twitch.const import DOMAIN, OAUTH2_TOKEN, OAUTH_SC
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from . import TwitchIterObject, get_generator
|
||||||
from tests.components.twitch import TwitchMock
|
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
|
||||||
|
|
||||||
type ComponentSetup = Callable[[TwitchMock | None], Awaitable[None]]
|
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||||
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
CLIENT_ID = "1234"
|
CLIENT_ID = "1234"
|
||||||
CLIENT_SECRET = "5678"
|
CLIENT_SECRET = "5678"
|
||||||
@ -92,23 +92,32 @@ def mock_connection(aioclient_mock: AiohttpClientMocker) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="twitch_mock")
|
@pytest.fixture
|
||||||
def twitch_mock() -> TwitchMock:
|
def twitch_mock() -> Generator[AsyncMock, None, None]:
|
||||||
"""Return as fixture to inject other mocks."""
|
"""Return as fixture to inject other mocks."""
|
||||||
return TwitchMock()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="twitch")
|
|
||||||
def mock_twitch(twitch_mock: TwitchMock):
|
|
||||||
"""Mock Twitch."""
|
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.twitch.Twitch",
|
"homeassistant.components.twitch.Twitch",
|
||||||
return_value=twitch_mock,
|
autospec=True,
|
||||||
),
|
) as mock_client,
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.twitch.config_flow.Twitch",
|
"homeassistant.components.twitch.config_flow.Twitch",
|
||||||
return_value=twitch_mock,
|
new=mock_client,
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
yield twitch_mock
|
mock_client.return_value.get_users = lambda *args, **kwargs: get_generator(
|
||||||
|
"get_users.json", TwitchUser
|
||||||
|
)
|
||||||
|
mock_client.return_value.get_followed_channels.return_value = TwitchIterObject(
|
||||||
|
"get_followed_channels.json", FollowedChannel
|
||||||
|
)
|
||||||
|
mock_client.return_value.get_streams.return_value = get_generator(
|
||||||
|
"get_streams.json", Stream
|
||||||
|
)
|
||||||
|
mock_client.return_value.check_user_subscription.return_value = (
|
||||||
|
UserSubscription(
|
||||||
|
**load_json_object_fixture("check_user_subscription.json", DOMAIN)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_client.return_value.has_required_auth.return_value = True
|
||||||
|
yield mock_client
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"is_gift": true
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"is_gift": false
|
||||||
|
}
|
1
tests/components/twitch/fixtures/empty_response.json
Normal file
1
tests/components/twitch/fixtures/empty_response.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
10
tests/components/twitch/fixtures/get_followed_channels.json
Normal file
10
tests/components/twitch/fixtures/get_followed_channels.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"broadcaster_login": "internetofthings",
|
||||||
|
"followed_at": "2023-08-01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"broadcaster_login": "homeassistant",
|
||||||
|
"followed_at": "2023-08-01"
|
||||||
|
}
|
||||||
|
]
|
7
tests/components/twitch/fixtures/get_streams.json
Normal file
7
tests/components/twitch/fixtures/get_streams.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"game_name": "Good game",
|
||||||
|
"title": "Title",
|
||||||
|
"thumbnail_url": "stream-medium.png"
|
||||||
|
}
|
||||||
|
]
|
9
tests/components/twitch/fixtures/get_users.json
Normal file
9
tests/components/twitch/fixtures/get_users.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"display_name": "channel123",
|
||||||
|
"offline_image_url": "logo.png",
|
||||||
|
"profile_image_url": "logo.png",
|
||||||
|
"view_count": 42
|
||||||
|
}
|
||||||
|
]
|
9
tests/components/twitch/fixtures/get_users_2.json
Normal file
9
tests/components/twitch/fixtures/get_users_2.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 456,
|
||||||
|
"display_name": "channel123",
|
||||||
|
"offline_image_url": "logo.png",
|
||||||
|
"profile_image_url": "logo.png",
|
||||||
|
"view_count": 42
|
||||||
|
}
|
||||||
|
]
|
@ -1,6 +1,8 @@
|
|||||||
"""Test config flow for Twitch."""
|
"""Test config flow for Twitch."""
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from twitchAPI.object.api import TwitchUser
|
||||||
|
|
||||||
from homeassistant.components.twitch.const import (
|
from homeassistant.components.twitch.const import (
|
||||||
CONF_CHANNELS,
|
CONF_CHANNELS,
|
||||||
@ -12,10 +14,9 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.data_entry_flow import FlowResult, FlowResultType
|
from homeassistant.data_entry_flow import FlowResult, FlowResultType
|
||||||
from homeassistant.helpers import config_entry_oauth2_flow
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
from . import setup_integration
|
from . import get_generator, setup_integration
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.components.twitch import TwitchMock
|
|
||||||
from tests.components.twitch.conftest import CLIENT_ID, TITLE
|
from tests.components.twitch.conftest import CLIENT_ID, TITLE
|
||||||
from tests.typing import ClientSessionGenerator
|
from tests.typing import ClientSessionGenerator
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ async def test_full_flow(
|
|||||||
hass_client_no_auth: ClientSessionGenerator,
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
current_request_with_host: None,
|
current_request_with_host: None,
|
||||||
mock_setup_entry,
|
mock_setup_entry,
|
||||||
twitch: TwitchMock,
|
twitch_mock: AsyncMock,
|
||||||
scopes: list[str],
|
scopes: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check full flow."""
|
"""Check full flow."""
|
||||||
@ -80,7 +81,7 @@ async def test_already_configured(
|
|||||||
current_request_with_host: None,
|
current_request_with_host: None,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
mock_setup_entry,
|
mock_setup_entry,
|
||||||
twitch: TwitchMock,
|
twitch_mock: AsyncMock,
|
||||||
scopes: list[str],
|
scopes: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check flow aborts when account already configured."""
|
"""Check flow aborts when account already configured."""
|
||||||
@ -90,13 +91,10 @@ async def test_already_configured(
|
|||||||
)
|
)
|
||||||
await _do_get_token(hass, result, hass_client_no_auth, scopes)
|
await _do_get_token(hass, result, hass_client_no_auth, scopes)
|
||||||
|
|
||||||
with patch(
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
"homeassistant.components.twitch.config_flow.Twitch", return_value=TwitchMock()
|
|
||||||
):
|
|
||||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.ABORT
|
assert result["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
async def test_reauth(
|
async def test_reauth(
|
||||||
@ -105,7 +103,7 @@ async def test_reauth(
|
|||||||
current_request_with_host: None,
|
current_request_with_host: None,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
mock_setup_entry,
|
mock_setup_entry,
|
||||||
twitch: TwitchMock,
|
twitch_mock: AsyncMock,
|
||||||
scopes: list[str],
|
scopes: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check reauth flow."""
|
"""Check reauth flow."""
|
||||||
@ -136,7 +134,7 @@ async def test_reauth_from_import(
|
|||||||
hass_client_no_auth: ClientSessionGenerator,
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
current_request_with_host: None,
|
current_request_with_host: None,
|
||||||
mock_setup_entry,
|
mock_setup_entry,
|
||||||
twitch: TwitchMock,
|
twitch_mock: AsyncMock,
|
||||||
expires_at,
|
expires_at,
|
||||||
scopes: list[str],
|
scopes: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -163,7 +161,7 @@ async def test_reauth_from_import(
|
|||||||
current_request_with_host,
|
current_request_with_host,
|
||||||
config_entry,
|
config_entry,
|
||||||
mock_setup_entry,
|
mock_setup_entry,
|
||||||
twitch,
|
twitch_mock,
|
||||||
scopes,
|
scopes,
|
||||||
)
|
)
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
@ -178,12 +176,14 @@ async def test_reauth_wrong_account(
|
|||||||
current_request_with_host: None,
|
current_request_with_host: None,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
mock_setup_entry,
|
mock_setup_entry,
|
||||||
twitch: TwitchMock,
|
twitch_mock: AsyncMock,
|
||||||
scopes: list[str],
|
scopes: list[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Check reauth flow."""
|
"""Check reauth flow."""
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
twitch.different_user_id = True
|
twitch_mock.return_value.get_users = lambda *args, **kwargs: get_generator(
|
||||||
|
"get_users_2.json", TwitchUser
|
||||||
|
)
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={
|
context={
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""Tests for YouTube."""
|
"""Tests for Twitch."""
|
||||||
|
|
||||||
import http
|
import http
|
||||||
import time
|
import time
|
||||||
from unittest.mock import patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ClientError
|
from aiohttp.client_exceptions import ClientError
|
||||||
import pytest
|
import pytest
|
||||||
@ -11,14 +11,14 @@ from homeassistant.components.twitch.const import DOMAIN, OAUTH2_TOKEN
|
|||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import TwitchMock, setup_integration
|
from . import setup_integration
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_success(
|
async def test_setup_success(
|
||||||
hass: HomeAssistant, config_entry: MockConfigEntry, twitch: TwitchMock
|
hass: HomeAssistant, config_entry: MockConfigEntry, twitch_mock: AsyncMock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test successful setup and unload."""
|
"""Test successful setup and unload."""
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
@ -38,7 +38,7 @@ async def test_expired_token_refresh_success(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
twitch: TwitchMock,
|
twitch_mock: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test expired token is refreshed."""
|
"""Test expired token is refreshed."""
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ async def test_expired_token_refresh_failure(
|
|||||||
status: http.HTTPStatus,
|
status: http.HTTPStatus,
|
||||||
expected_state: ConfigEntryState,
|
expected_state: ConfigEntryState,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
twitch: TwitchMock,
|
twitch_mock: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test failure while refreshing token with a transient error."""
|
"""Test failure while refreshing token with a transient error."""
|
||||||
|
|
||||||
@ -93,8 +93,10 @@ async def test_expired_token_refresh_failure(
|
|||||||
OAUTH2_TOKEN,
|
OAUTH2_TOKEN,
|
||||||
status=status,
|
status=status,
|
||||||
)
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
await setup_integration(hass, config_entry)
|
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Verify a transient failure has occurred
|
# Verify a transient failure has occurred
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
@ -102,7 +104,7 @@ async def test_expired_token_refresh_failure(
|
|||||||
|
|
||||||
|
|
||||||
async def test_expired_token_refresh_client_error(
|
async def test_expired_token_refresh_client_error(
|
||||||
hass: HomeAssistant, config_entry: MockConfigEntry, twitch: TwitchMock
|
hass: HomeAssistant, config_entry: MockConfigEntry, twitch_mock: AsyncMock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test failure while refreshing token with a client error."""
|
"""Test failure while refreshing token with a client error."""
|
||||||
|
|
||||||
@ -110,7 +112,10 @@ async def test_expired_token_refresh_client_error(
|
|||||||
"homeassistant.components.twitch.OAuth2Session.async_ensure_token_valid",
|
"homeassistant.components.twitch.OAuth2Session.async_ensure_token_valid",
|
||||||
side_effect=ClientError,
|
side_effect=ClientError,
|
||||||
):
|
):
|
||||||
await setup_integration(hass, config_entry)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Verify a transient failure has occurred
|
# Verify a transient failure has occurred
|
||||||
entries = hass.config_entries.async_entries(DOMAIN)
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
"""The tests for an update of the Twitch component."""
|
"""The tests for an update of the Twitch component."""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
import pytest
|
from twitchAPI.object.api import FollowedChannel, Stream, UserSubscription
|
||||||
|
from twitchAPI.type import TwitchResourceNotFound
|
||||||
|
|
||||||
|
from homeassistant.components.twitch import DOMAIN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from ...common import MockConfigEntry
|
from . import TwitchIterObject, get_generator_from_data, setup_integration
|
||||||
from . import (
|
|
||||||
TwitchAPIExceptionMock,
|
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||||
TwitchInvalidTokenMock,
|
|
||||||
TwitchInvalidUserMock,
|
|
||||||
TwitchMissingScopeMock,
|
|
||||||
TwitchMock,
|
|
||||||
TwitchUnauthorizedMock,
|
|
||||||
setup_integration,
|
|
||||||
)
|
|
||||||
|
|
||||||
ENTITY_ID = "sensor.channel123"
|
ENTITY_ID = "sensor.channel123"
|
||||||
|
|
||||||
|
|
||||||
async def test_offline(
|
async def test_offline(
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test offline state."""
|
"""Test offline state."""
|
||||||
twitch.is_streaming = False
|
twitch_mock.return_value.get_streams.return_value = get_generator_from_data(
|
||||||
|
[], Stream
|
||||||
|
)
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
|
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
@ -33,7 +31,7 @@ async def test_offline(
|
|||||||
|
|
||||||
|
|
||||||
async def test_streaming(
|
async def test_streaming(
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test streaming state."""
|
"""Test streaming state."""
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
@ -46,10 +44,15 @@ async def test_streaming(
|
|||||||
|
|
||||||
|
|
||||||
async def test_oauth_without_sub_and_follow(
|
async def test_oauth_without_sub_and_follow(
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test state with oauth."""
|
"""Test state with oauth."""
|
||||||
twitch.is_following = False
|
twitch_mock.return_value.get_followed_channels.return_value = TwitchIterObject(
|
||||||
|
"empty_response.json", FollowedChannel
|
||||||
|
)
|
||||||
|
twitch_mock.return_value.check_user_subscription.side_effect = (
|
||||||
|
TwitchResourceNotFound
|
||||||
|
)
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
|
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
@ -58,11 +61,15 @@ async def test_oauth_without_sub_and_follow(
|
|||||||
|
|
||||||
|
|
||||||
async def test_oauth_with_sub(
|
async def test_oauth_with_sub(
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test state with oauth and sub."""
|
"""Test state with oauth and sub."""
|
||||||
twitch.is_subscribed = True
|
twitch_mock.return_value.get_followed_channels.return_value = TwitchIterObject(
|
||||||
twitch.is_following = False
|
"empty_response.json", FollowedChannel
|
||||||
|
)
|
||||||
|
twitch_mock.return_value.check_user_subscription.return_value = UserSubscription(
|
||||||
|
**load_json_object_fixture("check_user_subscription_2.json", DOMAIN)
|
||||||
|
)
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
|
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
@ -72,7 +79,7 @@ async def test_oauth_with_sub(
|
|||||||
|
|
||||||
|
|
||||||
async def test_oauth_with_follow(
|
async def test_oauth_with_follow(
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test state with oauth and follow."""
|
"""Test state with oauth and follow."""
|
||||||
await setup_integration(hass, config_entry)
|
await setup_integration(hass, config_entry)
|
||||||
@ -82,40 +89,3 @@ async def test_oauth_with_follow(
|
|||||||
assert sensor_state.attributes["following_since"] == datetime(
|
assert sensor_state.attributes["following_since"] == datetime(
|
||||||
year=2023, month=8, day=1
|
year=2023, month=8, day=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"twitch_mock",
|
|
||||||
[TwitchUnauthorizedMock(), TwitchMissingScopeMock(), TwitchInvalidTokenMock()],
|
|
||||||
)
|
|
||||||
async def test_auth_invalid(
|
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
|
||||||
) -> None:
|
|
||||||
"""Test auth failures."""
|
|
||||||
await setup_integration(hass, config_entry)
|
|
||||||
|
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
|
||||||
assert sensor_state is None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("twitch_mock", [TwitchInvalidUserMock()])
|
|
||||||
async def test_auth_with_invalid_user(
|
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
|
||||||
) -> None:
|
|
||||||
"""Test auth with invalid user."""
|
|
||||||
await setup_integration(hass, config_entry)
|
|
||||||
|
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
|
||||||
assert "subscribed" not in sensor_state.attributes
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("twitch_mock", [TwitchAPIExceptionMock()])
|
|
||||||
async def test_auth_with_api_exception(
|
|
||||||
hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry
|
|
||||||
) -> None:
|
|
||||||
"""Test auth with invalid user."""
|
|
||||||
await setup_integration(hass, config_entry)
|
|
||||||
|
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
|
||||||
assert sensor_state.attributes["subscribed"] is False
|
|
||||||
assert "subscription_is_gifted" not in sensor_state.attributes
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user