Bump TwitchAPI to 3.10.0 (#92418)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
e27554f7a6
commit
c12fae4775
6 changed files with 338 additions and 148 deletions
|
@ -5,5 +5,5 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/twitch",
|
"documentation": "https://www.home-assistant.io/integrations/twitch",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["twitch"],
|
"loggers": ["twitch"],
|
||||||
"requirements": ["twitchAPI==2.5.2"]
|
"requirements": ["twitchAPI==3.10.0"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,17 @@ from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from twitchAPI.helper import first
|
||||||
from twitchAPI.twitch import (
|
from twitchAPI.twitch import (
|
||||||
AuthScope,
|
AuthScope,
|
||||||
AuthType,
|
AuthType,
|
||||||
InvalidTokenException,
|
InvalidTokenException,
|
||||||
MissingScopeException,
|
MissingScopeException,
|
||||||
Twitch,
|
Twitch,
|
||||||
|
TwitchAPIException,
|
||||||
TwitchAuthorizationException,
|
TwitchAuthorizationException,
|
||||||
|
TwitchResourceNotFound,
|
||||||
|
TwitchUser,
|
||||||
)
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
@ -51,10 +55,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Twitch platform."""
|
"""Set up the Twitch platform."""
|
||||||
|
@ -64,7 +68,7 @@ def setup_platform(
|
||||||
oauth_token = config.get(CONF_TOKEN)
|
oauth_token = config.get(CONF_TOKEN)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = Twitch(
|
client = await Twitch(
|
||||||
app_id=client_id,
|
app_id=client_id,
|
||||||
app_secret=client_secret,
|
app_secret=client_secret,
|
||||||
target_app_auth_scope=OAUTH_SCOPES,
|
target_app_auth_scope=OAUTH_SCOPES,
|
||||||
|
@ -76,7 +80,7 @@ def setup_platform(
|
||||||
|
|
||||||
if oauth_token:
|
if oauth_token:
|
||||||
try:
|
try:
|
||||||
client.set_user_authentication(
|
await client.set_user_authentication(
|
||||||
token=oauth_token, scope=OAUTH_SCOPES, validate=True
|
token=oauth_token, scope=OAUTH_SCOPES, validate=True
|
||||||
)
|
)
|
||||||
except MissingScopeException:
|
except MissingScopeException:
|
||||||
|
@ -86,71 +90,45 @@ def setup_platform(
|
||||||
_LOGGER.error("OAuth token is invalid")
|
_LOGGER.error("OAuth token is invalid")
|
||||||
return
|
return
|
||||||
|
|
||||||
channels = client.get_users(logins=channels)
|
twitch_users: list[TwitchUser] = []
|
||||||
|
async for channel in client.get_users(logins=channels):
|
||||||
|
twitch_users.append(channel)
|
||||||
|
|
||||||
add_entities(
|
async_add_entities(
|
||||||
[TwitchSensor(channel, client) for channel in channels["data"]],
|
[TwitchSensor(channel, client) for channel in twitch_users],
|
||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TwitchSensor(SensorEntity):
|
class TwitchSensor(SensorEntity):
|
||||||
"""Representation of an Twitch channel."""
|
"""Representation of a Twitch channel."""
|
||||||
|
|
||||||
_attr_icon = ICON
|
_attr_icon = ICON
|
||||||
|
|
||||||
def __init__(self, channel: dict[str, str], client: Twitch) -> None:
|
def __init__(self, channel: TwitchUser, client: Twitch) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self._client = client
|
self._client = client
|
||||||
|
self._channel = channel
|
||||||
self._enable_user_auth = client.has_required_auth(AuthType.USER, OAUTH_SCOPES)
|
self._enable_user_auth = client.has_required_auth(AuthType.USER, OAUTH_SCOPES)
|
||||||
self._attr_name = channel["display_name"]
|
self._attr_name = channel.display_name
|
||||||
self._attr_unique_id = channel["id"]
|
self._attr_unique_id = channel.id
|
||||||
|
|
||||||
def update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Update device state."""
|
"""Update device state."""
|
||||||
followers = self._client.get_users_follows(to_id=self.unique_id)["total"]
|
followers = (await self._client.get_users_follows(to_id=self._channel.id)).total
|
||||||
channel = self._client.get_users(user_ids=[self.unique_id])["data"][0]
|
|
||||||
self._attr_extra_state_attributes = {
|
self._attr_extra_state_attributes = {
|
||||||
ATTR_FOLLOWING: followers,
|
ATTR_FOLLOWING: followers,
|
||||||
ATTR_VIEWS: channel["view_count"],
|
ATTR_VIEWS: self._channel.view_count,
|
||||||
}
|
}
|
||||||
if self._enable_user_auth:
|
if self._enable_user_auth:
|
||||||
user = self._client.get_users()["data"][0]["id"]
|
await self._async_add_user_attributes()
|
||||||
|
if stream := (
|
||||||
subs = self._client.check_user_subscription(
|
await first(self._client.get_streams(user_id=[self._channel.id], first=1))
|
||||||
user_id=user, broadcaster_id=self.unique_id
|
):
|
||||||
)
|
|
||||||
if "data" in subs:
|
|
||||||
self._attr_extra_state_attributes[ATTR_SUBSCRIPTION] = True
|
|
||||||
self._attr_extra_state_attributes[ATTR_SUBSCRIPTION_GIFTED] = subs[
|
|
||||||
"data"
|
|
||||||
][0]["is_gift"]
|
|
||||||
elif "status" in subs and subs["status"] == 404:
|
|
||||||
self._attr_extra_state_attributes[ATTR_SUBSCRIPTION] = False
|
|
||||||
elif "error" in subs:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Error response on check_user_subscription: %s", subs["error"]
|
|
||||||
)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
_LOGGER.error("Unknown error response on check_user_subscription")
|
|
||||||
return
|
|
||||||
|
|
||||||
follows = self._client.get_users_follows(
|
|
||||||
from_id=user, to_id=self.unique_id
|
|
||||||
)["data"]
|
|
||||||
self._attr_extra_state_attributes[ATTR_FOLLOW] = len(follows) > 0
|
|
||||||
if len(follows):
|
|
||||||
self._attr_extra_state_attributes[ATTR_FOLLOW_SINCE] = follows[0][
|
|
||||||
"followed_at"
|
|
||||||
]
|
|
||||||
|
|
||||||
if streams := self._client.get_streams(user_id=[self.unique_id])["data"]:
|
|
||||||
stream = streams[0]
|
|
||||||
self._attr_native_value = STATE_STREAMING
|
self._attr_native_value = STATE_STREAMING
|
||||||
self._attr_extra_state_attributes[ATTR_GAME] = stream["game_name"]
|
self._attr_extra_state_attributes[ATTR_GAME] = stream.game_name
|
||||||
self._attr_extra_state_attributes[ATTR_TITLE] = stream["title"]
|
self._attr_extra_state_attributes[ATTR_TITLE] = stream.title
|
||||||
self._attr_entity_picture = stream["thumbnail_url"]
|
self._attr_entity_picture = stream.thumbnail_url
|
||||||
if self._attr_entity_picture is not None:
|
if self._attr_entity_picture is not None:
|
||||||
self._attr_entity_picture = self._attr_entity_picture.format(
|
self._attr_entity_picture = self._attr_entity_picture.format(
|
||||||
height=24,
|
height=24,
|
||||||
|
@ -160,4 +138,30 @@ class TwitchSensor(SensorEntity):
|
||||||
self._attr_native_value = STATE_OFFLINE
|
self._attr_native_value = STATE_OFFLINE
|
||||||
self._attr_extra_state_attributes[ATTR_GAME] = None
|
self._attr_extra_state_attributes[ATTR_GAME] = None
|
||||||
self._attr_extra_state_attributes[ATTR_TITLE] = None
|
self._attr_extra_state_attributes[ATTR_TITLE] = None
|
||||||
self._attr_entity_picture = channel["profile_image_url"]
|
self._attr_entity_picture = self._channel.profile_image_url
|
||||||
|
|
||||||
|
async def _async_add_user_attributes(self) -> None:
|
||||||
|
if not (user := await first(self._client.get_users())):
|
||||||
|
return
|
||||||
|
self._attr_extra_state_attributes[ATTR_SUBSCRIPTION] = False
|
||||||
|
try:
|
||||||
|
sub = await self._client.check_user_subscription(
|
||||||
|
user_id=user.id, broadcaster_id=self._channel.id
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes[ATTR_SUBSCRIPTION] = True
|
||||||
|
self._attr_extra_state_attributes[ATTR_SUBSCRIPTION_GIFTED] = sub.is_gift
|
||||||
|
except TwitchResourceNotFound:
|
||||||
|
_LOGGER.debug("User is not subscribed")
|
||||||
|
except TwitchAPIException as exc:
|
||||||
|
_LOGGER.error("Error response on check_user_subscription: %s", exc)
|
||||||
|
|
||||||
|
follows = (
|
||||||
|
await self._client.get_users_follows(
|
||||||
|
from_id=user.id, to_id=self._channel.id
|
||||||
|
)
|
||||||
|
).data
|
||||||
|
self._attr_extra_state_attributes[ATTR_FOLLOW] = len(follows) > 0
|
||||||
|
if len(follows):
|
||||||
|
self._attr_extra_state_attributes[ATTR_FOLLOW_SINCE] = follows[
|
||||||
|
0
|
||||||
|
].followed_at
|
||||||
|
|
|
@ -2556,7 +2556,7 @@ twentemilieu==1.0.0
|
||||||
twilio==6.32.0
|
twilio==6.32.0
|
||||||
|
|
||||||
# homeassistant.components.twitch
|
# homeassistant.components.twitch
|
||||||
twitchAPI==2.5.2
|
twitchAPI==3.10.0
|
||||||
|
|
||||||
# homeassistant.components.ukraine_alarm
|
# homeassistant.components.ukraine_alarm
|
||||||
uasiren==0.0.1
|
uasiren==0.0.1
|
||||||
|
|
|
@ -1847,7 +1847,7 @@ twentemilieu==1.0.0
|
||||||
twilio==6.32.0
|
twilio==6.32.0
|
||||||
|
|
||||||
# homeassistant.components.twitch
|
# homeassistant.components.twitch
|
||||||
twitchAPI==2.5.2
|
twitchAPI==3.10.0
|
||||||
|
|
||||||
# homeassistant.components.ukraine_alarm
|
# homeassistant.components.ukraine_alarm
|
||||||
uasiren==0.0.1
|
uasiren==0.0.1
|
||||||
|
|
|
@ -1 +1,190 @@
|
||||||
"""Tests for the Twitch component."""
|
"""Tests for the Twitch component."""
|
||||||
|
import asyncio
|
||||||
|
from collections.abc import AsyncGenerator
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from twitchAPI.object import TwitchUser
|
||||||
|
from twitchAPI.twitch import (
|
||||||
|
InvalidTokenException,
|
||||||
|
MissingScopeException,
|
||||||
|
TwitchAPIException,
|
||||||
|
TwitchAuthorizationException,
|
||||||
|
TwitchResourceNotFound,
|
||||||
|
)
|
||||||
|
from twitchAPI.types import AuthScope, AuthType
|
||||||
|
|
||||||
|
USER_OBJECT: TwitchUser = TwitchUser(
|
||||||
|
id=123,
|
||||||
|
display_name="channel123",
|
||||||
|
offline_image_url="logo.png",
|
||||||
|
profile_image_url="logo.png",
|
||||||
|
view_count=42,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchUserFollowResultMock:
|
||||||
|
"""Mock for twitch user follow result."""
|
||||||
|
|
||||||
|
def __init__(self, follows: list[dict[str, Any]]) -> None:
|
||||||
|
"""Initialize mock."""
|
||||||
|
self.total = len(follows)
|
||||||
|
self.data = follows
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UserSubscriptionMock:
|
||||||
|
"""User subscription mock."""
|
||||||
|
|
||||||
|
broadcaster_id: str
|
||||||
|
is_gift: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UserFollowMock:
|
||||||
|
"""User follow mock."""
|
||||||
|
|
||||||
|
followed_at: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StreamMock:
|
||||||
|
"""Stream mock."""
|
||||||
|
|
||||||
|
game_name: str
|
||||||
|
title: str
|
||||||
|
thumbnail_url: str
|
||||||
|
|
||||||
|
|
||||||
|
STREAMS = StreamMock(
|
||||||
|
game_name="Good game", title="Title", thumbnail_url="stream-medium.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TwitchMock:
|
||||||
|
"""Mock for the twitch object."""
|
||||||
|
|
||||||
|
def __await__(self):
|
||||||
|
"""Add async capabilities to the mock."""
|
||||||
|
t = asyncio.create_task(self._noop())
|
||||||
|
yield from t
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
is_streaming: bool = True,
|
||||||
|
is_gifted: bool = False,
|
||||||
|
is_subscribed: bool = False,
|
||||||
|
is_following: bool = True,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize mock."""
|
||||||
|
self._is_streaming = is_streaming
|
||||||
|
self._is_gifted = is_gifted
|
||||||
|
self._is_subscribed = is_subscribed
|
||||||
|
self._is_following = is_following
|
||||||
|
|
||||||
|
async def _noop(self):
|
||||||
|
"""Fake function to create task."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_users(
|
||||||
|
self, user_ids: Optional[list[str]] = None, logins: Optional[list[str]] = None
|
||||||
|
) -> AsyncGenerator[TwitchUser, None]:
|
||||||
|
"""Get list of mock users."""
|
||||||
|
for user in [USER_OBJECT]:
|
||||||
|
yield user
|
||||||
|
|
||||||
|
def has_required_auth(
|
||||||
|
self, required_type: AuthType, required_scope: list[AuthScope]
|
||||||
|
) -> bool:
|
||||||
|
"""Return if auth required."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def get_users_follows(
|
||||||
|
self, to_id: Optional[str] = None, from_id: Optional[str] = None
|
||||||
|
) -> TwitchUserFollowResultMock:
|
||||||
|
"""Return the followers of the user."""
|
||||||
|
if self._is_following:
|
||||||
|
return TwitchUserFollowResultMock(
|
||||||
|
follows=[UserFollowMock("2020-01-20T21:22:42") for _ in range(0, 24)]
|
||||||
|
)
|
||||||
|
return TwitchUserFollowResultMock(follows=[])
|
||||||
|
|
||||||
|
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], validate: bool = True
|
||||||
|
) -> None:
|
||||||
|
"""Set user authentication."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
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: Optional[list[str]] = None, logins: Optional[list[str]] = 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,11 +1,20 @@
|
||||||
"""The tests for an update of the Twitch component."""
|
"""The tests for an update of the Twitch component."""
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant.components import sensor
|
from homeassistant.components import sensor
|
||||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||||
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 . import (
|
||||||
|
TwitchAPIExceptionMock,
|
||||||
|
TwitchInvalidTokenMock,
|
||||||
|
TwitchInvalidUserMock,
|
||||||
|
TwitchMissingScopeMock,
|
||||||
|
TwitchMock,
|
||||||
|
TwitchUnauthorizedMock,
|
||||||
|
)
|
||||||
|
|
||||||
ENTITY_ID = "sensor.channel123"
|
ENTITY_ID = "sensor.channel123"
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
sensor.DOMAIN: {
|
sensor.DOMAIN: {
|
||||||
|
@ -25,41 +34,13 @@ CONFIG_WITH_OAUTH = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
USER_OBJECT = {
|
|
||||||
"id": 123,
|
|
||||||
"display_name": "channel123",
|
|
||||||
"offline_image_url": "logo.png",
|
|
||||||
"profile_image_url": "logo.png",
|
|
||||||
"view_count": 42,
|
|
||||||
}
|
|
||||||
STREAM_OBJECT_ONLINE = {
|
|
||||||
"game_name": "Good Game",
|
|
||||||
"title": "Title",
|
|
||||||
"thumbnail_url": "stream-medium.png",
|
|
||||||
}
|
|
||||||
|
|
||||||
FOLLOWERS_OBJECT = [{"followed_at": "2020-01-20T21:22:42"}] * 24
|
|
||||||
OAUTH_USER_ID = {"id": 987}
|
|
||||||
SUB_ACTIVE = {"is_gift": False}
|
|
||||||
FOLLOW_ACTIVE = {"followed_at": "2020-01-20T21:22:42"}
|
|
||||||
|
|
||||||
|
|
||||||
def make_data(data):
|
|
||||||
"""Create a data object."""
|
|
||||||
return {"data": data, "total": len(data)}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_init(hass: HomeAssistant) -> None:
|
async def test_init(hass: HomeAssistant) -> None:
|
||||||
"""Test initial config."""
|
"""Test initial config."""
|
||||||
|
|
||||||
twitch_mock = MagicMock()
|
|
||||||
twitch_mock.get_streams.return_value = make_data([])
|
|
||||||
twitch_mock.get_users.return_value = make_data([USER_OBJECT])
|
|
||||||
twitch_mock.get_users_follows.return_value = make_data(FOLLOWERS_OBJECT)
|
|
||||||
twitch_mock.has_required_auth.return_value = False
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.twitch.sensor.Twitch", return_value=twitch_mock
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
|
return_value=TwitchMock(is_streaming=False),
|
||||||
):
|
):
|
||||||
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -76,15 +57,9 @@ async def test_init(hass: HomeAssistant) -> None:
|
||||||
async def test_offline(hass: HomeAssistant) -> None:
|
async def test_offline(hass: HomeAssistant) -> None:
|
||||||
"""Test offline state."""
|
"""Test offline state."""
|
||||||
|
|
||||||
twitch_mock = MagicMock()
|
|
||||||
twitch_mock.get_streams.return_value = make_data([])
|
|
||||||
twitch_mock.get_users.return_value = make_data([USER_OBJECT])
|
|
||||||
twitch_mock.get_users_follows.return_value = make_data(FOLLOWERS_OBJECT)
|
|
||||||
twitch_mock.has_required_auth.return_value = False
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.twitch.sensor.Twitch",
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
return_value=twitch_mock,
|
return_value=TwitchMock(is_streaming=False),
|
||||||
):
|
):
|
||||||
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -97,15 +72,9 @@ async def test_offline(hass: HomeAssistant) -> None:
|
||||||
async def test_streaming(hass: HomeAssistant) -> None:
|
async def test_streaming(hass: HomeAssistant) -> None:
|
||||||
"""Test streaming state."""
|
"""Test streaming state."""
|
||||||
|
|
||||||
twitch_mock = MagicMock()
|
|
||||||
twitch_mock.get_users.return_value = make_data([USER_OBJECT])
|
|
||||||
twitch_mock.get_users_follows.return_value = make_data(FOLLOWERS_OBJECT)
|
|
||||||
twitch_mock.get_streams.return_value = make_data([STREAM_OBJECT_ONLINE])
|
|
||||||
twitch_mock.has_required_auth.return_value = False
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.twitch.sensor.Twitch",
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
return_value=twitch_mock,
|
return_value=TwitchMock(),
|
||||||
):
|
):
|
||||||
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -113,30 +82,16 @@ async def test_streaming(hass: HomeAssistant) -> None:
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
assert sensor_state.state == "streaming"
|
assert sensor_state.state == "streaming"
|
||||||
assert sensor_state.attributes["entity_picture"] == "stream-medium.png"
|
assert sensor_state.attributes["entity_picture"] == "stream-medium.png"
|
||||||
assert sensor_state.attributes["game"] == "Good Game"
|
assert sensor_state.attributes["game"] == "Good game"
|
||||||
assert sensor_state.attributes["title"] == "Title"
|
assert sensor_state.attributes["title"] == "Title"
|
||||||
|
|
||||||
|
|
||||||
async def test_oauth_without_sub_and_follow(hass: HomeAssistant) -> None:
|
async def test_oauth_without_sub_and_follow(hass: HomeAssistant) -> None:
|
||||||
"""Test state with oauth."""
|
"""Test state with oauth."""
|
||||||
|
|
||||||
twitch_mock = MagicMock()
|
|
||||||
twitch_mock.get_streams.return_value = make_data([])
|
|
||||||
twitch_mock.get_users.side_effect = [
|
|
||||||
make_data([USER_OBJECT]),
|
|
||||||
make_data([USER_OBJECT]),
|
|
||||||
make_data([OAUTH_USER_ID]),
|
|
||||||
]
|
|
||||||
twitch_mock.get_users_follows.side_effect = [
|
|
||||||
make_data(FOLLOWERS_OBJECT),
|
|
||||||
make_data([]),
|
|
||||||
]
|
|
||||||
twitch_mock.has_required_auth.return_value = True
|
|
||||||
twitch_mock.check_user_subscription.return_value = {"status": 404}
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.twitch.sensor.Twitch",
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
return_value=twitch_mock,
|
return_value=TwitchMock(is_following=False),
|
||||||
):
|
):
|
||||||
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -149,25 +104,11 @@ async def test_oauth_without_sub_and_follow(hass: HomeAssistant) -> None:
|
||||||
async def test_oauth_with_sub(hass: HomeAssistant) -> None:
|
async def test_oauth_with_sub(hass: HomeAssistant) -> None:
|
||||||
"""Test state with oauth and sub."""
|
"""Test state with oauth and sub."""
|
||||||
|
|
||||||
twitch_mock = MagicMock()
|
|
||||||
twitch_mock.get_streams.return_value = make_data([])
|
|
||||||
twitch_mock.get_users.side_effect = [
|
|
||||||
make_data([USER_OBJECT]),
|
|
||||||
make_data([USER_OBJECT]),
|
|
||||||
make_data([OAUTH_USER_ID]),
|
|
||||||
]
|
|
||||||
twitch_mock.get_users_follows.side_effect = [
|
|
||||||
make_data(FOLLOWERS_OBJECT),
|
|
||||||
make_data([]),
|
|
||||||
]
|
|
||||||
twitch_mock.has_required_auth.return_value = True
|
|
||||||
|
|
||||||
# This function does not return an array so use make_data
|
|
||||||
twitch_mock.check_user_subscription.return_value = make_data([SUB_ACTIVE])
|
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.twitch.sensor.Twitch",
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
return_value=twitch_mock,
|
return_value=TwitchMock(
|
||||||
|
is_subscribed=True, is_gifted=False, is_following=False
|
||||||
|
),
|
||||||
):
|
):
|
||||||
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -181,28 +122,84 @@ async def test_oauth_with_sub(hass: HomeAssistant) -> None:
|
||||||
async def test_oauth_with_follow(hass: HomeAssistant) -> None:
|
async def test_oauth_with_follow(hass: HomeAssistant) -> None:
|
||||||
"""Test state with oauth and follow."""
|
"""Test state with oauth and follow."""
|
||||||
|
|
||||||
twitch_mock = MagicMock()
|
with patch(
|
||||||
twitch_mock.get_streams.return_value = make_data([])
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
twitch_mock.get_users.side_effect = [
|
return_value=TwitchMock(),
|
||||||
make_data([USER_OBJECT]),
|
):
|
||||||
make_data([USER_OBJECT]),
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
make_data([OAUTH_USER_ID]),
|
await hass.async_block_till_done()
|
||||||
]
|
|
||||||
twitch_mock.get_users_follows.side_effect = [
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
make_data(FOLLOWERS_OBJECT),
|
assert sensor_state.attributes["following"] is True
|
||||||
make_data([FOLLOW_ACTIVE]),
|
assert sensor_state.attributes["following_since"] == "2020-01-20T21:22:42"
|
||||||
]
|
|
||||||
twitch_mock.has_required_auth.return_value = True
|
|
||||||
twitch_mock.check_user_subscription.return_value = {"status": 404}
|
async def test_auth_with_invalid_credentials(hass: HomeAssistant) -> None:
|
||||||
|
"""Test auth with invalid credentials."""
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.twitch.sensor.Twitch",
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
return_value=twitch_mock,
|
return_value=TwitchUnauthorizedMock(),
|
||||||
|
):
|
||||||
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
|
assert sensor_state is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_auth_with_missing_scope(hass: HomeAssistant) -> None:
|
||||||
|
"""Test auth with invalid credentials."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
|
return_value=TwitchMissingScopeMock(),
|
||||||
|
):
|
||||||
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
|
assert sensor_state is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_auth_with_invalid_token(hass: HomeAssistant) -> None:
|
||||||
|
"""Test auth with invalid credentials."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
|
return_value=TwitchInvalidTokenMock(),
|
||||||
|
):
|
||||||
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
|
assert sensor_state is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_auth_with_invalid_user(hass: HomeAssistant) -> None:
|
||||||
|
"""Test auth with invalid user."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
|
return_value=TwitchInvalidUserMock(),
|
||||||
|
):
|
||||||
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
|
assert "subscribed" not in sensor_state.attributes
|
||||||
|
|
||||||
|
|
||||||
|
async def test_auth_with_api_exception(hass: HomeAssistant) -> None:
|
||||||
|
"""Test auth with invalid user."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.twitch.sensor.Twitch",
|
||||||
|
return_value=TwitchAPIExceptionMock(),
|
||||||
):
|
):
|
||||||
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
sensor_state = hass.states.get(ENTITY_ID)
|
sensor_state = hass.states.get(ENTITY_ID)
|
||||||
assert sensor_state.attributes["subscribed"] is False
|
assert sensor_state.attributes["subscribed"] is False
|
||||||
assert sensor_state.attributes["following"] is True
|
assert "subscription_is_gifted" not in sensor_state.attributes
|
||||||
assert sensor_state.attributes["following_since"] == "2020-01-20T21:22:42"
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue