mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 06:37:52 +00:00
2024.8.3 (#124569)
This commit is contained in:
commit
516f3295bf
@ -92,7 +92,9 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
except AirGradientError:
|
except AirGradientError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
else:
|
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()
|
self._abort_if_unique_id_configured()
|
||||||
await self.set_configuration_source()
|
await self.set_configuration_source()
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
|
@ -661,9 +661,12 @@ class RemoteCapabilities(AlexaEntity):
|
|||||||
def interfaces(self) -> Generator[AlexaCapability]:
|
def interfaces(self) -> Generator[AlexaCapability]:
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
yield AlexaPowerController(self.entity)
|
yield AlexaPowerController(self.entity)
|
||||||
yield AlexaModeController(
|
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
|
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 AlexaEndpointHealth(self.hass, self.entity)
|
||||||
yield Alexa(self.entity)
|
yield Alexa(self.entity)
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ from typing import Any
|
|||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from yalexs.authenticator import ValidationResult
|
from yalexs.authenticator_common import ValidationResult
|
||||||
from yalexs.const import BRANDS, DEFAULT_BRAND
|
from yalexs.const import BRANDS_WITHOUT_OAUTH, DEFAULT_BRAND
|
||||||
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
@ -118,7 +118,7 @@ class AugustConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_BRAND,
|
CONF_BRAND,
|
||||||
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
|
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
|
||||||
): vol.In(BRANDS),
|
): vol.In(BRANDS_WITHOUT_OAUTH),
|
||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_LOGIN_METHOD,
|
CONF_LOGIN_METHOD,
|
||||||
default=self._user_auth_details.get(
|
default=self._user_auth_details.get(
|
||||||
@ -208,7 +208,7 @@ class AugustConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
vol.Required(
|
vol.Required(
|
||||||
CONF_BRAND,
|
CONF_BRAND,
|
||||||
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
|
default=self._user_auth_details.get(CONF_BRAND, DEFAULT_BRAND),
|
||||||
): vol.In(BRANDS),
|
): vol.In(BRANDS_WITHOUT_OAUTH),
|
||||||
vol.Required(CONF_PASSWORD): str,
|
vol.Required(CONF_PASSWORD): str,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -28,5 +28,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pubnub", "yalexs"],
|
"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",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pybravia"],
|
"loggers": ["pybravia"],
|
||||||
"requirements": ["pybravia==0.3.3"],
|
"requirements": ["pybravia==0.3.4"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"st": "urn:schemas-sony-com:service:ScalarWebAPI:1",
|
"st": "urn:schemas-sony-com:service:ScalarWebAPI:1",
|
||||||
|
@ -49,7 +49,14 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
|
|||||||
try:
|
try:
|
||||||
user_response = await self.api.user.get()
|
user_response = await self.api.user.get()
|
||||||
tasks_response = await self.api.tasks.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:
|
except ClientResponseError as error:
|
||||||
raise UpdateFailed(f"Error communicating with API: {error}") from error
|
raise UpdateFailed(f"Error communicating with API: {error}") from error
|
||||||
|
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||||
"iot_class": "local_polling",
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiohomekit", "commentjson"],
|
"loggers": ["aiohomekit", "commentjson"],
|
||||||
"requirements": ["aiohomekit==3.2.2"],
|
"requirements": ["aiohomekit==3.2.3"],
|
||||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/homeworks",
|
"documentation": "https://www.home-assistant.io/integrations/homeworks",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyhomeworks"],
|
"loggers": ["pyhomeworks"],
|
||||||
"requirements": ["pyhomeworks==1.1.1"]
|
"requirements": ["pyhomeworks==1.1.2"]
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,6 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiohue"],
|
"loggers": ["aiohue"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aiohue==4.7.2"],
|
"requirements": ["aiohue==4.7.3"],
|
||||||
"zeroconf": ["_hue._tcp.local."]
|
"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_DEVICE_ID: device.id, # type: ignore[union-attr]
|
||||||
CONF_UNIQUE_ID: hue_resource.id,
|
CONF_UNIQUE_ID: hue_resource.id,
|
||||||
CONF_TYPE: hue_resource.relative_rotary.rotary_report.action.value,
|
CONF_TYPE: hue_resource.relative_rotary.rotary_report.action.value,
|
||||||
CONF_SUBTYPE: hue_resource.relative_rotary.last_event.rotation.direction.value,
|
CONF_SUBTYPE: hue_resource.relative_rotary.rotary_report.rotation.direction.value,
|
||||||
CONF_DURATION: hue_resource.relative_rotary.last_event.rotation.duration,
|
CONF_DURATION: hue_resource.relative_rotary.rotary_report.rotation.duration,
|
||||||
CONF_STEPS: hue_resource.relative_rotary.last_event.rotation.steps,
|
CONF_STEPS: hue_resource.relative_rotary.rotary_report.rotation.steps,
|
||||||
}
|
}
|
||||||
hass.bus.async_fire(ATTR_HUE_EVENT, data)
|
hass.bus.async_fire(ATTR_HUE_EVENT, data)
|
||||||
|
|
||||||
|
@ -31,12 +31,14 @@
|
|||||||
"round": "[%key:component::integration::config::step::user::data::round%]",
|
"round": "[%key:component::integration::config::step::user::data::round%]",
|
||||||
"source": "[%key:component::integration::config::step::user::data::source%]",
|
"source": "[%key:component::integration::config::step::user::data::source%]",
|
||||||
"unit_prefix": "[%key:component::integration::config::step::user::data::unit_prefix%]",
|
"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": {
|
"data_description": {
|
||||||
"round": "[%key:component::integration::config::step::user::data_description::round%]",
|
"round": "[%key:component::integration::config::step::user::data_description::round%]",
|
||||||
"unit_prefix": "[%key:component::integration::config::step::user::data_description::unit_prefix%]",
|
"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_name = None
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_target_temperature_step = 0.5
|
_attr_target_temperature_step = 0.5
|
||||||
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, controller: ControllerDevice, zone: Zone) -> None:
|
def __init__(self, controller: ControllerDevice, zone: Zone) -> None:
|
||||||
"""Initialise ZoneDevice."""
|
"""Initialise ZoneDevice."""
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"loggers": ["xknx", "xknxproject"],
|
"loggers": ["xknx", "xknxproject"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"xknx==3.1.0",
|
"xknx==3.1.1",
|
||||||
"xknxproject==3.7.1",
|
"xknxproject==3.7.1",
|
||||||
"knx-frontend==2024.8.9.225351"
|
"knx-frontend==2024.8.9.225351"
|
||||||
],
|
],
|
||||||
|
@ -60,6 +60,8 @@ TRANSITION_BLOCKLIST = (
|
|||||||
(4456, 1011, "1.0.0", "2.00.00"),
|
(4456, 1011, "1.0.0", "2.00.00"),
|
||||||
(4488, 260, "1.0", "1.0.0"),
|
(4488, 260, "1.0", "1.0.0"),
|
||||||
(4488, 514, "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, 24875, "1.0", "27.0"),
|
||||||
(4999, 25057, "1.0", "27.0"),
|
(4999, 25057, "1.0", "27.0"),
|
||||||
(5009, 514, "1.0", "1.0.0"),
|
(5009, 514, "1.0", "1.0.0"),
|
||||||
|
@ -229,12 +229,12 @@ DISCOVERY_SCHEMAS = [
|
|||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
translation_key="startup_on_off",
|
translation_key="startup_on_off",
|
||||||
options=["On", "Off", "Toggle", "Previous"],
|
options=["On", "Off", "Toggle", "Previous"],
|
||||||
measurement_to_ha=lambda x: {
|
measurement_to_ha=lambda x: { # pylint: disable=unnecessary-lambda
|
||||||
0: "Off",
|
0: "Off",
|
||||||
1: "On",
|
1: "On",
|
||||||
2: "Toggle",
|
2: "Toggle",
|
||||||
None: "Previous",
|
None: "Previous",
|
||||||
}[x],
|
}.get(x),
|
||||||
ha_to_native_value=lambda x: {
|
ha_to_native_value=lambda x: {
|
||||||
"Off": 0,
|
"Off": 0,
|
||||||
"On": 1,
|
"On": 1,
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["google_nest_sdm"],
|
"loggers": ["google_nest_sdm"],
|
||||||
"quality_scale": "platinum",
|
"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]:
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
"""Fetch data from NextBus."""
|
"""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:
|
def _update_data() -> dict:
|
||||||
"""Fetch data from NextBus."""
|
"""Fetch data from NextBus."""
|
||||||
self.logger.debug("Updating data from API (executor)")
|
self.logger.debug("Updating data from API (executor)")
|
||||||
predictions: dict[RouteStop, dict[str, Any]] = {}
|
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]] = []
|
prediction_results: list[dict[str, Any]] = []
|
||||||
try:
|
try:
|
||||||
prediction_results = self.client.predictions_for_stop(
|
prediction_results = self.client.predictions_for_stop(
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["roborock"],
|
"loggers": ["roborock"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"python-roborock==2.5.0",
|
"python-roborock==2.6.0",
|
||||||
"vacuum-map-parser-roborock==0.1.2"
|
"vacuum-map-parser-roborock==0.1.2"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -682,6 +682,7 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
|||||||
self.entry.async_create_background_task(
|
self.entry.async_create_background_task(
|
||||||
self.hass, self._async_connected(), "rpc device init", eager_start=True
|
self.hass, self._async_connected(), "rpc device init", eager_start=True
|
||||||
)
|
)
|
||||||
|
# Make sure entities are marked available
|
||||||
self.async_set_updated_data(None)
|
self.async_set_updated_data(None)
|
||||||
elif update_type is RpcUpdateType.DISCONNECTED:
|
elif update_type is RpcUpdateType.DISCONNECTED:
|
||||||
self.entry.async_create_background_task(
|
self.entry.async_create_background_task(
|
||||||
@ -690,6 +691,8 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
|||||||
"rpc device disconnected",
|
"rpc device disconnected",
|
||||||
eager_start=True,
|
eager_start=True,
|
||||||
)
|
)
|
||||||
|
# Make sure entities are marked as unavailable
|
||||||
|
self.async_set_updated_data(None)
|
||||||
elif update_type is RpcUpdateType.STATUS:
|
elif update_type is RpcUpdateType.STATUS:
|
||||||
self.async_set_updated_data(None)
|
self.async_set_updated_data(None)
|
||||||
if self.sleep_period:
|
if self.sleep_period:
|
||||||
@ -711,7 +714,8 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
|||||||
"""Shutdown the coordinator."""
|
"""Shutdown the coordinator."""
|
||||||
if self.device.connected:
|
if self.device.connected:
|
||||||
try:
|
try:
|
||||||
await async_stop_scanner(self.device)
|
if not self.sleep_period:
|
||||||
|
await async_stop_scanner(self.device)
|
||||||
await super().shutdown()
|
await super().shutdown()
|
||||||
except InvalidAuthError:
|
except InvalidAuthError:
|
||||||
self.entry.async_start_reauth(self.hass)
|
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_unique_id = f"{coordinator.mac}-{key}"
|
||||||
self._attr_name = get_rpc_entity_name(coordinator.device, 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
|
@property
|
||||||
def status(self) -> dict:
|
def status(self) -> dict:
|
||||||
"""Device status by entity key."""
|
"""Device status by entity key."""
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aioshelly"],
|
"loggers": ["aioshelly"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aioshelly==11.2.0"],
|
"requirements": ["aioshelly==11.2.4"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_http._tcp.local.",
|
"type": "_http._tcp.local.",
|
||||||
|
@ -172,10 +172,17 @@ async def async_browse_media(
|
|||||||
|
|
||||||
# Check for config entry specifier, and extract Spotify URI
|
# Check for config entry specifier, and extract Spotify URI
|
||||||
parsed_url = yarl.URL(media_content_id)
|
parsed_url = yarl.URL(media_content_id)
|
||||||
|
host = parsed_url.host
|
||||||
|
|
||||||
if (
|
if (
|
||||||
parsed_url.host is None
|
host is None
|
||||||
or (entry := hass.config_entries.async_get_entry(parsed_url.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)
|
or not isinstance(entry.runtime_data, HomeAssistantSpotifyData)
|
||||||
):
|
):
|
||||||
raise BrowseError("Invalid Spotify account specified")
|
raise BrowseError("Invalid Spotify account specified")
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/thethingsnetwork",
|
"documentation": "https://www.home-assistant.io/integrations/thethingsnetwork",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_polling",
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/tplink_omada",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"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
|
"LAP-C202S-WUSR": "Core200S", # Alt ID Model Core200S
|
||||||
"Core300S": "Core300S",
|
"Core300S": "Core300S",
|
||||||
"LAP-C301S-WJP": "Core300S", # Alt ID Model Core300S
|
"LAP-C301S-WJP": "Core300S", # Alt ID Model Core300S
|
||||||
|
"LAP-C301S-WAAA": "Core300S", # Alt ID Model Core300S
|
||||||
"Core400S": "Core400S",
|
"Core400S": "Core400S",
|
||||||
"LAP-C401S-WJP": "Core400S", # Alt ID Model Core400S
|
"LAP-C401S-WJP": "Core400S", # Alt ID Model Core400S
|
||||||
"LAP-C401S-WUSR": "Core400S", # Alt ID Model Core400S
|
"LAP-C401S-WUSR": "Core400S", # Alt ID Model Core400S
|
||||||
|
@ -46,7 +46,9 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
except WLEDConnectionError:
|
except WLEDConnectionError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
else:
|
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(
|
self._abort_if_unique_id_configured(
|
||||||
updates={CONF_HOST: user_input[CONF_HOST]}
|
updates={CONF_HOST: user_input[CONF_HOST]}
|
||||||
)
|
)
|
||||||
@ -56,8 +58,6 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_HOST: user_input[CONF_HOST],
|
CONF_HOST: user_input[CONF_HOST],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
user_input = {}
|
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
|
@ -92,7 +92,7 @@ def _get_obj_holidays(
|
|||||||
subdiv=province,
|
subdiv=province,
|
||||||
years=year,
|
years=year,
|
||||||
language=language,
|
language=language,
|
||||||
categories=set_categories, # type: ignore[arg-type]
|
categories=set_categories,
|
||||||
)
|
)
|
||||||
if (supported_languages := obj_holidays.supported_languages) and language == "en":
|
if (supported_languages := obj_holidays.supported_languages) and language == "en":
|
||||||
for lang in supported_languages:
|
for lang in supported_languages:
|
||||||
@ -102,7 +102,7 @@ def _get_obj_holidays(
|
|||||||
subdiv=province,
|
subdiv=province,
|
||||||
years=year,
|
years=year,
|
||||||
language=lang,
|
language=lang,
|
||||||
categories=set_categories, # type: ignore[arg-type]
|
categories=set_categories,
|
||||||
)
|
)
|
||||||
LOGGER.debug("Changing language from %s to %s", language, lang)
|
LOGGER.debug("Changing language from %s to %s", language, lang)
|
||||||
return obj_holidays
|
return obj_holidays
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["holidays"],
|
"loggers": ["holidays"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["holidays==0.53"]
|
"requirements": ["holidays==0.55"]
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 8
|
MINOR_VERSION: Final = 8
|
||||||
PATCH_VERSION: Final = "2"
|
PATCH_VERSION: Final = "3"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||||
|
@ -4,7 +4,7 @@ aiodhcpwatcher==1.0.2
|
|||||||
aiodiscover==2.1.0
|
aiodiscover==2.1.0
|
||||||
aiodns==3.2.0
|
aiodns==3.2.0
|
||||||
aiohttp-fast-zlib==0.1.1
|
aiohttp-fast-zlib==0.1.1
|
||||||
aiohttp==3.10.3
|
aiohttp==3.10.5
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
aiozoneinfo==0.2.1
|
aiozoneinfo==0.2.1
|
||||||
astral==2.2
|
astral==2.2
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.8.2"
|
version = "2024.8.3"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
@ -24,7 +24,7 @@ classifiers = [
|
|||||||
requires-python = ">=3.12.0"
|
requires-python = ">=3.12.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiodns==3.2.0",
|
"aiodns==3.2.0",
|
||||||
"aiohttp==3.10.3",
|
"aiohttp==3.10.5",
|
||||||
"aiohttp_cors==0.7.0",
|
"aiohttp_cors==0.7.0",
|
||||||
"aiohttp-fast-zlib==0.1.1",
|
"aiohttp-fast-zlib==0.1.1",
|
||||||
"aiozoneinfo==0.2.1",
|
"aiozoneinfo==0.2.1",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# Home Assistant Core
|
# Home Assistant Core
|
||||||
aiodns==3.2.0
|
aiodns==3.2.0
|
||||||
aiohttp==3.10.3
|
aiohttp==3.10.5
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
aiohttp-fast-zlib==0.1.1
|
aiohttp-fast-zlib==0.1.1
|
||||||
aiozoneinfo==0.2.1
|
aiozoneinfo==0.2.1
|
||||||
|
@ -255,10 +255,10 @@ aioguardian==2022.07.0
|
|||||||
aioharmony==0.2.10
|
aioharmony==0.2.10
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==3.2.2
|
aiohomekit==3.2.3
|
||||||
|
|
||||||
# homeassistant.components.hue
|
# homeassistant.components.hue
|
||||||
aiohue==4.7.2
|
aiohue==4.7.3
|
||||||
|
|
||||||
# homeassistant.components.imap
|
# homeassistant.components.imap
|
||||||
aioimaplib==1.1.0
|
aioimaplib==1.1.0
|
||||||
@ -359,7 +359,7 @@ aioruuvigateway==0.1.0
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==11.2.0
|
aioshelly==11.2.4
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
@ -989,7 +989,7 @@ google-cloud-texttospeech==2.16.3
|
|||||||
google-generativeai==0.6.0
|
google-generativeai==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==4.0.6
|
google-nest-sdm==4.0.7
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -1096,7 +1096,7 @@ hole==0.8.0
|
|||||||
|
|
||||||
# homeassistant.components.holiday
|
# homeassistant.components.holiday
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.53
|
holidays==0.55
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20240809.0
|
home-assistant-frontend==20240809.0
|
||||||
@ -1759,7 +1759,7 @@ pyblu==0.4.0
|
|||||||
pybotvac==0.0.25
|
pybotvac==0.0.25
|
||||||
|
|
||||||
# homeassistant.components.braviatv
|
# homeassistant.components.braviatv
|
||||||
pybravia==0.3.3
|
pybravia==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.nissan_leaf
|
# homeassistant.components.nissan_leaf
|
||||||
pycarwings2==2.14
|
pycarwings2==2.14
|
||||||
@ -1912,7 +1912,7 @@ pyhiveapi==0.5.16
|
|||||||
pyhomematic==0.1.77
|
pyhomematic==0.1.77
|
||||||
|
|
||||||
# homeassistant.components.homeworks
|
# homeassistant.components.homeworks
|
||||||
pyhomeworks==1.1.1
|
pyhomeworks==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.ialarm
|
# homeassistant.components.ialarm
|
||||||
pyialarm==2.2.0
|
pyialarm==2.2.0
|
||||||
@ -2341,7 +2341,7 @@ python-rabbitair==0.0.8
|
|||||||
python-ripple-api==0.0.3
|
python-ripple-api==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==2.5.0
|
python-roborock==2.6.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.36
|
python-smarttub==0.0.36
|
||||||
@ -2792,7 +2792,7 @@ total-connect-client==2024.5
|
|||||||
tp-connected==0.0.4
|
tp-connected==0.0.4
|
||||||
|
|
||||||
# homeassistant.components.tplink_omada
|
# homeassistant.components.tplink_omada
|
||||||
tplink-omada-client==1.3.12
|
tplink-omada-client==1.4.2
|
||||||
|
|
||||||
# homeassistant.components.transmission
|
# homeassistant.components.transmission
|
||||||
transmission-rpc==7.0.3
|
transmission-rpc==7.0.3
|
||||||
@ -2801,7 +2801,7 @@ transmission-rpc==7.0.3
|
|||||||
ttls==1.8.3
|
ttls==1.8.3
|
||||||
|
|
||||||
# homeassistant.components.thethingsnetwork
|
# homeassistant.components.thethingsnetwork
|
||||||
ttn_client==1.1.0
|
ttn_client==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.tuya
|
# homeassistant.components.tuya
|
||||||
tuya-device-sharing-sdk==0.1.9
|
tuya-device-sharing-sdk==0.1.9
|
||||||
@ -2936,7 +2936,7 @@ xbox-webapi==2.0.11
|
|||||||
xiaomi-ble==0.30.2
|
xiaomi-ble==0.30.2
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknx==3.1.0
|
xknx==3.1.1
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknxproject==3.7.1
|
xknxproject==3.7.1
|
||||||
@ -2959,7 +2959,7 @@ yalesmartalarmclient==0.3.9
|
|||||||
yalexs-ble==2.4.3
|
yalexs-ble==2.4.3
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==6.4.3
|
yalexs==8.4.1
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
|
@ -240,10 +240,10 @@ aioguardian==2022.07.0
|
|||||||
aioharmony==0.2.10
|
aioharmony==0.2.10
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==3.2.2
|
aiohomekit==3.2.3
|
||||||
|
|
||||||
# homeassistant.components.hue
|
# homeassistant.components.hue
|
||||||
aiohue==4.7.2
|
aiohue==4.7.3
|
||||||
|
|
||||||
# homeassistant.components.imap
|
# homeassistant.components.imap
|
||||||
aioimaplib==1.1.0
|
aioimaplib==1.1.0
|
||||||
@ -341,7 +341,7 @@ aioruuvigateway==0.1.0
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==11.2.0
|
aioshelly==11.2.4
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
@ -833,7 +833,7 @@ google-cloud-pubsub==2.13.11
|
|||||||
google-generativeai==0.6.0
|
google-generativeai==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==4.0.6
|
google-nest-sdm==4.0.7
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -916,7 +916,7 @@ hole==0.8.0
|
|||||||
|
|
||||||
# homeassistant.components.holiday
|
# homeassistant.components.holiday
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.53
|
holidays==0.55
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20240809.0
|
home-assistant-frontend==20240809.0
|
||||||
@ -1421,7 +1421,7 @@ pyblu==0.4.0
|
|||||||
pybotvac==0.0.25
|
pybotvac==0.0.25
|
||||||
|
|
||||||
# homeassistant.components.braviatv
|
# homeassistant.components.braviatv
|
||||||
pybravia==0.3.3
|
pybravia==0.3.4
|
||||||
|
|
||||||
# homeassistant.components.cloudflare
|
# homeassistant.components.cloudflare
|
||||||
pycfdns==3.0.0
|
pycfdns==3.0.0
|
||||||
@ -1523,7 +1523,7 @@ pyhiveapi==0.5.16
|
|||||||
pyhomematic==0.1.77
|
pyhomematic==0.1.77
|
||||||
|
|
||||||
# homeassistant.components.homeworks
|
# homeassistant.components.homeworks
|
||||||
pyhomeworks==1.1.1
|
pyhomeworks==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.ialarm
|
# homeassistant.components.ialarm
|
||||||
pyialarm==2.2.0
|
pyialarm==2.2.0
|
||||||
@ -1850,7 +1850,7 @@ python-picnic-api==1.1.0
|
|||||||
python-rabbitair==0.0.8
|
python-rabbitair==0.0.8
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==2.5.0
|
python-roborock==2.6.0
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.36
|
python-smarttub==0.0.36
|
||||||
@ -2190,7 +2190,7 @@ toonapi==0.3.0
|
|||||||
total-connect-client==2024.5
|
total-connect-client==2024.5
|
||||||
|
|
||||||
# homeassistant.components.tplink_omada
|
# homeassistant.components.tplink_omada
|
||||||
tplink-omada-client==1.3.12
|
tplink-omada-client==1.4.2
|
||||||
|
|
||||||
# homeassistant.components.transmission
|
# homeassistant.components.transmission
|
||||||
transmission-rpc==7.0.3
|
transmission-rpc==7.0.3
|
||||||
@ -2199,7 +2199,7 @@ transmission-rpc==7.0.3
|
|||||||
ttls==1.8.3
|
ttls==1.8.3
|
||||||
|
|
||||||
# homeassistant.components.thethingsnetwork
|
# homeassistant.components.thethingsnetwork
|
||||||
ttn_client==1.1.0
|
ttn_client==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.tuya
|
# homeassistant.components.tuya
|
||||||
tuya-device-sharing-sdk==0.1.9
|
tuya-device-sharing-sdk==0.1.9
|
||||||
@ -2316,7 +2316,7 @@ xbox-webapi==2.0.11
|
|||||||
xiaomi-ble==0.30.2
|
xiaomi-ble==0.30.2
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknx==3.1.0
|
xknx==3.1.1
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknxproject==3.7.1
|
xknxproject==3.7.1
|
||||||
@ -2336,7 +2336,7 @@ yalesmartalarmclient==0.3.9
|
|||||||
yalexs-ble==2.4.3
|
yalexs-ble==2.4.3
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==6.4.3
|
yalexs==8.4.1
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
|
@ -124,7 +124,6 @@ EXCEPTIONS = {
|
|||||||
"PyXiaomiGateway", # https://github.com/Danielhiversen/PyXiaomiGateway/pull/201
|
"PyXiaomiGateway", # https://github.com/Danielhiversen/PyXiaomiGateway/pull/201
|
||||||
"aiocomelit", # https://github.com/chemelli74/aiocomelit/pull/138
|
"aiocomelit", # https://github.com/chemelli74/aiocomelit/pull/138
|
||||||
"aioecowitt", # https://github.com/home-assistant-libs/aioecowitt/pull/180
|
"aioecowitt", # https://github.com/home-assistant-libs/aioecowitt/pull/180
|
||||||
"aiohappyeyeballs", # Python-2.0.1
|
|
||||||
"aioopenexchangerates", # https://github.com/MartinHjelmare/aioopenexchangerates/pull/94
|
"aioopenexchangerates", # https://github.com/MartinHjelmare/aioopenexchangerates/pull/94
|
||||||
"aiooui", # https://github.com/Bluetooth-Devices/aiooui/pull/8
|
"aiooui", # https://github.com/Bluetooth-Devices/aiooui/pull/8
|
||||||
"aioruuvigateway", # https://github.com/akx/aioruuvigateway/pull/6
|
"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["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "invalid_version"
|
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,
|
"current_activity": current_activity,
|
||||||
"activity_list": activity_list,
|
"activity_list": activity_list,
|
||||||
|
"supported_features": 4,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
msg = await smart_home.async_handle_message(hass, get_default_config(hass), request)
|
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(
|
hass.states.async_set(
|
||||||
"remote.unknown",
|
"remote.unknown",
|
||||||
"on",
|
"on",
|
||||||
{"current_activity": "UNKNOWN"},
|
{
|
||||||
|
"current_activity": "UNKNOWN",
|
||||||
|
"supported_features": 4,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
"remote.tv",
|
"remote.tv",
|
||||||
"on",
|
"on",
|
||||||
{"current_activity": "TV", "activity_list": ["TV", "MUSIC", "DVD"]},
|
{
|
||||||
|
"current_activity": "TV",
|
||||||
|
"activity_list": ["TV", "MUSIC", "DVD"],
|
||||||
|
"supported_features": 4,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
"remote.music",
|
"remote.music",
|
||||||
"on",
|
"on",
|
||||||
{"current_activity": "MUSIC", "activity_list": ["TV", "MUSIC", "DVD"]},
|
{
|
||||||
|
"current_activity": "MUSIC",
|
||||||
|
"activity_list": ["TV", "MUSIC", "DVD"],
|
||||||
|
"supported_features": 4,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
"remote.dvd",
|
"remote.dvd",
|
||||||
"on",
|
"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")
|
properties = await reported_properties(hass, "remote#unknown")
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from yalexs.manager.ratelimit import _RateLimitChecker
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_discovery", autouse=True)
|
@pytest.fixture(name="mock_discovery", autouse=True)
|
||||||
@ -12,3 +13,10 @@ def mock_discovery_fixture():
|
|||||||
"homeassistant.components.august.data.discovery_flow.async_create_flow"
|
"homeassistant.components.august.data.discovery_flow.async_create_flow"
|
||||||
) as mock_discovery:
|
) as mock_discovery:
|
||||||
yield 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,
|
DoorOperationActivity,
|
||||||
LockOperationActivity,
|
LockOperationActivity,
|
||||||
)
|
)
|
||||||
from yalexs.authenticator import AuthenticationState
|
from yalexs.authenticator_common import AuthenticationState
|
||||||
from yalexs.const import Brand
|
from yalexs.const import Brand
|
||||||
from yalexs.doorbell import Doorbell, DoorbellDetail
|
from yalexs.doorbell import Doorbell, DoorbellDetail
|
||||||
from yalexs.lock import Lock, LockDetail
|
from yalexs.lock import Lock, LockDetail
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import patch
|
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 yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
@ -50,5 +50,5 @@ async def _patched_refresh_access_token(
|
|||||||
)
|
)
|
||||||
await august_gateway.async_refresh_access_token_if_needed()
|
await august_gateway.async_refresh_access_token_if_needed()
|
||||||
refresh_access_token_mock.assert_called()
|
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
|
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(
|
aioclient_mock.get(
|
||||||
"https://habitica.com/api/v3/tasks/user",
|
"https://habitica.com/api/v3/tasks/user",
|
||||||
json={
|
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(
|
aioclient_mock.post(
|
||||||
"https://habitica.com/api/v3/tasks/user",
|
"https://habitica.com/api/v3/tasks/user",
|
||||||
|
@ -1288,7 +1288,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"button": {
|
"button": {
|
||||||
"last_event": "short_release"
|
"button_report": {
|
||||||
|
"event": "short_release"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id": "c658d3d8-a013-4b81-8ac6-78b248537e70",
|
"id": "c658d3d8-a013-4b81-8ac6-78b248537e70",
|
||||||
"id_v1": "/sensors/50",
|
"id_v1": "/sensors/50",
|
||||||
@ -1327,7 +1329,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"button": {
|
"button": {
|
||||||
"last_event": "short_release"
|
"button_report": {
|
||||||
|
"event": "short_release"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id": "7f1ab9f6-cc2b-4b40-9011-65e2af153f75",
|
"id": "7f1ab9f6-cc2b-4b40-9011-65e2af153f75",
|
||||||
"id_v1": "/sensors/10",
|
"id_v1": "/sensors/10",
|
||||||
@ -1366,7 +1370,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"button": {
|
"button": {
|
||||||
"last_event": "short_release"
|
"button_report": {
|
||||||
|
"event": "short_release"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id": "31cffcda-efc2-401f-a152-e10db3eed232",
|
"id": "31cffcda-efc2-401f-a152-e10db3eed232",
|
||||||
"id_v1": "/sensors/5",
|
"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
|
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(
|
async def test_block_sleeping_device_connection_error(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
|
@ -43,7 +43,7 @@ from . import (
|
|||||||
register_entity,
|
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
|
RELAY_BLOCK_ID = 0
|
||||||
SENSOR_BLOCK_ID = 3
|
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)
|
entry = entity_registry.async_get(entity_id)
|
||||||
assert not entry
|
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