mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
2024.8.3 (#124569)
This commit is contained in:
commit
516f3295bf
@ -92,7 +92,9 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
except AirGradientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(current_measures.serial_number)
|
||||
await self.async_set_unique_id(
|
||||
current_measures.serial_number, raise_on_progress=False
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
await self.set_configuration_source()
|
||||
return self.async_create_entry(
|
||||
|
@ -661,9 +661,12 @@ class RemoteCapabilities(AlexaEntity):
|
||||
def interfaces(self) -> Generator[AlexaCapability]:
|
||||
"""Yield the supported interfaces."""
|
||||
yield AlexaPowerController(self.entity)
|
||||
yield AlexaModeController(
|
||||
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
|
||||
)
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
activities = self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or []
|
||||
if activities and supported & remote.RemoteEntityFeature.ACTIVITY:
|
||||
yield AlexaModeController(
|
||||
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
|
||||
)
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
yield Alexa(self.entity)
|
||||
|
||||
|
@ -8,8 +8,8 @@ from typing import Any
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
from yalexs.authenticator import ValidationResult
|
||||
from yalexs.const import BRANDS, DEFAULT_BRAND
|
||||
from yalexs.authenticator_common import ValidationResult
|
||||
from yalexs.const import BRANDS_WITHOUT_OAUTH, DEFAULT_BRAND
|
||||
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
@ -118,7 +118,7 @@ class AugustConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
vol.Required(
|
||||
CONF_BRAND,
|
||||
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
|
||||
): vol.In(BRANDS),
|
||||
): vol.In(BRANDS_WITHOUT_OAUTH),
|
||||
vol.Required(
|
||||
CONF_LOGIN_METHOD,
|
||||
default=self._user_auth_details.get(
|
||||
@ -208,7 +208,7 @@ class AugustConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
vol.Required(
|
||||
CONF_BRAND,
|
||||
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
|
||||
): vol.In(BRANDS),
|
||||
): vol.In(BRANDS_WITHOUT_OAUTH),
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
),
|
||||
|
@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==6.4.3", "yalexs-ble==2.4.3"]
|
||||
"requirements": ["yalexs==8.4.1", "yalexs-ble==2.4.3"]
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pybravia"],
|
||||
"requirements": ["pybravia==0.3.3"],
|
||||
"requirements": ["pybravia==0.3.4"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "urn:schemas-sony-com:service:ScalarWebAPI:1",
|
||||
|
@ -49,7 +49,14 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
|
||||
try:
|
||||
user_response = await self.api.user.get()
|
||||
tasks_response = await self.api.tasks.user.get()
|
||||
tasks_response.extend(await self.api.tasks.user.get(type="completedTodos"))
|
||||
tasks_response.extend(
|
||||
[
|
||||
{"id": task["_id"], **task}
|
||||
for task in await self.api.tasks.user.get(type="completedTodos")
|
||||
if task.get("_id")
|
||||
]
|
||||
)
|
||||
|
||||
except ClientResponseError as error:
|
||||
raise UpdateFailed(f"Error communicating with API: {error}") from error
|
||||
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.53", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.55", "babel==2.15.0"]
|
||||
}
|
||||
|
@ -14,6 +14,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiohomekit", "commentjson"],
|
||||
"requirements": ["aiohomekit==3.2.2"],
|
||||
"requirements": ["aiohomekit==3.2.3"],
|
||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/homeworks",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyhomeworks"],
|
||||
"requirements": ["pyhomeworks==1.1.1"]
|
||||
"requirements": ["pyhomeworks==1.1.2"]
|
||||
}
|
||||
|
@ -11,6 +11,6 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiohue"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiohue==4.7.2"],
|
||||
"requirements": ["aiohue==4.7.3"],
|
||||
"zeroconf": ["_hue._tcp.local."]
|
||||
}
|
||||
|
@ -80,9 +80,9 @@ async def async_setup_hue_events(bridge: HueBridge):
|
||||
CONF_DEVICE_ID: device.id, # type: ignore[union-attr]
|
||||
CONF_UNIQUE_ID: hue_resource.id,
|
||||
CONF_TYPE: hue_resource.relative_rotary.rotary_report.action.value,
|
||||
CONF_SUBTYPE: hue_resource.relative_rotary.last_event.rotation.direction.value,
|
||||
CONF_DURATION: hue_resource.relative_rotary.last_event.rotation.duration,
|
||||
CONF_STEPS: hue_resource.relative_rotary.last_event.rotation.steps,
|
||||
CONF_SUBTYPE: hue_resource.relative_rotary.rotary_report.rotation.direction.value,
|
||||
CONF_DURATION: hue_resource.relative_rotary.rotary_report.rotation.duration,
|
||||
CONF_STEPS: hue_resource.relative_rotary.rotary_report.rotation.steps,
|
||||
}
|
||||
hass.bus.async_fire(ATTR_HUE_EVENT, data)
|
||||
|
||||
|
@ -31,12 +31,14 @@
|
||||
"round": "[%key:component::integration::config::step::user::data::round%]",
|
||||
"source": "[%key:component::integration::config::step::user::data::source%]",
|
||||
"unit_prefix": "[%key:component::integration::config::step::user::data::unit_prefix%]",
|
||||
"unit_time": "[%key:component::integration::config::step::user::data::unit_time%]"
|
||||
"unit_time": "[%key:component::integration::config::step::user::data::unit_time%]",
|
||||
"max_sub_interval": "[%key:component::integration::config::step::user::data::max_sub_interval%]"
|
||||
},
|
||||
"data_description": {
|
||||
"round": "[%key:component::integration::config::step::user::data_description::round%]",
|
||||
"unit_prefix": "[%key:component::integration::config::step::user::data_description::unit_prefix%]",
|
||||
"unit_time": "[%key:component::integration::config::step::user::data_description::unit_time%]"
|
||||
"unit_time": "[%key:component::integration::config::step::user::data_description::unit_time%]",
|
||||
"max_sub_interval": "[%key:component::integration::config::step::user::data_description::max_sub_interval%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -441,6 +441,9 @@ class ZoneDevice(ClimateEntity):
|
||||
_attr_name = None
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature_step = 0.5
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
def __init__(self, controller: ControllerDevice, zone: Zone) -> None:
|
||||
"""Initialise ZoneDevice."""
|
||||
|
@ -11,7 +11,7 @@
|
||||
"loggers": ["xknx", "xknxproject"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"xknx==3.1.0",
|
||||
"xknx==3.1.1",
|
||||
"xknxproject==3.7.1",
|
||||
"knx-frontend==2024.8.9.225351"
|
||||
],
|
||||
|
@ -60,6 +60,8 @@ TRANSITION_BLOCKLIST = (
|
||||
(4456, 1011, "1.0.0", "2.00.00"),
|
||||
(4488, 260, "1.0", "1.0.0"),
|
||||
(4488, 514, "1.0", "1.0.0"),
|
||||
(4921, 42, "1.0", "1.01.060"),
|
||||
(4921, 43, "1.0", "1.01.060"),
|
||||
(4999, 24875, "1.0", "27.0"),
|
||||
(4999, 25057, "1.0", "27.0"),
|
||||
(5009, 514, "1.0", "1.0.0"),
|
||||
|
@ -229,12 +229,12 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
translation_key="startup_on_off",
|
||||
options=["On", "Off", "Toggle", "Previous"],
|
||||
measurement_to_ha=lambda x: {
|
||||
measurement_to_ha=lambda x: { # pylint: disable=unnecessary-lambda
|
||||
0: "Off",
|
||||
1: "On",
|
||||
2: "Toggle",
|
||||
None: "Previous",
|
||||
}[x],
|
||||
}.get(x),
|
||||
ha_to_native_value=lambda x: {
|
||||
"Off": 0,
|
||||
"On": 1,
|
||||
|
@ -20,5 +20,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["google_nest_sdm"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["google-nest-sdm==4.0.6"]
|
||||
"requirements": ["google-nest-sdm==4.0.7"]
|
||||
}
|
||||
|
@ -50,13 +50,15 @@ class NextBusDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Fetch data from NextBus."""
|
||||
self.logger.debug("Updating data from API. Routes: %s", str(self._route_stops))
|
||||
|
||||
_route_stops = set(self._route_stops)
|
||||
self.logger.debug("Updating data from API. Routes: %s", str(_route_stops))
|
||||
|
||||
def _update_data() -> dict:
|
||||
"""Fetch data from NextBus."""
|
||||
self.logger.debug("Updating data from API (executor)")
|
||||
predictions: dict[RouteStop, dict[str, Any]] = {}
|
||||
for route_stop in self._route_stops:
|
||||
for route_stop in _route_stops:
|
||||
prediction_results: list[dict[str, Any]] = []
|
||||
try:
|
||||
prediction_results = self.client.predictions_for_stop(
|
||||
|
@ -7,7 +7,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["roborock"],
|
||||
"requirements": [
|
||||
"python-roborock==2.5.0",
|
||||
"python-roborock==2.6.0",
|
||||
"vacuum-map-parser-roborock==0.1.2"
|
||||
]
|
||||
}
|
||||
|
@ -682,6 +682,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
||||
self.entry.async_create_background_task(
|
||||
self.hass, self._async_connected(), "rpc device init", eager_start=True
|
||||
)
|
||||
# Make sure entities are marked available
|
||||
self.async_set_updated_data(None)
|
||||
elif update_type is RpcUpdateType.DISCONNECTED:
|
||||
self.entry.async_create_background_task(
|
||||
@ -690,6 +691,8 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
||||
"rpc device disconnected",
|
||||
eager_start=True,
|
||||
)
|
||||
# Make sure entities are marked as unavailable
|
||||
self.async_set_updated_data(None)
|
||||
elif update_type is RpcUpdateType.STATUS:
|
||||
self.async_set_updated_data(None)
|
||||
if self.sleep_period:
|
||||
@ -711,7 +714,8 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
||||
"""Shutdown the coordinator."""
|
||||
if self.device.connected:
|
||||
try:
|
||||
await async_stop_scanner(self.device)
|
||||
if not self.sleep_period:
|
||||
await async_stop_scanner(self.device)
|
||||
await super().shutdown()
|
||||
except InvalidAuthError:
|
||||
self.entry.async_start_reauth(self.hass)
|
||||
|
@ -358,6 +358,14 @@ class ShellyRpcEntity(CoordinatorEntity[ShellyRpcCoordinator]):
|
||||
self._attr_unique_id = f"{coordinator.mac}-{key}"
|
||||
self._attr_name = get_rpc_entity_name(coordinator.device, key)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Check if device is available and initialized or sleepy."""
|
||||
coordinator = self.coordinator
|
||||
return super().available and (
|
||||
coordinator.device.initialized or bool(coordinator.sleep_period)
|
||||
)
|
||||
|
||||
@property
|
||||
def status(self) -> dict:
|
||||
"""Device status by entity key."""
|
||||
|
@ -9,7 +9,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioshelly"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioshelly==11.2.0"],
|
||||
"requirements": ["aioshelly==11.2.4"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_http._tcp.local.",
|
||||
|
@ -172,10 +172,17 @@ async def async_browse_media(
|
||||
|
||||
# Check for config entry specifier, and extract Spotify URI
|
||||
parsed_url = yarl.URL(media_content_id)
|
||||
host = parsed_url.host
|
||||
|
||||
if (
|
||||
parsed_url.host is None
|
||||
or (entry := hass.config_entries.async_get_entry(parsed_url.host)) is None
|
||||
host is None
|
||||
# config entry ids can be upper or lower case. Yarl always returns host
|
||||
# names in lower case, so we need to look for the config entry in both
|
||||
or (
|
||||
entry := hass.config_entries.async_get_entry(host)
|
||||
or hass.config_entries.async_get_entry(host.upper())
|
||||
)
|
||||
is None
|
||||
or not isinstance(entry.runtime_data, HomeAssistantSpotifyData)
|
||||
):
|
||||
raise BrowseError("Invalid Spotify account specified")
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/thethingsnetwork",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["ttn_client==1.1.0"]
|
||||
"requirements": ["ttn_client==1.2.0"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/tplink_omada",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["tplink-omada-client==1.3.12"]
|
||||
"requirements": ["tplink-omada-client==1.4.2"]
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ SKU_TO_BASE_DEVICE = {
|
||||
"LAP-C202S-WUSR": "Core200S", # Alt ID Model Core200S
|
||||
"Core300S": "Core300S",
|
||||
"LAP-C301S-WJP": "Core300S", # Alt ID Model Core300S
|
||||
"LAP-C301S-WAAA": "Core300S", # Alt ID Model Core300S
|
||||
"Core400S": "Core400S",
|
||||
"LAP-C401S-WJP": "Core400S", # Alt ID Model Core400S
|
||||
"LAP-C401S-WUSR": "Core400S", # Alt ID Model Core400S
|
||||
|
@ -46,7 +46,9 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
except WLEDConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(device.info.mac_address)
|
||||
await self.async_set_unique_id(
|
||||
device.info.mac_address, raise_on_progress=False
|
||||
)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: user_input[CONF_HOST]}
|
||||
)
|
||||
@ -56,8 +58,6 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
},
|
||||
)
|
||||
else:
|
||||
user_input = {}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
|
@ -92,7 +92,7 @@ def _get_obj_holidays(
|
||||
subdiv=province,
|
||||
years=year,
|
||||
language=language,
|
||||
categories=set_categories, # type: ignore[arg-type]
|
||||
categories=set_categories,
|
||||
)
|
||||
if (supported_languages := obj_holidays.supported_languages) and language == "en":
|
||||
for lang in supported_languages:
|
||||
@ -102,7 +102,7 @@ def _get_obj_holidays(
|
||||
subdiv=province,
|
||||
years=year,
|
||||
language=lang,
|
||||
categories=set_categories, # type: ignore[arg-type]
|
||||
categories=set_categories,
|
||||
)
|
||||
LOGGER.debug("Changing language from %s to %s", language, lang)
|
||||
return obj_holidays
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.53"]
|
||||
"requirements": ["holidays==0.55"]
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2024
|
||||
MINOR_VERSION: Final = 8
|
||||
PATCH_VERSION: Final = "2"
|
||||
PATCH_VERSION: Final = "3"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||
|
@ -4,7 +4,7 @@ aiodhcpwatcher==1.0.2
|
||||
aiodiscover==2.1.0
|
||||
aiodns==3.2.0
|
||||
aiohttp-fast-zlib==0.1.1
|
||||
aiohttp==3.10.3
|
||||
aiohttp==3.10.5
|
||||
aiohttp_cors==0.7.0
|
||||
aiozoneinfo==0.2.1
|
||||
astral==2.2
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2024.8.2"
|
||||
version = "2024.8.3"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
@ -24,7 +24,7 @@ classifiers = [
|
||||
requires-python = ">=3.12.0"
|
||||
dependencies = [
|
||||
"aiodns==3.2.0",
|
||||
"aiohttp==3.10.3",
|
||||
"aiohttp==3.10.5",
|
||||
"aiohttp_cors==0.7.0",
|
||||
"aiohttp-fast-zlib==0.1.1",
|
||||
"aiozoneinfo==0.2.1",
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
# Home Assistant Core
|
||||
aiodns==3.2.0
|
||||
aiohttp==3.10.3
|
||||
aiohttp==3.10.5
|
||||
aiohttp_cors==0.7.0
|
||||
aiohttp-fast-zlib==0.1.1
|
||||
aiozoneinfo==0.2.1
|
||||
|
@ -255,10 +255,10 @@ aioguardian==2022.07.0
|
||||
aioharmony==0.2.10
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==3.2.2
|
||||
aiohomekit==3.2.3
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==4.7.2
|
||||
aiohue==4.7.3
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==1.1.0
|
||||
@ -359,7 +359,7 @@ aioruuvigateway==0.1.0
|
||||
aiosenz==1.0.0
|
||||
|
||||
# homeassistant.components.shelly
|
||||
aioshelly==11.2.0
|
||||
aioshelly==11.2.4
|
||||
|
||||
# homeassistant.components.skybell
|
||||
aioskybell==22.7.0
|
||||
@ -989,7 +989,7 @@ google-cloud-texttospeech==2.16.3
|
||||
google-generativeai==0.6.0
|
||||
|
||||
# homeassistant.components.nest
|
||||
google-nest-sdm==4.0.6
|
||||
google-nest-sdm==4.0.7
|
||||
|
||||
# homeassistant.components.google_travel_time
|
||||
googlemaps==2.5.1
|
||||
@ -1096,7 +1096,7 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.53
|
||||
holidays==0.55
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240809.0
|
||||
@ -1759,7 +1759,7 @@ pyblu==0.4.0
|
||||
pybotvac==0.0.25
|
||||
|
||||
# homeassistant.components.braviatv
|
||||
pybravia==0.3.3
|
||||
pybravia==0.3.4
|
||||
|
||||
# homeassistant.components.nissan_leaf
|
||||
pycarwings2==2.14
|
||||
@ -1912,7 +1912,7 @@ pyhiveapi==0.5.16
|
||||
pyhomematic==0.1.77
|
||||
|
||||
# homeassistant.components.homeworks
|
||||
pyhomeworks==1.1.1
|
||||
pyhomeworks==1.1.2
|
||||
|
||||
# homeassistant.components.ialarm
|
||||
pyialarm==2.2.0
|
||||
@ -2341,7 +2341,7 @@ python-rabbitair==0.0.8
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==2.5.0
|
||||
python-roborock==2.6.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.36
|
||||
@ -2792,7 +2792,7 @@ total-connect-client==2024.5
|
||||
tp-connected==0.0.4
|
||||
|
||||
# homeassistant.components.tplink_omada
|
||||
tplink-omada-client==1.3.12
|
||||
tplink-omada-client==1.4.2
|
||||
|
||||
# homeassistant.components.transmission
|
||||
transmission-rpc==7.0.3
|
||||
@ -2801,7 +2801,7 @@ transmission-rpc==7.0.3
|
||||
ttls==1.8.3
|
||||
|
||||
# homeassistant.components.thethingsnetwork
|
||||
ttn_client==1.1.0
|
||||
ttn_client==1.2.0
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-device-sharing-sdk==0.1.9
|
||||
@ -2936,7 +2936,7 @@ xbox-webapi==2.0.11
|
||||
xiaomi-ble==0.30.2
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==3.1.0
|
||||
xknx==3.1.1
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknxproject==3.7.1
|
||||
@ -2959,7 +2959,7 @@ yalesmartalarmclient==0.3.9
|
||||
yalexs-ble==2.4.3
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==6.4.3
|
||||
yalexs==8.4.1
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.14
|
||||
|
@ -240,10 +240,10 @@ aioguardian==2022.07.0
|
||||
aioharmony==0.2.10
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==3.2.2
|
||||
aiohomekit==3.2.3
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==4.7.2
|
||||
aiohue==4.7.3
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==1.1.0
|
||||
@ -341,7 +341,7 @@ aioruuvigateway==0.1.0
|
||||
aiosenz==1.0.0
|
||||
|
||||
# homeassistant.components.shelly
|
||||
aioshelly==11.2.0
|
||||
aioshelly==11.2.4
|
||||
|
||||
# homeassistant.components.skybell
|
||||
aioskybell==22.7.0
|
||||
@ -833,7 +833,7 @@ google-cloud-pubsub==2.13.11
|
||||
google-generativeai==0.6.0
|
||||
|
||||
# homeassistant.components.nest
|
||||
google-nest-sdm==4.0.6
|
||||
google-nest-sdm==4.0.7
|
||||
|
||||
# homeassistant.components.google_travel_time
|
||||
googlemaps==2.5.1
|
||||
@ -916,7 +916,7 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.53
|
||||
holidays==0.55
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240809.0
|
||||
@ -1421,7 +1421,7 @@ pyblu==0.4.0
|
||||
pybotvac==0.0.25
|
||||
|
||||
# homeassistant.components.braviatv
|
||||
pybravia==0.3.3
|
||||
pybravia==0.3.4
|
||||
|
||||
# homeassistant.components.cloudflare
|
||||
pycfdns==3.0.0
|
||||
@ -1523,7 +1523,7 @@ pyhiveapi==0.5.16
|
||||
pyhomematic==0.1.77
|
||||
|
||||
# homeassistant.components.homeworks
|
||||
pyhomeworks==1.1.1
|
||||
pyhomeworks==1.1.2
|
||||
|
||||
# homeassistant.components.ialarm
|
||||
pyialarm==2.2.0
|
||||
@ -1850,7 +1850,7 @@ python-picnic-api==1.1.0
|
||||
python-rabbitair==0.0.8
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==2.5.0
|
||||
python-roborock==2.6.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.36
|
||||
@ -2190,7 +2190,7 @@ toonapi==0.3.0
|
||||
total-connect-client==2024.5
|
||||
|
||||
# homeassistant.components.tplink_omada
|
||||
tplink-omada-client==1.3.12
|
||||
tplink-omada-client==1.4.2
|
||||
|
||||
# homeassistant.components.transmission
|
||||
transmission-rpc==7.0.3
|
||||
@ -2199,7 +2199,7 @@ transmission-rpc==7.0.3
|
||||
ttls==1.8.3
|
||||
|
||||
# homeassistant.components.thethingsnetwork
|
||||
ttn_client==1.1.0
|
||||
ttn_client==1.2.0
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-device-sharing-sdk==0.1.9
|
||||
@ -2316,7 +2316,7 @@ xbox-webapi==2.0.11
|
||||
xiaomi-ble==0.30.2
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==3.1.0
|
||||
xknx==3.1.1
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknxproject==3.7.1
|
||||
@ -2336,7 +2336,7 @@ yalesmartalarmclient==0.3.9
|
||||
yalexs-ble==2.4.3
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==6.4.3
|
||||
yalexs==8.4.1
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.14
|
||||
|
@ -124,7 +124,6 @@ EXCEPTIONS = {
|
||||
"PyXiaomiGateway", # https://github.com/Danielhiversen/PyXiaomiGateway/pull/201
|
||||
"aiocomelit", # https://github.com/chemelli74/aiocomelit/pull/138
|
||||
"aioecowitt", # https://github.com/home-assistant-libs/aioecowitt/pull/180
|
||||
"aiohappyeyeballs", # Python-2.0.1
|
||||
"aioopenexchangerates", # https://github.com/MartinHjelmare/aioopenexchangerates/pull/94
|
||||
"aiooui", # https://github.com/Bluetooth-Devices/aiooui/pull/8
|
||||
"aioruuvigateway", # https://github.com/akx/aioruuvigateway/pull/6
|
||||
|
@ -253,3 +253,32 @@ async def test_zeroconf_flow_abort_old_firmware(hass: HomeAssistant) -> None:
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "invalid_version"
|
||||
|
||||
|
||||
async def test_user_flow_works_discovery(
|
||||
hass: HomeAssistant,
|
||||
mock_new_airgradient_client: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test user flow can continue after discovery happened."""
|
||||
await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=ZEROCONF_DISCOVERY,
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 2
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_HOST: "10.0.0.131"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
# Verify the discovery flow was aborted
|
||||
assert not hass.config_entries.flow.async_progress(DOMAIN)
|
||||
|
@ -70,6 +70,7 @@ async def test_discovery_remote(
|
||||
{
|
||||
"current_activity": current_activity,
|
||||
"activity_list": activity_list,
|
||||
"supported_features": 4,
|
||||
},
|
||||
)
|
||||
msg = await smart_home.async_handle_message(hass, get_default_config(hass), request)
|
||||
@ -790,22 +791,37 @@ async def test_report_remote_activity(hass: HomeAssistant) -> None:
|
||||
hass.states.async_set(
|
||||
"remote.unknown",
|
||||
"on",
|
||||
{"current_activity": "UNKNOWN"},
|
||||
{
|
||||
"current_activity": "UNKNOWN",
|
||||
"supported_features": 4,
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"remote.tv",
|
||||
"on",
|
||||
{"current_activity": "TV", "activity_list": ["TV", "MUSIC", "DVD"]},
|
||||
{
|
||||
"current_activity": "TV",
|
||||
"activity_list": ["TV", "MUSIC", "DVD"],
|
||||
"supported_features": 4,
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"remote.music",
|
||||
"on",
|
||||
{"current_activity": "MUSIC", "activity_list": ["TV", "MUSIC", "DVD"]},
|
||||
{
|
||||
"current_activity": "MUSIC",
|
||||
"activity_list": ["TV", "MUSIC", "DVD"],
|
||||
"supported_features": 4,
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"remote.dvd",
|
||||
"on",
|
||||
{"current_activity": "DVD", "activity_list": ["TV", "MUSIC", "DVD"]},
|
||||
{
|
||||
"current_activity": "DVD",
|
||||
"activity_list": ["TV", "MUSIC", "DVD"],
|
||||
"supported_features": 4,
|
||||
},
|
||||
)
|
||||
|
||||
properties = await reported_properties(hass, "remote#unknown")
|
||||
|
@ -3,6 +3,7 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from yalexs.manager.ratelimit import _RateLimitChecker
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_discovery", autouse=True)
|
||||
@ -12,3 +13,10 @@ def mock_discovery_fixture():
|
||||
"homeassistant.components.august.data.discovery_flow.async_create_flow"
|
||||
) as mock_discovery:
|
||||
yield mock_discovery
|
||||
|
||||
|
||||
@pytest.fixture(name="disable_ratelimit_checks", autouse=True)
|
||||
def disable_ratelimit_checks_fixture():
|
||||
"""Disable rate limit checks."""
|
||||
with patch.object(_RateLimitChecker, "register_wakeup"):
|
||||
yield
|
||||
|
@ -25,7 +25,7 @@ from yalexs.activity import (
|
||||
DoorOperationActivity,
|
||||
LockOperationActivity,
|
||||
)
|
||||
from yalexs.authenticator import AuthenticationState
|
||||
from yalexs.authenticator_common import AuthenticationState
|
||||
from yalexs.const import Brand
|
||||
from yalexs.doorbell import Doorbell, DoorbellDetail
|
||||
from yalexs.lock import Lock, LockDetail
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from yalexs.authenticator import ValidationResult
|
||||
from yalexs.authenticator_common import ValidationResult
|
||||
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
||||
|
||||
from homeassistant import config_entries
|
||||
|
@ -50,5 +50,5 @@ async def _patched_refresh_access_token(
|
||||
)
|
||||
await august_gateway.async_refresh_access_token_if_needed()
|
||||
refresh_access_token_mock.assert_called()
|
||||
assert august_gateway.access_token == new_token
|
||||
assert await august_gateway.async_get_access_token() == new_token
|
||||
assert august_gateway.authentication.access_token_expires == new_token_expire_time
|
||||
|
@ -73,7 +73,20 @@ def common_requests(aioclient_mock: AiohttpClientMocker) -> AiohttpClientMocker:
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
aioclient_mock.get(
|
||||
"https://habitica.com/api/v3/tasks/user?type=completedTodos",
|
||||
json={
|
||||
"data": [
|
||||
{
|
||||
"text": "this is a mock todo #5",
|
||||
"id": 5,
|
||||
"_id": 5,
|
||||
"type": "todo",
|
||||
"completed": True,
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"https://habitica.com/api/v3/tasks/user",
|
||||
json={
|
||||
@ -88,19 +101,6 @@ def common_requests(aioclient_mock: AiohttpClientMocker) -> AiohttpClientMocker:
|
||||
]
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"https://habitica.com/api/v3/tasks/user?type=completedTodos",
|
||||
json={
|
||||
"data": [
|
||||
{
|
||||
"text": "this is a mock todo #5",
|
||||
"id": 5,
|
||||
"type": "todo",
|
||||
"completed": True,
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
aioclient_mock.post(
|
||||
"https://habitica.com/api/v3/tasks/user",
|
||||
|
@ -1288,7 +1288,9 @@
|
||||
},
|
||||
{
|
||||
"button": {
|
||||
"last_event": "short_release"
|
||||
"button_report": {
|
||||
"event": "short_release"
|
||||
}
|
||||
},
|
||||
"id": "c658d3d8-a013-4b81-8ac6-78b248537e70",
|
||||
"id_v1": "/sensors/50",
|
||||
@ -1327,7 +1329,9 @@
|
||||
},
|
||||
{
|
||||
"button": {
|
||||
"last_event": "short_release"
|
||||
"button_report": {
|
||||
"event": "short_release"
|
||||
}
|
||||
},
|
||||
"id": "7f1ab9f6-cc2b-4b40-9011-65e2af153f75",
|
||||
"id_v1": "/sensors/10",
|
||||
@ -1366,7 +1370,9 @@
|
||||
},
|
||||
{
|
||||
"button": {
|
||||
"last_event": "short_release"
|
||||
"button_report": {
|
||||
"event": "short_release"
|
||||
}
|
||||
},
|
||||
"id": "31cffcda-efc2-401f-a152-e10db3eed232",
|
||||
"id_v1": "/sensors/5",
|
||||
|
@ -854,6 +854,27 @@ async def test_rpc_runs_connected_events_when_initialized(
|
||||
assert call.script_list() in mock_rpc_device.mock_calls
|
||||
|
||||
|
||||
async def test_rpc_sleeping_device_unload_ignore_ble_scanner(
|
||||
hass: HomeAssistant,
|
||||
mock_rpc_device: Mock,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Test RPC sleeping device does not stop ble scanner on unload."""
|
||||
monkeypatch.setattr(mock_rpc_device, "connected", True)
|
||||
entry = await init_integration(hass, 2, sleep_period=1000)
|
||||
|
||||
# Make device online
|
||||
mock_rpc_device.mock_online()
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
# Unload
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# BLE script list is called during stop ble scanner
|
||||
assert call.script_list() not in mock_rpc_device.mock_calls
|
||||
|
||||
|
||||
async def test_block_sleeping_device_connection_error(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
|
@ -43,7 +43,7 @@ from . import (
|
||||
register_entity,
|
||||
)
|
||||
|
||||
from tests.common import mock_restore_cache_with_extra_data
|
||||
from tests.common import async_fire_time_changed, mock_restore_cache_with_extra_data
|
||||
|
||||
RELAY_BLOCK_ID = 0
|
||||
SENSOR_BLOCK_ID = 3
|
||||
@ -1189,3 +1189,35 @@ async def test_rpc_remove_enum_virtual_sensor_when_orphaned(
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert not entry
|
||||
|
||||
|
||||
async def test_rpc_device_sensor_goes_unavailable_on_disconnect(
|
||||
hass: HomeAssistant,
|
||||
mock_rpc_device: Mock,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test RPC device with sensor goes unavailable on disconnect."""
|
||||
await init_integration(hass, 2)
|
||||
temp_sensor_state = hass.states.get("sensor.test_name_temperature")
|
||||
assert temp_sensor_state is not None
|
||||
assert temp_sensor_state.state != STATE_UNAVAILABLE
|
||||
monkeypatch.setattr(mock_rpc_device, "connected", False)
|
||||
monkeypatch.setattr(mock_rpc_device, "initialized", False)
|
||||
mock_rpc_device.mock_disconnected()
|
||||
await hass.async_block_till_done()
|
||||
temp_sensor_state = hass.states.get("sensor.test_name_temperature")
|
||||
assert temp_sensor_state.state == STATE_UNAVAILABLE
|
||||
|
||||
freezer.tick(60)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert "NotInitialized" not in caplog.text
|
||||
|
||||
monkeypatch.setattr(mock_rpc_device, "connected", True)
|
||||
monkeypatch.setattr(mock_rpc_device, "initialized", True)
|
||||
mock_rpc_device.mock_initialized()
|
||||
await hass.async_block_till_done()
|
||||
temp_sensor_state = hass.states.get("sensor.test_name_temperature")
|
||||
assert temp_sensor_state.state != STATE_UNAVAILABLE
|
||||
|
128
tests/components/spotify/conftest.py
Normal file
128
tests/components/spotify/conftest.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""Common test fixtures."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.application_credentials import (
|
||||
ClientCredential,
|
||||
async_import_client_credential,
|
||||
)
|
||||
from homeassistant.components.spotify import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry_1() -> MockConfigEntry:
|
||||
"""Mock a config entry with an upper case entry id."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="spotify_1",
|
||||
data={
|
||||
"auth_implementation": "spotify_c95e4090d4d3438b922331e7428f8171",
|
||||
"token": {
|
||||
"access_token": "AccessToken",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "RefreshToken",
|
||||
"scope": "playlist-read-private ...",
|
||||
"expires_at": 1724198975.8829377,
|
||||
},
|
||||
"id": "32oesphrnacjcf7vw5bf6odx3oiu",
|
||||
"name": "spotify_account_1",
|
||||
},
|
||||
unique_id="84fce612f5b8",
|
||||
entry_id="01J5TX5A0FF6G5V0QJX6HBC94T",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry_2() -> MockConfigEntry:
|
||||
"""Mock a config entry with a lower case entry id."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="spotify_2",
|
||||
data={
|
||||
"auth_implementation": "spotify_c95e4090d4d3438b922331e7428f8171",
|
||||
"token": {
|
||||
"access_token": "AccessToken",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "RefreshToken",
|
||||
"scope": "playlist-read-private ...",
|
||||
"expires_at": 1724198975.8829377,
|
||||
},
|
||||
"id": "55oesphrnacjcf7vw5bf6odx3oiu",
|
||||
"name": "spotify_account_2",
|
||||
},
|
||||
unique_id="99fce612f5b8",
|
||||
entry_id="32oesphrnacjcf7vw5bf6odx3",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def spotify_playlists() -> dict[str, Any]:
|
||||
"""Mock the return from getting a list of playlists."""
|
||||
return {
|
||||
"href": "https://api.spotify.com/v1/users/31oesphrnacjcf7vw5bf6odx3oiu/playlists?offset=0&limit=48",
|
||||
"limit": 48,
|
||||
"next": None,
|
||||
"offset": 0,
|
||||
"previous": None,
|
||||
"total": 1,
|
||||
"items": [
|
||||
{
|
||||
"collaborative": False,
|
||||
"description": "",
|
||||
"id": "unique_identifier_00",
|
||||
"name": "Playlist1",
|
||||
"type": "playlist",
|
||||
"uri": "spotify:playlist:unique_identifier_00",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def spotify_mock(spotify_playlists: dict[str, Any]) -> Generator[MagicMock]:
|
||||
"""Mock the Spotify API."""
|
||||
with patch("homeassistant.components.spotify.Spotify") as spotify_mock:
|
||||
mock = MagicMock()
|
||||
mock.current_user_playlists.return_value = spotify_playlists
|
||||
spotify_mock.return_value = mock
|
||||
yield spotify_mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def spotify_setup(
|
||||
hass: HomeAssistant,
|
||||
spotify_mock: MagicMock,
|
||||
mock_config_entry_1: MockConfigEntry,
|
||||
mock_config_entry_2: MockConfigEntry,
|
||||
):
|
||||
"""Set up the spotify integration."""
|
||||
with patch(
|
||||
"homeassistant.components.spotify.OAuth2Session.async_ensure_token_valid"
|
||||
):
|
||||
await async_setup_component(hass, "application_credentials", {})
|
||||
await hass.async_block_till_done()
|
||||
await async_import_client_credential(
|
||||
hass,
|
||||
DOMAIN,
|
||||
ClientCredential("CLIENT_ID", "CLIENT_SECRET"),
|
||||
"spotify_c95e4090d4d3438b922331e7428f8171",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_config_entry_1.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry_1.entry_id)
|
||||
mock_config_entry_2.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry_2.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
yield
|
236
tests/components/spotify/snapshots/test_media_browser.ambr
Normal file
236
tests/components/spotify/snapshots/test_media_browser.ambr
Normal file
@ -0,0 +1,236 @@
|
||||
# serializer version: 1
|
||||
# name: test_browse_media_categories
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_playlists',
|
||||
'media_content_type': 'spotify://current_user_playlists',
|
||||
'thumbnail': None,
|
||||
'title': 'Playlists',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_followed_artists',
|
||||
'media_content_type': 'spotify://current_user_followed_artists',
|
||||
'thumbnail': None,
|
||||
'title': 'Artists',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_saved_albums',
|
||||
'media_content_type': 'spotify://current_user_saved_albums',
|
||||
'thumbnail': None,
|
||||
'title': 'Albums',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_saved_tracks',
|
||||
'media_content_type': 'spotify://current_user_saved_tracks',
|
||||
'thumbnail': None,
|
||||
'title': 'Tracks',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.PODCAST: 'podcast'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_saved_shows',
|
||||
'media_content_type': 'spotify://current_user_saved_shows',
|
||||
'thumbnail': None,
|
||||
'title': 'Podcasts',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_recently_played',
|
||||
'media_content_type': 'spotify://current_user_recently_played',
|
||||
'thumbnail': None,
|
||||
'title': 'Recently played',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.ARTIST: 'artist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_top_artists',
|
||||
'media_content_type': 'spotify://current_user_top_artists',
|
||||
'thumbnail': None,
|
||||
'title': 'Top Artists',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_top_tracks',
|
||||
'media_content_type': 'spotify://current_user_top_tracks',
|
||||
'thumbnail': None,
|
||||
'title': 'Top Tracks',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.GENRE: 'genre'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/categories',
|
||||
'media_content_type': 'spotify://categories',
|
||||
'thumbnail': None,
|
||||
'title': 'Categories',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/featured_playlists',
|
||||
'media_content_type': 'spotify://featured_playlists',
|
||||
'thumbnail': None,
|
||||
'title': 'Featured Playlists',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': <MediaClass.ALBUM: 'album'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/new_releases',
|
||||
'media_content_type': 'spotify://new_releases',
|
||||
'thumbnail': None,
|
||||
'title': 'New Releases',
|
||||
}),
|
||||
]),
|
||||
'children_media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/library',
|
||||
'media_content_type': 'spotify://library',
|
||||
'not_shown': 0,
|
||||
'thumbnail': None,
|
||||
'title': 'Media Library',
|
||||
})
|
||||
# ---
|
||||
# name: test_browse_media_playlists
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:playlist:unique_identifier_00',
|
||||
'media_content_type': 'spotify://playlist',
|
||||
'thumbnail': None,
|
||||
'title': 'Playlist1',
|
||||
}),
|
||||
]),
|
||||
'children_media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_playlists',
|
||||
'media_content_type': 'spotify://current_user_playlists',
|
||||
'not_shown': 0,
|
||||
'thumbnail': None,
|
||||
'title': 'Playlists',
|
||||
})
|
||||
# ---
|
||||
# name: test_browse_media_playlists[01J5TX5A0FF6G5V0QJX6HBC94T]
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/spotify:playlist:unique_identifier_00',
|
||||
'media_content_type': 'spotify://playlist',
|
||||
'thumbnail': None,
|
||||
'title': 'Playlist1',
|
||||
}),
|
||||
]),
|
||||
'children_media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://01j5tx5a0ff6g5v0qjx6hbc94t/current_user_playlists',
|
||||
'media_content_type': 'spotify://current_user_playlists',
|
||||
'not_shown': 0,
|
||||
'thumbnail': None,
|
||||
'title': 'Playlists',
|
||||
})
|
||||
# ---
|
||||
# name: test_browse_media_playlists[32oesphrnacjcf7vw5bf6odx3]
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': True,
|
||||
'children_media_class': <MediaClass.TRACK: 'track'>,
|
||||
'media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_content_id': 'spotify://32oesphrnacjcf7vw5bf6odx3/spotify:playlist:unique_identifier_00',
|
||||
'media_content_type': 'spotify://playlist',
|
||||
'thumbnail': None,
|
||||
'title': 'Playlist1',
|
||||
}),
|
||||
]),
|
||||
'children_media_class': <MediaClass.PLAYLIST: 'playlist'>,
|
||||
'media_class': <MediaClass.DIRECTORY: 'directory'>,
|
||||
'media_content_id': 'spotify://32oesphrnacjcf7vw5bf6odx3/current_user_playlists',
|
||||
'media_content_type': 'spotify://current_user_playlists',
|
||||
'not_shown': 0,
|
||||
'thumbnail': None,
|
||||
'title': 'Playlists',
|
||||
})
|
||||
# ---
|
||||
# name: test_browse_media_root
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.APP: 'app'>,
|
||||
'media_content_id': 'spotify://01J5TX5A0FF6G5V0QJX6HBC94T',
|
||||
'media_content_type': 'spotify://library',
|
||||
'thumbnail': 'https://brands.home-assistant.io/_/spotify/logo.png',
|
||||
'title': 'spotify_1',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': None,
|
||||
'media_class': <MediaClass.APP: 'app'>,
|
||||
'media_content_id': 'spotify://32oesphrnacjcf7vw5bf6odx3',
|
||||
'media_content_type': 'spotify://library',
|
||||
'thumbnail': 'https://brands.home-assistant.io/_/spotify/logo.png',
|
||||
'title': 'spotify_2',
|
||||
}),
|
||||
]),
|
||||
'children_media_class': <MediaClass.APP: 'app'>,
|
||||
'media_class': <MediaClass.APP: 'app'>,
|
||||
'media_content_id': 'spotify://',
|
||||
'media_content_type': 'spotify',
|
||||
'not_shown': 0,
|
||||
'thumbnail': 'https://brands.home-assistant.io/_/spotify/logo.png',
|
||||
'title': 'Spotify',
|
||||
})
|
||||
# ---
|
61
tests/components/spotify/test_media_browser.py
Normal file
61
tests/components/spotify/test_media_browser.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""Test the media browser interface."""
|
||||
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.spotify import DOMAIN
|
||||
from homeassistant.components.spotify.browse_media import async_browse_media
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
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)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
|
||||
async def test_browse_media_root(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
spotify_setup,
|
||||
) -> None:
|
||||
"""Test browsing the root."""
|
||||
response = await async_browse_media(hass, None, None)
|
||||
assert response.as_dict() == snapshot
|
||||
|
||||
|
||||
async def test_browse_media_categories(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
spotify_setup,
|
||||
) -> None:
|
||||
"""Test browsing categories."""
|
||||
response = await async_browse_media(
|
||||
hass, "spotify://library", "spotify://01J5TX5A0FF6G5V0QJX6HBC94T"
|
||||
)
|
||||
assert response.as_dict() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config_entry_id"), [("01J5TX5A0FF6G5V0QJX6HBC94T"), ("32oesphrnacjcf7vw5bf6odx3")]
|
||||
)
|
||||
async def test_browse_media_playlists(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
config_entry_id: str,
|
||||
spotify_setup,
|
||||
) -> None:
|
||||
"""Test browsing playlists for the two config entries."""
|
||||
response = await async_browse_media(
|
||||
hass,
|
||||
"spotify://current_user_playlists",
|
||||
f"spotify://{config_entry_id}/current_user_playlists",
|
||||
)
|
||||
assert response.as_dict() == snapshot
|
Loading…
x
Reference in New Issue
Block a user