diff --git a/homeassistant/components/twitch/__init__.py b/homeassistant/components/twitch/__init__.py index 60c9dcabb36..40a744684b9 100644 --- a/homeassistant/components/twitch/__init__.py +++ b/homeassistant/components/twitch/__init__.py @@ -39,7 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady from err access_token = entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN] - client = await Twitch( + client = Twitch( app_id=implementation.client_id, authenticate_app=False, ) diff --git a/homeassistant/components/twitch/config_flow.py b/homeassistant/components/twitch/config_flow.py index 146d2f39088..7f006f194f5 100644 --- a/homeassistant/components/twitch/config_flow.py +++ b/homeassistant/components/twitch/config_flow.py @@ -50,7 +50,7 @@ class OAuth2FlowHandler( self.flow_impl, ) - client = await Twitch( + client = Twitch( app_id=implementation.client_id, authenticate_app=False, ) diff --git a/tests/components/twitch/__init__.py b/tests/components/twitch/__init__.py index d37c386f0a3..3a6643392f1 100644 --- a/tests/components/twitch/__init__.py +++ b/tests/components/twitch/__init__.py @@ -1,246 +1,55 @@ """Tests for the Twitch component.""" -import asyncio from collections.abc import AsyncGenerator, AsyncIterator -from dataclasses import dataclass -from datetime import datetime +from typing import Any, Generic, TypeVar -from twitchAPI.object.api import FollowedChannelsResult, TwitchUser -from twitchAPI.twitch import ( - InvalidTokenException, - MissingScopeException, - TwitchAPIException, - TwitchAuthorizationException, - TwitchResourceNotFound, -) -from twitchAPI.type import AuthScope, AuthType +from twitchAPI.object.base import TwitchObject +from homeassistant.components.twitch import DOMAIN 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: """Fixture for setting up the component.""" 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: - return TwitchUser( - id=user_id, - display_name="channel123", - offline_image_url="logo.png", - profile_image_url="logo.png", - view_count=42, - ) +TwitchType = TypeVar("TwitchType", bound=TwitchObject) -async def async_iterator(iterable) -> AsyncIterator: - """Return async iterator.""" - for i in iterable: - yield i +class TwitchIterObject(Generic[TwitchType]): + """Twitch object iterator.""" + 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 -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): + async def __aiter__(self) -> AsyncIterator[TwitchType]: """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: - """Mock for twitch channel follow result.""" - - def __init__(self, follows: list[ChannelFollowerMock]) -> None: - """Initialize mock.""" - self.total = len(follows) - self.data = follows - - def __aiter__(self): - """Return async iterator.""" - return async_iterator(self.data) +async def get_generator( + fixture: str, target_type: type[TwitchType] +) -> AsyncGenerator[TwitchType, None]: + """Return async generator.""" + data = load_json_array_fixture(fixture, DOMAIN) + async for item in get_generator_from_data(data, target_type): + yield item -STREAMS = StreamMock( - game_name="Good game", title="Title", thumbnail_url="stream-medium.png" -) - - -class TwitchMock: - """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 +async def get_generator_from_data( + items: list[dict[str, Any]], target_type: type[TwitchType] +) -> AsyncGenerator[TwitchType, None]: + """Return async generator.""" + for item in items: + yield target_type(**item) diff --git a/tests/components/twitch/conftest.py b/tests/components/twitch/conftest.py index e950bb16c5e..054b4b38a7c 100644 --- a/tests/components/twitch/conftest.py +++ b/tests/components/twitch/conftest.py @@ -1,10 +1,11 @@ """Configure tests for the Twitch integration.""" -from collections.abc import Awaitable, Callable, Generator +from collections.abc import Generator import time from unittest.mock import AsyncMock, patch import pytest +from twitchAPI.object.api import FollowedChannel, Stream, TwitchUser, UserSubscription from homeassistant.components.application_credentials import ( ClientCredential, @@ -14,11 +15,10 @@ from homeassistant.components.twitch.const import DOMAIN, OAUTH2_TOKEN, OAUTH_SC from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry -from tests.components.twitch import TwitchMock -from tests.test_util.aiohttp import AiohttpClientMocker +from . import TwitchIterObject, get_generator -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_SECRET = "5678" @@ -92,23 +92,32 @@ def mock_connection(aioclient_mock: AiohttpClientMocker) -> None: ) -@pytest.fixture(name="twitch_mock") -def twitch_mock() -> TwitchMock: +@pytest.fixture +def twitch_mock() -> Generator[AsyncMock, None, None]: """Return as fixture to inject other mocks.""" - return TwitchMock() - - -@pytest.fixture(name="twitch") -def mock_twitch(twitch_mock: TwitchMock): - """Mock Twitch.""" with ( patch( "homeassistant.components.twitch.Twitch", - return_value=twitch_mock, - ), + autospec=True, + ) as mock_client, patch( "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 diff --git a/tests/components/twitch/fixtures/check_user_subscription.json b/tests/components/twitch/fixtures/check_user_subscription.json new file mode 100644 index 00000000000..b1b2a3d852a --- /dev/null +++ b/tests/components/twitch/fixtures/check_user_subscription.json @@ -0,0 +1,3 @@ +{ + "is_gift": true +} diff --git a/tests/components/twitch/fixtures/check_user_subscription_2.json b/tests/components/twitch/fixtures/check_user_subscription_2.json new file mode 100644 index 00000000000..94d56c5ee12 --- /dev/null +++ b/tests/components/twitch/fixtures/check_user_subscription_2.json @@ -0,0 +1,3 @@ +{ + "is_gift": false +} diff --git a/tests/components/twitch/fixtures/empty_response.json b/tests/components/twitch/fixtures/empty_response.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/tests/components/twitch/fixtures/empty_response.json @@ -0,0 +1 @@ +[] diff --git a/tests/components/twitch/fixtures/get_followed_channels.json b/tests/components/twitch/fixtures/get_followed_channels.json new file mode 100644 index 00000000000..4add7cc0a98 --- /dev/null +++ b/tests/components/twitch/fixtures/get_followed_channels.json @@ -0,0 +1,10 @@ +[ + { + "broadcaster_login": "internetofthings", + "followed_at": "2023-08-01" + }, + { + "broadcaster_login": "homeassistant", + "followed_at": "2023-08-01" + } +] diff --git a/tests/components/twitch/fixtures/get_streams.json b/tests/components/twitch/fixtures/get_streams.json new file mode 100644 index 00000000000..3714d97aaef --- /dev/null +++ b/tests/components/twitch/fixtures/get_streams.json @@ -0,0 +1,7 @@ +[ + { + "game_name": "Good game", + "title": "Title", + "thumbnail_url": "stream-medium.png" + } +] diff --git a/tests/components/twitch/fixtures/get_users.json b/tests/components/twitch/fixtures/get_users.json new file mode 100644 index 00000000000..b5262eb282e --- /dev/null +++ b/tests/components/twitch/fixtures/get_users.json @@ -0,0 +1,9 @@ +[ + { + "id": 123, + "display_name": "channel123", + "offline_image_url": "logo.png", + "profile_image_url": "logo.png", + "view_count": 42 + } +] diff --git a/tests/components/twitch/fixtures/get_users_2.json b/tests/components/twitch/fixtures/get_users_2.json new file mode 100644 index 00000000000..11ed194213a --- /dev/null +++ b/tests/components/twitch/fixtures/get_users_2.json @@ -0,0 +1,9 @@ +[ + { + "id": 456, + "display_name": "channel123", + "offline_image_url": "logo.png", + "profile_image_url": "logo.png", + "view_count": 42 + } +] diff --git a/tests/components/twitch/test_config_flow.py b/tests/components/twitch/test_config_flow.py index 94fa2ce0427..7807cd38e1a 100644 --- a/tests/components/twitch/test_config_flow.py +++ b/tests/components/twitch/test_config_flow.py @@ -1,6 +1,8 @@ """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 ( CONF_CHANNELS, @@ -12,10 +14,9 @@ from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult, FlowResultType 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.components.twitch import TwitchMock from tests.components.twitch.conftest import CLIENT_ID, TITLE from tests.typing import ClientSessionGenerator @@ -51,7 +52,7 @@ async def test_full_flow( hass_client_no_auth: ClientSessionGenerator, current_request_with_host: None, mock_setup_entry, - twitch: TwitchMock, + twitch_mock: AsyncMock, scopes: list[str], ) -> None: """Check full flow.""" @@ -80,7 +81,7 @@ async def test_already_configured( current_request_with_host: None, config_entry: MockConfigEntry, mock_setup_entry, - twitch: TwitchMock, + twitch_mock: AsyncMock, scopes: list[str], ) -> None: """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) - with patch( - "homeassistant.components.twitch.config_flow.Twitch", return_value=TwitchMock() - ): - result = await hass.config_entries.flow.async_configure(result["flow_id"]) + result = await hass.config_entries.flow.async_configure(result["flow_id"]) - assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "already_configured" + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" async def test_reauth( @@ -105,7 +103,7 @@ async def test_reauth( current_request_with_host: None, config_entry: MockConfigEntry, mock_setup_entry, - twitch: TwitchMock, + twitch_mock: AsyncMock, scopes: list[str], ) -> None: """Check reauth flow.""" @@ -136,7 +134,7 @@ async def test_reauth_from_import( hass_client_no_auth: ClientSessionGenerator, current_request_with_host: None, mock_setup_entry, - twitch: TwitchMock, + twitch_mock: AsyncMock, expires_at, scopes: list[str], ) -> None: @@ -163,7 +161,7 @@ async def test_reauth_from_import( current_request_with_host, config_entry, mock_setup_entry, - twitch, + twitch_mock, scopes, ) entries = hass.config_entries.async_entries(DOMAIN) @@ -178,12 +176,14 @@ async def test_reauth_wrong_account( current_request_with_host: None, config_entry: MockConfigEntry, mock_setup_entry, - twitch: TwitchMock, + twitch_mock: AsyncMock, scopes: list[str], ) -> None: """Check reauth flow.""" 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( DOMAIN, context={ diff --git a/tests/components/twitch/test_init.py b/tests/components/twitch/test_init.py index d3b9313c46e..6261c69bf7d 100644 --- a/tests/components/twitch/test_init.py +++ b/tests/components/twitch/test_init.py @@ -1,8 +1,8 @@ -"""Tests for YouTube.""" +"""Tests for Twitch.""" import http import time -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from aiohttp.client_exceptions import ClientError import pytest @@ -11,14 +11,14 @@ from homeassistant.components.twitch.const import DOMAIN, OAUTH2_TOKEN from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant -from . import TwitchMock, setup_integration +from . import setup_integration from tests.common import MockConfigEntry from tests.test_util.aiohttp import AiohttpClientMocker async def test_setup_success( - hass: HomeAssistant, config_entry: MockConfigEntry, twitch: TwitchMock + hass: HomeAssistant, config_entry: MockConfigEntry, twitch_mock: AsyncMock ) -> None: """Test successful setup and unload.""" await setup_integration(hass, config_entry) @@ -38,7 +38,7 @@ async def test_expired_token_refresh_success( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, config_entry: MockConfigEntry, - twitch: TwitchMock, + twitch_mock: AsyncMock, ) -> None: """Test expired token is refreshed.""" @@ -84,7 +84,7 @@ async def test_expired_token_refresh_failure( status: http.HTTPStatus, expected_state: ConfigEntryState, config_entry: MockConfigEntry, - twitch: TwitchMock, + twitch_mock: AsyncMock, ) -> None: """Test failure while refreshing token with a transient error.""" @@ -93,8 +93,10 @@ async def test_expired_token_refresh_failure( OAUTH2_TOKEN, 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 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( - hass: HomeAssistant, config_entry: MockConfigEntry, twitch: TwitchMock + hass: HomeAssistant, config_entry: MockConfigEntry, twitch_mock: AsyncMock ) -> None: """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", 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 entries = hass.config_entries.async_entries(DOMAIN) diff --git a/tests/components/twitch/test_sensor.py b/tests/components/twitch/test_sensor.py index bb6624f7847..e5cddf8e192 100644 --- a/tests/components/twitch/test_sensor.py +++ b/tests/components/twitch/test_sensor.py @@ -1,30 +1,28 @@ """The tests for an update of the Twitch component.""" 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 ...common import MockConfigEntry -from . import ( - TwitchAPIExceptionMock, - TwitchInvalidTokenMock, - TwitchInvalidUserMock, - TwitchMissingScopeMock, - TwitchMock, - TwitchUnauthorizedMock, - setup_integration, -) +from . import TwitchIterObject, get_generator_from_data, setup_integration + +from tests.common import MockConfigEntry, load_json_object_fixture ENTITY_ID = "sensor.channel123" async def test_offline( - hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry + hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry ) -> None: """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) sensor_state = hass.states.get(ENTITY_ID) @@ -33,7 +31,7 @@ async def test_offline( async def test_streaming( - hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry + hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry ) -> None: """Test streaming state.""" await setup_integration(hass, config_entry) @@ -46,10 +44,15 @@ async def test_streaming( async def test_oauth_without_sub_and_follow( - hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry + hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry ) -> None: """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) 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( - hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry + hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry ) -> None: """Test state with oauth and sub.""" - twitch.is_subscribed = True - 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.return_value = UserSubscription( + **load_json_object_fixture("check_user_subscription_2.json", DOMAIN) + ) await setup_integration(hass, config_entry) sensor_state = hass.states.get(ENTITY_ID) @@ -72,7 +79,7 @@ async def test_oauth_with_sub( async def test_oauth_with_follow( - hass: HomeAssistant, twitch: TwitchMock, config_entry: MockConfigEntry + hass: HomeAssistant, twitch_mock: AsyncMock, config_entry: MockConfigEntry ) -> None: """Test state with oauth and follow.""" await setup_integration(hass, config_entry) @@ -82,40 +89,3 @@ async def test_oauth_with_follow( assert sensor_state.attributes["following_since"] == datetime( 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