This commit is contained in:
Franck Nijhof 2025-07-28 10:15:08 +02:00 committed by GitHub
commit 777b3128bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 288 additions and 94 deletions

View File

@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "bronze",
"requirements": ["aioamazondevices==3.5.0"]
"requirements": ["aioamazondevices==3.5.1"]
}

View File

@ -15,6 +15,7 @@ from homeassistant.components.climate import (
FAN_HIGH,
FAN_LOW,
FAN_MEDIUM,
FAN_OFF,
SWING_BOTH,
SWING_HORIZONTAL,
SWING_OFF,
@ -31,6 +32,7 @@ from .coordinator import FGLairConfigEntry, FGLairCoordinator
from .entity import FGLairEntity
HA_TO_FUJI_FAN = {
FAN_OFF: FanSpeed.QUIET,
FAN_LOW: FanSpeed.LOW,
FAN_MEDIUM: FanSpeed.MEDIUM,
FAN_HIGH: FanSpeed.HIGH,

View File

@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["habiticalib"],
"quality_scale": "platinum",
"requirements": ["habiticalib==0.4.0"]
"requirements": ["habiticalib==0.4.1"]
}

View File

@ -10,6 +10,9 @@ import aiolifx_effects as aiolifx_effects_module
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_BRIGHTNESS_STEP,
ATTR_BRIGHTNESS_STEP_PCT,
ATTR_EFFECT,
ATTR_TRANSITION,
LIGHT_TURN_ON_SCHEMA,
@ -234,6 +237,20 @@ class LIFXLight(LIFXEntity, LightEntity):
else:
fade = 0
if ATTR_BRIGHTNESS_STEP in kwargs or ATTR_BRIGHTNESS_STEP_PCT in kwargs:
brightness = self.brightness if self.is_on and self.brightness else 0
if ATTR_BRIGHTNESS_STEP in kwargs:
brightness += kwargs.pop(ATTR_BRIGHTNESS_STEP)
else:
brightness_pct = round(brightness / 255 * 100)
brightness = round(
(brightness_pct + kwargs.pop(ATTR_BRIGHTNESS_STEP_PCT)) / 100 * 255
)
kwargs[ATTR_BRIGHTNESS] = max(0, min(255, brightness))
# These are both False if ATTR_POWER is not set
power_on = kwargs.get(ATTR_POWER, False)
power_off = not kwargs.get(ATTR_POWER, True)

View File

@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from chip.clusters import Objects as clusters
from chip.clusters.Objects import NullValue
from matter_server.client.models import device_types
from homeassistant.components.light import (
@ -241,7 +242,7 @@ class MatterLight(MatterEntity, LightEntity):
return int(color_temp)
def _get_brightness(self) -> int:
def _get_brightness(self) -> int | None:
"""Get brightness from matter."""
level_control = self._endpoint.get_cluster(clusters.LevelControl)
@ -255,6 +256,10 @@ class MatterLight(MatterEntity, LightEntity):
self.entity_id,
)
if level_control.currentLevel is NullValue:
# currentLevel is a nullable value.
return None
return round(
renormalize(
level_control.currentLevel,

View File

@ -218,6 +218,9 @@ def _async_fix_device_id(
for device_entry in device_entries:
unique_id = str(next(iter(device_entry.identifiers))[1])
device_entry_map[unique_id] = device_entry
if unique_id.startswith(mac_address):
# Already in the correct format
continue
if (suffix := unique_id.removeprefix(str(serial_number))) != unique_id:
migrations[unique_id] = f"{mac_address}{suffix}"

View File

@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/schlage",
"iot_class": "cloud_polling",
"requirements": ["pyschlage==2025.4.0"]
"requirements": ["pyschlage==2025.7.2"]
}

View File

@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["pysuez", "regex"],
"quality_scale": "bronze",
"requirements": ["pysuezV2==2.0.5"]
"requirements": ["pysuezV2==2.0.7"]
}

View File

@ -82,7 +82,7 @@ class PushBot(BaseTelegramBot):
self.base_url = config.data.get(CONF_URL) or get_url(
hass, require_ssl=True, allow_internal=False
)
self.webhook_url = f"{self.base_url}{TELEGRAM_WEBHOOK_URL}"
self.webhook_url = self.base_url + _get_webhook_url(bot)
async def shutdown(self) -> None:
"""Shutdown the app."""
@ -98,9 +98,11 @@ class PushBot(BaseTelegramBot):
api_kwargs={"secret_token": self.secret_token},
connect_timeout=5,
)
except TelegramError:
except TelegramError as err:
retry_num += 1
_LOGGER.warning("Error trying to set webhook (retry #%d)", retry_num)
_LOGGER.warning(
"Error trying to set webhook (retry #%d)", retry_num, exc_info=err
)
return False
@ -143,7 +145,6 @@ class PushBotView(HomeAssistantView):
"""View for handling webhook calls from Telegram."""
requires_auth = False
url = TELEGRAM_WEBHOOK_URL
name = "telegram_webhooks"
def __init__(
@ -160,6 +161,7 @@ class PushBotView(HomeAssistantView):
self.application = application
self.trusted_networks = trusted_networks
self.secret_token = secret_token
self.url = _get_webhook_url(bot)
async def post(self, request: HomeAssistantRequest) -> Response | None:
"""Accept the POST from telegram."""
@ -183,3 +185,7 @@ class PushBotView(HomeAssistantView):
await self.application.process_update(update)
return None
def _get_webhook_url(bot: Bot) -> str:
return f"{TELEGRAM_WEBHOOK_URL}_{bot.id}"

View File

@ -14,9 +14,8 @@ CONF_REFRESH_TOKEN = "refresh_token"
LOGGER = logging.getLogger(__package__)
CLIENT_ID = "71b813eb-4a2e-483a-b831-4dec5cb9bf0d"
AUTHORIZE_URL = "https://auth.tesla.com/oauth2/v3/authorize"
TOKEN_URL = "https://auth.tesla.com/oauth2/v3/token"
AUTHORIZE_URL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/authorize"
TOKEN_URL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token"
SCOPES = [
Scope.OPENID,

View File

@ -23,7 +23,7 @@ async def async_setup_entry(
entities: list[WebControlProGenericEntity] = [
WebControlProIdentifyButton(config_entry.entry_id, dest)
for dest in hub.dests.values()
if dest.action(WMS_WebControl_pro_API_actionDescription.Identify)
if dest.hasAction(WMS_WebControl_pro_API_actionDescription.Identify)
]
async_add_entities(entities)

View File

@ -32,9 +32,9 @@ async def async_setup_entry(
entities: list[WebControlProGenericEntity] = []
for dest in hub.dests.values():
if dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive):
if dest.hasAction(WMS_WebControl_pro_API_actionDescription.AwningDrive):
entities.append(WebControlProAwning(config_entry.entry_id, dest))
elif dest.action(
elif dest.hasAction(
WMS_WebControl_pro_API_actionDescription.RollerShutterBlindDrive
):
entities.append(WebControlProRollerShutter(config_entry.entry_id, dest))

View File

@ -33,9 +33,9 @@ async def async_setup_entry(
entities: list[WebControlProGenericEntity] = []
for dest in hub.dests.values():
if dest.action(WMS_WebControl_pro_API_actionDescription.LightDimming):
if dest.hasAction(WMS_WebControl_pro_API_actionDescription.LightDimming):
entities.append(WebControlProDimmer(config_entry.entry_id, dest))
elif dest.action(WMS_WebControl_pro_API_actionDescription.LightSwitch):
elif dest.hasAction(WMS_WebControl_pro_API_actionDescription.LightSwitch):
entities.append(WebControlProLight(config_entry.entry_id, dest))
async_add_entities(entities)

View File

@ -14,5 +14,5 @@
"documentation": "https://www.home-assistant.io/integrations/wmspro",
"integration_type": "hub",
"iot_class": "local_polling",
"requirements": ["pywmspro==0.3.0"]
"requirements": ["pywmspro==0.3.2"]
}

View File

@ -6,5 +6,5 @@
"iot_class": "cloud_push",
"loggers": ["pyasn1", "slixmpp"],
"quality_scale": "legacy",
"requirements": ["slixmpp==1.8.5", "emoji==2.8.0"]
"requirements": ["slixmpp==1.10.0", "emoji==2.8.0"]
}

View File

@ -144,7 +144,8 @@ async def async_send_message( # noqa: C901
self.loop = hass.loop
self.force_starttls = use_tls
self.enable_starttls = use_tls
self.enable_direct_tls = use_tls
self.use_ipv6 = False
self.add_event_handler("failed_all_auth", self.disconnect_on_login_fail)
self.add_event_handler("session_start", self.start)
@ -163,7 +164,7 @@ async def async_send_message( # noqa: C901
self.register_plugin("xep_0128") # Service Discovery
self.register_plugin("xep_0363") # HTTP upload
self.connect(force_starttls=self.force_starttls, use_ssl=False)
self.connect()
async def start(self, event):
"""Start the communication and sends the message."""

View File

@ -495,10 +495,23 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self._usb_discovery = True
if current_config_entries:
return await self.async_step_intent_migrate()
return await self.async_step_confirm_usb_migration()
return await self.async_step_installation_type()
async def async_step_confirm_usb_migration(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm USB migration."""
if user_input is not None:
return await self.async_step_intent_migrate()
return self.async_show_form(
step_id="confirm_usb_migration",
description_placeholders={
"usb_title": self.context["title_placeholders"][CONF_NAME],
},
)
async def async_step_manual(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from collections.abc import Sequence
from typing import Any
from zwave_js_server.const import NodeStatus
from zwave_js_server.exceptions import BaseZwaveJSServerError
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import (
@ -27,8 +26,6 @@ from .discovery import ZwaveDiscoveryInfo
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
EVENT_VALUE_REMOVED = "value removed"
EVENT_DEAD = "dead"
EVENT_ALIVE = "alive"
class ZWaveBaseEntity(Entity):
@ -141,11 +138,6 @@ class ZWaveBaseEntity(Entity):
)
)
for status_event in (EVENT_ALIVE, EVENT_DEAD):
self.async_on_remove(
self.info.node.on(status_event, self._node_status_alive_or_dead)
)
self.async_on_remove(
async_dispatcher_connect(
self.hass,
@ -211,19 +203,7 @@ class ZWaveBaseEntity(Entity):
@property
def available(self) -> bool:
"""Return entity availability."""
return (
self.driver.client.connected
and bool(self.info.node.ready)
and self.info.node.status != NodeStatus.DEAD
)
@callback
def _node_status_alive_or_dead(self, event_data: dict) -> None:
"""Call when node status changes to alive or dead.
Should not be overridden by subclasses.
"""
self.async_write_ha_state()
return self.driver.client.connected and bool(self.info.node.ready)
@callback
def _value_changed(self, event_data: dict) -> None:

View File

@ -108,6 +108,10 @@
"start_addon": {
"title": "Configuring add-on"
},
"confirm_usb_migration": {
"description": "You are about to migrate your Z-Wave network from the old adapter to the new adapter {usb_title}. This will take a backup of the network from the old adapter and restore the network to the new adapter.\n\nPress Submit to continue with the migration.",
"title": "Migrate to a new adapter"
},
"zeroconf_confirm": {
"description": "Do you want to add the Z-Wave Server with home ID {home_id} found at {url} to Home Assistant?",
"title": "Discovered Z-Wave Server"

View File

@ -200,18 +200,13 @@ class ZWaveNodeFirmwareUpdate(UpdateEntity):
)
return
# If device is asleep/dead, wait for it to wake up/become alive before
# attempting an update
for status, event_name in (
(NodeStatus.ASLEEP, "wake up"),
(NodeStatus.DEAD, "alive"),
):
if self.node.status == status:
if not self._status_unsub:
self._status_unsub = self.node.once(
event_name, self._update_on_status_change
)
return
# If device is asleep, wait for it to wake up before attempting an update
if self.node.status == NodeStatus.ASLEEP:
if not self._status_unsub:
self._status_unsub = self.node.once(
"wake up", self._update_on_status_change
)
return
try:
# Retrieve all firmware updates including non-stable ones but filter

View File

@ -25,7 +25,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2025
MINOR_VERSION: Final = 7
PATCH_VERSION: Final = "3"
PATCH_VERSION: Final = "4"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2025.7.3"
version = "2025.7.4"
license = "Apache-2.0"
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
description = "Open-source home automation platform running on Python 3."

12
requirements_all.txt generated
View File

@ -185,7 +185,7 @@ aioairzone-cloud==0.6.12
aioairzone==1.0.0
# homeassistant.components.alexa_devices
aioamazondevices==3.5.0
aioamazondevices==3.5.1
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@ -1121,7 +1121,7 @@ ha-philipsjs==3.2.2
ha-silabs-firmware-client==0.2.0
# homeassistant.components.habitica
habiticalib==0.4.0
habiticalib==0.4.1
# homeassistant.components.bluetooth
habluetooth==3.49.0
@ -2309,7 +2309,7 @@ pysabnzbd==1.1.1
pysaj==0.0.16
# homeassistant.components.schlage
pyschlage==2025.4.0
pyschlage==2025.7.2
# homeassistant.components.sensibo
pysensibo==1.2.1
@ -2384,7 +2384,7 @@ pysqueezebox==0.12.1
pystiebeleltron==0.1.0
# homeassistant.components.suez_water
pysuezV2==2.0.5
pysuezV2==2.0.7
# homeassistant.components.switchbee
pyswitchbee==1.8.3
@ -2596,7 +2596,7 @@ pywilight==0.0.74
pywizlight==0.6.3
# homeassistant.components.wmspro
pywmspro==0.3.0
pywmspro==0.3.2
# homeassistant.components.ws66i
pyws66i==1.1
@ -2786,7 +2786,7 @@ skyboxremote==0.0.6
slack_sdk==3.33.4
# homeassistant.components.xmpp
slixmpp==1.8.5
slixmpp==1.10.0
# homeassistant.components.smart_meter_texas
smart-meter-texas==0.5.5

View File

@ -173,7 +173,7 @@ aioairzone-cloud==0.6.12
aioairzone==1.0.0
# homeassistant.components.alexa_devices
aioamazondevices==3.5.0
aioamazondevices==3.5.1
# homeassistant.components.ambient_network
# homeassistant.components.ambient_station
@ -982,7 +982,7 @@ ha-philipsjs==3.2.2
ha-silabs-firmware-client==0.2.0
# homeassistant.components.habitica
habiticalib==0.4.0
habiticalib==0.4.1
# homeassistant.components.bluetooth
habluetooth==3.49.0
@ -1921,7 +1921,7 @@ pyrympro==0.0.9
pysabnzbd==1.1.1
# homeassistant.components.schlage
pyschlage==2025.4.0
pyschlage==2025.7.2
# homeassistant.components.sensibo
pysensibo==1.2.1
@ -1987,7 +1987,7 @@ pysqueezebox==0.12.1
pystiebeleltron==0.1.0
# homeassistant.components.suez_water
pysuezV2==2.0.5
pysuezV2==2.0.7
# homeassistant.components.switchbee
pyswitchbee==1.8.3
@ -2154,7 +2154,7 @@ pywilight==0.0.74
pywizlight==0.6.3
# homeassistant.components.wmspro
pywmspro==0.3.0
pywmspro==0.3.2
# homeassistant.components.ws66i
pyws66i==1.1

View File

@ -30,6 +30,8 @@ from homeassistant.components.lifx.manager import (
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_BRIGHTNESS_PCT,
ATTR_BRIGHTNESS_STEP,
ATTR_BRIGHTNESS_STEP_PCT,
ATTR_COLOR_MODE,
ATTR_COLOR_NAME,
ATTR_COLOR_TEMP_KELVIN,
@ -1735,6 +1737,48 @@ async def test_transitions_color_bulb(hass: HomeAssistant) -> None:
bulb.set_color.reset_mock()
async def test_lifx_set_state_brightness(hass: HomeAssistant) -> None:
"""Test lifx.set_state works with brightness, brightness_pct and brightness_step."""
config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
)
config_entry.add_to_hass(hass)
bulb = _mocked_bulb_new_firmware()
bulb.power_level = 65535
bulb.color = [0, 0, 32768, 3500]
with (
_patch_discovery(device=bulb),
_patch_config_flow_try_connect(device=bulb),
_patch_device(device=bulb),
):
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
await hass.async_block_till_done()
entity_id = "light.my_bulb"
# brightness_step should convert from 8 bit to 16 bit
await hass.services.async_call(
DOMAIN,
"set_state",
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_STEP: 128},
blocking=True,
)
assert bulb.set_color.calls[0][0][0] == [0, 0, 65535, 3500]
bulb.set_color.reset_mock()
# brightness_step_pct should convert from percentage to 16 bit
await hass.services.async_call(
DOMAIN,
"set_state",
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_STEP_PCT: 50},
blocking=True,
)
assert bulb.set_color.calls[0][0][0] == [0, 0, 65535, 3500]
bulb.set_color.reset_mock()
async def test_lifx_set_state_color(hass: HomeAssistant) -> None:
"""Test lifx.set_state works with color names and RGB."""
config_entry = MockConfigEntry(

View File

@ -131,6 +131,15 @@ async def test_dimmable_light(
) -> None:
"""Test a dimmable light."""
# Test for currentLevel is None
set_node_attribute(matter_node, 1, 8, 0, None)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state is not None
assert state.state == "on"
assert state.attributes["brightness"] is None
# Test that the light brightness is 50 (out of 254)
set_node_attribute(matter_node, 1, 8, 0, 50)
await trigger_subscription_callback(hass, matter_client)

View File

@ -449,3 +449,75 @@ async def test_fix_duplicate_device_ids(
assert device_entry.identifiers == {(DOMAIN, MAC_ADDRESS_UNIQUE_ID)}
assert device_entry.name_by_user == expected_device_name
assert device_entry.disabled_by == expected_disabled_by
async def test_reload_migration_with_leading_zero_mac(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test migration and reload of a device with a mac address with a leading zero."""
mac_address = "01:02:03:04:05:06"
mac_address_unique_id = dr.format_mac(mac_address)
serial_number = "0"
# Setup the config entry to be in a pre-migrated state
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=serial_number,
data={
"host": "127.0.0.1",
"password": "password",
CONF_MAC: mac_address,
"serial_number": serial_number,
},
)
config_entry.add_to_hass(hass)
# Create a device and entity with the old unique id format
device_entry = device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, f"{serial_number}-1")},
)
entity_entry = entity_registry.async_get_or_create(
"switch",
DOMAIN,
f"{serial_number}-1-zone1",
suggested_object_id="zone1",
config_entry=config_entry,
device_id=device_entry.id,
)
# Setup the integration, which will migrate the unique ids
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Verify the device and entity were migrated to the new format
migrated_device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, f"{mac_address_unique_id}-1")}
)
assert migrated_device_entry is not None
migrated_entity_entry = entity_registry.async_get(entity_entry.entity_id)
assert migrated_entity_entry is not None
assert migrated_entity_entry.unique_id == f"{mac_address_unique_id}-1-zone1"
# Reload the integration
await hass.config_entries.async_reload(config_entry.entry_id)
await hass.async_block_till_done()
# Verify the device and entity still have the correct identifiers and were not duplicated
reloaded_device_entry = device_registry.async_get(migrated_device_entry.id)
assert reloaded_device_entry is not None
assert reloaded_device_entry.identifiers == {(DOMAIN, f"{mac_address_unique_id}-1")}
reloaded_entity_entry = entity_registry.async_get(entity_entry.entity_id)
assert reloaded_entity_entry is not None
assert reloaded_entity_entry.unique_id == f"{mac_address_unique_id}-1-zone1"
assert (
len(dr.async_entries_for_config_entry(device_registry, config_entry.entry_id))
== 1
)
assert (
len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id))
== 1
)

View File

@ -87,5 +87,7 @@ def mock_suez_client(recorder_mock: Recorder) -> Generator[AsyncMock]:
)
suez_client.fetch_aggregated_data.return_value = result
suez_client.get_price.return_value = PriceResult("4.74")
suez_client.get_price.return_value = PriceResult(
"OK", {"price": 4.74}, "Price is 4.74"
)
yield suez_client

View File

@ -364,7 +364,7 @@ async def test_webhook_endpoint_generates_telegram_text_event(
events = async_capture_events(hass, "telegram_text")
response = await client.post(
TELEGRAM_WEBHOOK_URL,
f"{TELEGRAM_WEBHOOK_URL}_123456",
json=update_message_text,
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
)
@ -391,7 +391,7 @@ async def test_webhook_endpoint_generates_telegram_command_event(
events = async_capture_events(hass, "telegram_command")
response = await client.post(
TELEGRAM_WEBHOOK_URL,
f"{TELEGRAM_WEBHOOK_URL}_123456",
json=update_message_command,
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
)
@ -418,7 +418,7 @@ async def test_webhook_endpoint_generates_telegram_callback_event(
events = async_capture_events(hass, "telegram_callback")
response = await client.post(
TELEGRAM_WEBHOOK_URL,
f"{TELEGRAM_WEBHOOK_URL}_123456",
json=update_callback_query,
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
)
@ -594,7 +594,7 @@ async def test_webhook_endpoint_unauthorized_update_doesnt_generate_telegram_tex
events = async_capture_events(hass, "telegram_text")
response = await client.post(
TELEGRAM_WEBHOOK_URL,
f"{TELEGRAM_WEBHOOK_URL}_123456",
json=unauthorized_update_message_text,
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
)
@ -618,7 +618,7 @@ async def test_webhook_endpoint_without_secret_token_is_denied(
async_capture_events(hass, "telegram_text")
response = await client.post(
TELEGRAM_WEBHOOK_URL,
f"{TELEGRAM_WEBHOOK_URL}_123456",
json=update_message_text,
)
assert response.status == 401
@ -636,7 +636,7 @@ async def test_webhook_endpoint_invalid_secret_token_is_denied(
async_capture_events(hass, "telegram_text")
response = await client.post(
TELEGRAM_WEBHOOK_URL,
f"{TELEGRAM_WEBHOOK_URL}_123456",
json=update_message_text,
headers={"X-Telegram-Bot-Api-Secret-Token": incorrect_secret_token},
)

View File

@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, patch
from telegram import WebhookInfo
from telegram.error import TimedOut
from homeassistant.components.telegram_bot.webhooks import TELEGRAM_WEBHOOK_URL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
@ -115,7 +116,7 @@ async def test_webhooks_update_invalid_json(
client = await hass_client()
response = await client.post(
"/api/telegram_webhooks",
f"{TELEGRAM_WEBHOOK_URL}_123456",
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
)
assert response.status == 400
@ -139,7 +140,7 @@ async def test_webhooks_unauthorized_network(
return_value=IPv4Network("1.2.3.4"),
) as mock_remote:
response = await client.post(
"/api/telegram_webhooks",
f"{TELEGRAM_WEBHOOK_URL}_123456",
json="mock json",
headers={"X-Telegram-Bot-Api-Secret-Token": mock_generate_secret_token},
)

View File

@ -8,7 +8,7 @@ from homeassistant.components.application_credentials import (
ClientCredential,
async_import_client_credential,
)
from homeassistant.components.tesla_fleet.const import CLIENT_ID, DOMAIN
from homeassistant.components.tesla_fleet.const import DOMAIN
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@ -28,7 +28,7 @@ async def setup_platform(
await async_import_client_credential(
hass,
DOMAIN,
ClientCredential(CLIENT_ID, "", "Home Assistant"),
ClientCredential("CLIENT_ID", "CLIENT_SECRET", "Home Assistant"),
DOMAIN,
)

View File

@ -932,6 +932,11 @@ async def test_usb_discovery_migration(
assert mock_usb_serial_by_id.call_count == 2
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm_usb_migration"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "backup_nvm"
@ -1049,6 +1054,11 @@ async def test_usb_discovery_migration_restore_driver_ready_timeout(
assert mock_usb_serial_by_id.call_count == 2
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "confirm_usb_migration"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] is FlowResultType.SHOW_PROGRESS
assert result["step_id"] == "backup_nvm"

View File

@ -37,7 +37,11 @@ from homeassistant.helpers import (
)
from homeassistant.setup import async_setup_component
from .common import AIR_TEMPERATURE_SENSOR, EATON_RF9640_ENTITY
from .common import (
AIR_TEMPERATURE_SENSOR,
BULB_6_MULTI_COLOR_LIGHT_ENTITY,
EATON_RF9640_ENTITY,
)
from tests.common import (
MockConfigEntry,
@ -2168,3 +2172,39 @@ async def test_factory_reset_node(
assert len(notifications) == 1
assert list(notifications)[0] == msg_id
assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]
async def test_entity_available_when_node_dead(
hass: HomeAssistant, client, bulb_6_multi_color, integration
) -> None:
"""Test that entities remain available even when the node is dead."""
node = bulb_6_multi_color
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state
assert state.state != STATE_UNAVAILABLE
# Send dead event to the node
event = Event(
"dead", data={"source": "node", "event": "dead", "nodeId": node.node_id}
)
node.receive_event(event)
await hass.async_block_till_done()
# Entity should remain available even though the node is dead
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state
assert state.state != STATE_UNAVAILABLE
# Send alive event to bring the node back
event = Event(
"alive", data={"source": "node", "event": "alive", "nodeId": node.node_id}
)
node.receive_event(event)
await hass.async_block_till_done()
# Entity should still be available
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state
assert state.state != STATE_UNAVAILABLE

View File

@ -28,7 +28,7 @@ from homeassistant.components.zwave_js.lock import (
SERVICE_SET_LOCK_CONFIGURATION,
SERVICE_SET_LOCK_USERCODE,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
@ -295,7 +295,8 @@ async def test_door_lock(
assert node.status == NodeStatus.DEAD
state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)
assert state
assert state.state == STATE_UNAVAILABLE
# The state should still be locked, even if the node is dead
assert state.state == LockState.LOCKED
async def test_only_one_lock(

View File

@ -277,7 +277,7 @@ async def test_update_entity_dead(
zen_31,
integration,
) -> None:
"""Test update occurs when device is dead after it becomes alive."""
"""Test update occurs even when device is dead."""
event = Event(
"dead",
data={"source": "node", "event": "dead", "nodeId": zen_31.node_id},
@ -290,17 +290,7 @@ async def test_update_entity_dead(
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
await hass.async_block_till_done()
# Because node is asleep we shouldn't attempt to check for firmware updates
assert len(client.async_send_command.call_args_list) == 0
event = Event(
"alive",
data={"source": "node", "event": "alive", "nodeId": zen_31.node_id},
)
zen_31.receive_event(event)
await hass.async_block_till_done()
# Now that the node is up we can check for updates
# Checking for firmware updates should proceed even for dead nodes
assert len(client.async_send_command.call_args_list) > 0
args = client.async_send_command.call_args_list[0][0][0]