Skip Tuya update if it is not relevent (#160407)

This commit is contained in:
epenet
2026-01-09 07:01:43 +01:00
committed by GitHub
parent 1ac2280266
commit 5e43708a40
15 changed files with 455 additions and 68 deletions
+38
View File
@@ -6,6 +6,7 @@ import pathlib
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
from tuya_sharing import CustomerDevice, Manager
from homeassistant.components.tuya import DeviceListener
@@ -61,3 +62,40 @@ async def initialize_entry(
with patch("homeassistant.components.tuya.Manager", return_value=mock_manager):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
async def check_selective_state_update(
hass: HomeAssistant,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
*,
entity_id: str,
dpcode: str,
initial_state: str,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test selective state update.
This test verifies that when an update event comes with properties that do NOT
include the dpcode (e.g., a battery event for a door sensor),
the entity state is not changed and last_reported is not updated.
"""
initial_reported = "2024-01-01T00:00:00+00:00"
assert hass.states.get(entity_id).state == initial_state
assert hass.states.get(entity_id).last_reported.isoformat() == initial_reported
# Force update the dpcode and trigger device update
freezer.tick(30)
mock_device.status[dpcode] = None
await mock_listener.async_send_device_update(hass, mock_device, {})
assert hass.states.get(entity_id).state == initial_state
assert hass.states.get(entity_id).last_reported.isoformat() == initial_reported
# Trigger device update with provided updates
freezer.tick(30)
await mock_listener.async_send_device_update(hass, mock_device, updates)
assert hass.states.get(entity_id).state == expected_state
assert hass.states.get(entity_id).last_reported.isoformat() == last_reported
+49 -57
View File
@@ -14,7 +14,7 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import MockDeviceListener, initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -35,6 +35,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["mcs_oxslv1c9"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"battery_percentage": 80}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"doorcontact_state": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"battery_percentage": 50, "doorcontact_state": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.BINARY_SENSOR])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="binary_sensor.window_downstairs_door",
dpcode="doorcontact_state",
initial_state="off",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
"mock_device_code",
["cs_zibqa9dutqyaxym2"],
@@ -75,59 +123,3 @@ async def test_bitmap(
assert hass.states.get("binary_sensor.dehumidifier_tank_full").state == tankfull
assert hass.states.get("binary_sensor.dehumidifier_defrost").state == defrost
assert hass.states.get("binary_sensor.dehumidifier_wet").state == wet
@pytest.mark.parametrize(
"mock_device_code",
["mcs_oxslv1c9"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"battery_percentage": 80}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"doorcontact_state": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"battery_percentage": 50, "doorcontact_state": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.BINARY_SENSOR])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test binary sensor only updates when its dpcode is in updated properties.
This test verifies that when an update event comes with properties that do NOT
include the binary sensor's dpcode (e.g., a battery event for a door sensor),
the binary sensor state is not changed and last_reported is not updated.
"""
entity_id = "binary_sensor.window_downstairs_door"
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
assert hass.states.get(entity_id).state == "off"
assert (
hass.states.get(entity_id).last_reported.isoformat()
== "2024-01-01T00:00:00+00:00"
)
# Force update the dpcode - should be ignored unless event contains the property
mock_device.status["doorcontact_state"] = True
freezer.tick(60)
await mock_listener.async_send_device_update(hass, mock_device, updates)
assert hass.states.get(entity_id).state == expected_state
assert hass.states.get(entity_id).last_reported.isoformat() == last_reported
+51 -1
View File
@@ -2,8 +2,10 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -17,7 +19,7 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -37,6 +39,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["mal_gyitctrjj1kefxp2"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"switch_alarm_sound": True}, "15.0", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"delay_set": 17}, "17.0", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"switch_alarm_sound": True, "delay_set": 17},
"17.0",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.NUMBER])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="number.multifunction_alarm_arm_delay",
dpcode="delay_set",
initial_state="15.0",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
"mock_device_code",
["mal_gyitctrjj1kefxp2"],
+51 -1
View File
@@ -2,8 +2,10 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -18,7 +20,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -38,6 +40,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["cl_zah67ekd"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"control": "stop"}, "forward", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"control_back_mode": "back"}, "back", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"control": "stop", "control_back_mode": "back"},
"back",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SELECT])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="select.kitchen_blinds_motor_mode",
dpcode="control_back_mode",
initial_state="forward",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
"mock_device_code",
["cl_zah67ekd"],
+51 -1
View File
@@ -2,8 +2,10 @@
from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -12,7 +14,7 @@ from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -31,3 +33,51 @@ async def test_platform_setup_and_discovery(
await initialize_entry(hass, mock_manager, mock_config_entry, mock_devices)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["mcs_8yhypbo7"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"doorcontact_state": True}, "62.0", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"battery_percentage": 50}, "50.0", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"doorcontact_state": True, "battery_percentage": 50},
"50.0",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SENSOR])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="sensor.boite_aux_lettres_arriere_battery",
dpcode="battery_percentage",
initial_state="62.0",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
+50 -1
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -18,7 +19,7 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -38,6 +39,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["sp_sdd5f5f2dl5wydjf"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"basic_wdr": False}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"siren_switch": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"basic_wdr": False, "siren_switch": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SIREN])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="siren.c9",
dpcode="siren_switch",
initial_state="off",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SIREN])
@pytest.mark.parametrize(
"mock_device_code",
+50 -1
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -19,7 +20,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -39,6 +40,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["cz_PGEkBctAbtzKOZng"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"countdown_1": 50}, "off", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"switch": True}, "on", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"countdown_1": 50, "switch": True},
"on",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.SWITCH])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="switch.din_socket",
dpcode="switch",
initial_state="off",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@pytest.mark.parametrize(
("preexisting_entity", "disabled_by", "expected_entity", "expected_issue"),
[
+50 -1
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from tuya_sharing import CustomerDevice, Manager
@@ -18,7 +19,7 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import initialize_entry
from . import MockDeviceListener, check_selective_state_update, initialize_entry
from tests.common import MockConfigEntry, snapshot_platform
@@ -38,6 +39,54 @@ async def test_platform_setup_and_discovery(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
"mock_device_code",
["sfkzq_ed7frwissyqrejic"],
)
@pytest.mark.parametrize(
("updates", "expected_state", "last_reported"),
[
# Update without dpcode - state should not change, last_reported stays at initial
({"battery_percentage": 50}, "open", "2024-01-01T00:00:00+00:00"),
# Update with dpcode - state should change, last_reported advances
({"switch_1": False}, "closed", "2024-01-01T00:01:00+00:00"),
# Update with multiple properties including dpcode - state should change
(
{"battery_percentage": 50, "switch_1": False},
"closed",
"2024-01-01T00:01:00+00:00",
),
],
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.VALVE])
@pytest.mark.freeze_time("2024-01-01")
async def test_selective_state_update(
hass: HomeAssistant,
mock_manager: Manager,
mock_config_entry: MockConfigEntry,
mock_device: CustomerDevice,
mock_listener: MockDeviceListener,
freezer: FrozenDateTimeFactory,
updates: dict[str, Any],
expected_state: str,
last_reported: str,
) -> None:
"""Test skip_update/last_reported."""
await initialize_entry(hass, mock_manager, mock_config_entry, mock_device)
await check_selective_state_update(
hass,
mock_device,
mock_listener,
freezer,
entity_id="valve.jie_hashui_fa_valve_1",
dpcode="switch_1",
initial_state="open",
updates=updates,
expected_state=expected_state,
last_reported=last_reported,
)
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.VALVE])
@pytest.mark.parametrize(
"mock_device_code",