Skip to content

Commit 56266db

Browse files
committed
Initial tests
1 parent 2123900 commit 56266db

File tree

4 files changed

+196
-12
lines changed

4 files changed

+196
-12
lines changed

tests/async_mock.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Mock utilities that are async aware."""
2+
3+
from unittest.mock import * # noqa: F401, F403
4+
5+
6+
class _IntSentinelObject(int):
7+
"""Sentinel-like object that is also an integer subclass.
8+
9+
Allows sentinels to be used
10+
in loggers that perform int-specific string formatting.
11+
"""
12+
13+
def __new__(cls, name):
14+
instance = super().__new__(cls, 0)
15+
instance.name = name
16+
return instance
17+
18+
def __repr__(self):
19+
return f"int_sentinel.{self.name}"
20+
21+
def __hash__(self):
22+
return hash((int(self), self.name))
23+
24+
def __eq__(self, other):
25+
return self is other
26+
27+
__str__ = __reduce__ = __repr__
28+
29+
30+
class _IntSentinel:
31+
def __init__(self):
32+
self._sentinels = {}
33+
34+
def __getattr__(self, name):
35+
if name == "__bases__":
36+
raise AttributeError
37+
return self._sentinels.setdefault(name, _IntSentinelObject(name))
38+
39+
def __reduce__(self):
40+
return "int_sentinel"
41+
42+
43+
int_sentinel = _IntSentinel()

tests/test_tuya_mcu.py

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
from unittest import mock
55

66
import pytest
7+
from zigpy.device import Device
8+
from zigpy.quirks.registry import DeviceRegistry
9+
from zigpy.quirks.v2 import CustomDeviceV2
10+
import zigpy.types as t
711
from zigpy.zcl import foundation
12+
from zigpy.zcl.clusters.general import Basic
813

9-
from tests.common import ClusterListener, MockDatetime
14+
from tests.common import ClusterListener, MockDatetime, wait_for_zigpy_tasks
1015
import zhaquirks
1116
from zhaquirks.tuya import TUYA_MCU_VERSION_RSP, TUYA_SET_TIME, TuyaDPType
1217
from zhaquirks.tuya.mcu import (
@@ -15,8 +20,15 @@
1520
TuyaAttributesCluster,
1621
TuyaClusterData,
1722
TuyaMCUCluster,
23+
TuyaOnOffNM,
24+
TuyaQuirkBuilder,
25+
TuyaSoilMoisture,
26+
TuyaTemperatureMeasurement,
27+
TuyaValveWaterConsumed,
1828
)
1929

30+
from .async_mock import sentinel
31+
2032
zhaquirks.setup()
2133

2234
ZCL_TUYA_VERSION_RSP = b"\x09\x06\x11\x01\x6d\x82"
@@ -375,3 +387,130 @@ async def test_tuya_mcu_classes():
375387
TuyaClusterData(manufacturer="xiaomi")
376388
with pytest.raises(ValueError):
377389
TuyaClusterData(manufacturer=b"")
390+
391+
392+
@pytest.fixture(name="device_mock")
393+
def real_device(MockAppController):
394+
"""Device fixture with a single endpoint."""
395+
ieee = sentinel.ieee
396+
nwk = 0x2233
397+
device = Device(MockAppController, ieee, nwk)
398+
399+
device.add_endpoint(1)
400+
device[1].profile_id = 0x0104
401+
device[1].device_type = 0x0051
402+
device.model = "model"
403+
device.manufacturer = "manufacturer"
404+
device[1].add_input_cluster(0x0000)
405+
device[1].add_input_cluster(0xEF00)
406+
device[1].add_output_cluster(0x000A)
407+
device[1].add_output_cluster(0x0019)
408+
return device
409+
410+
411+
async def test_tuya_quirkbuilder(device_mock):
412+
"""Test adding a v2 Tuya Quirk to the registry and getting back a quirked device."""
413+
414+
registry = DeviceRegistry()
415+
416+
class TestEnum(t.enum8):
417+
"""Test Enum."""
418+
419+
A = 0x00
420+
B = 0x01
421+
422+
entry = (
423+
TuyaQuirkBuilder(device_mock.manufacturer, device_mock.model, registry=registry)
424+
.tuya_battery(dp_id=1)
425+
.tuya_metering(dp_id=2)
426+
.tuya_onoff(dp_id=3)
427+
.tuya_soil_moisture(dp_id=4)
428+
.tuya_temperature(dp_id=5)
429+
.tuya_switch(
430+
dp_id=6,
431+
attribute_name="test_onoff",
432+
)
433+
.tuya_number(
434+
dp_id=7,
435+
attribute_name="test_number",
436+
type=t.uint16_t,
437+
)
438+
.tuya_binary_sensor(
439+
dp_id=8,
440+
attribute_name="test_binary",
441+
)
442+
.tuya_sensor(
443+
dp_id=9,
444+
attribute_name="test_sensor",
445+
type=t.uint8_t,
446+
)
447+
.tuya_enum(
448+
dp_id=10,
449+
attribute_name="test_enum",
450+
enum_class=TestEnum,
451+
)
452+
.add_to_registry()
453+
)
454+
455+
# coverage for overridden __eq__ method
456+
assert entry.adds_metadata[0] != entry.adds_metadata[1]
457+
assert entry.adds_metadata[0] != entry
458+
459+
quirked = registry.get_device(device_mock)
460+
assert isinstance(quirked, CustomDeviceV2)
461+
assert quirked in registry
462+
463+
ep = quirked.endpoints[1]
464+
465+
assert ep.basic is not None
466+
assert isinstance(ep.basic, Basic)
467+
468+
assert ep.tuya_manufacturer is not None
469+
assert isinstance(ep.tuya_manufacturer, TuyaMCUCluster)
470+
471+
tuya_cluster = ep.tuya_manufacturer
472+
tuya_listener = ClusterListener(tuya_cluster)
473+
assert tuya_cluster.attributes_by_name["test_onoff"].id == 0xEF06
474+
assert tuya_cluster.attributes_by_name["test_number"].id == 0xEF07
475+
assert tuya_cluster.attributes_by_name["test_binary"].id == 0xEF08
476+
assert tuya_cluster.attributes_by_name["test_sensor"].id == 0xEF09
477+
assert tuya_cluster.attributes_by_name["test_enum"].id == 0xEF0A
478+
479+
assert ep.smartenergy_metering is not None
480+
assert isinstance(ep.smartenergy_metering, TuyaValveWaterConsumed)
481+
482+
assert ep.on_off is not None
483+
assert isinstance(ep.on_off, TuyaOnOffNM)
484+
485+
assert ep.soil_moisture is not None
486+
assert isinstance(ep.soil_moisture, TuyaSoilMoisture)
487+
488+
assert ep.temperature is not None
489+
assert isinstance(ep.temperature, TuyaTemperatureMeasurement)
490+
491+
with mock.patch.object(
492+
tuya_cluster.endpoint, "request", return_value=foundation.Status.SUCCESS
493+
) as m1:
494+
(status,) = await tuya_cluster.write_attributes(
495+
{
496+
"test_enum": 0x01,
497+
}
498+
)
499+
500+
await wait_for_zigpy_tasks()
501+
m1.assert_called_with(
502+
cluster=61184,
503+
sequence=1,
504+
data=b"\x01\x01\x00\x00\x01\n\x04\x00\x01\x01",
505+
command_id=0,
506+
timeout=5,
507+
expect_reply=False,
508+
use_ieee=False,
509+
ask_for_ack=None,
510+
)
511+
assert status == [
512+
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
513+
]
514+
515+
assert tuya_listener.attribute_updates[0][0] == 0xEF0A
516+
assert tuya_listener.attribute_updates[0][1] == TestEnum.B

zhaquirks/tuya/mcu/__init__.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ def __init__(
780780
self.new_attributes: dict[int, foundation.ZCLAttributeDef] = {}
781781
super().__init__(manufacturer, model, registry)
782782

783-
def battery(
783+
def tuya_battery(
784784
self,
785785
dp_id: int,
786786
power_cfg: PowerConfiguration = TuyaPowerConfigurationCluster2AAA,
@@ -796,7 +796,7 @@ def battery(
796796
self.adds(power_cfg)
797797
return self
798798

799-
def metering(
799+
def tuya_metering(
800800
self,
801801
dp_id: int,
802802
metering_cfg: TuyaLocalCluster = TuyaValveWaterConsumed,
@@ -808,8 +808,9 @@ def metering(
808808
"current_summ_delivered",
809809
)
810810
self.adds(metering_cfg)
811+
return self
811812

812-
def onoff(
813+
def tuya_onoff(
813814
self,
814815
dp_id: int,
815816
onoff_cfg: TuyaLocalCluster = TuyaOnOffNM,
@@ -821,8 +822,9 @@ def onoff(
821822
"on_off",
822823
)
823824
self.adds(onoff_cfg)
825+
return self
824826

825-
def soil_moisture(
827+
def tuya_soil_moisture(
826828
self,
827829
dp_id: int,
828830
soil_cfg: TuyaLocalCluster = TuyaSoilMoisture,
@@ -838,7 +840,7 @@ def soil_moisture(
838840
self.adds(soil_cfg)
839841
return self
840842

841-
def temperature(
843+
def tuya_temperature(
842844
self,
843845
dp_id: int,
844846
temp_cfg: TuyaLocalCluster = TuyaTemperatureMeasurement,

zhaquirks/tuya/ts0601_sensor.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,9 @@ class TuyaTempHumiditySensorVar04(CustomDevice):
330330
(
331331
TuyaQuirkBuilder("_TZE284_aao3yzhs", "TS0601")
332332
.also_applies_to("_TZE284_sgabhwa6", "TS0601")
333-
.temperature(dp_id=5, scale=10)
334-
.battery(dp_id=15)
335-
.soil_moisture(dp_id=3)
333+
.tuya_temperature(dp_id=5, scale=10)
334+
.tuya_battery(dp_id=15)
335+
.tuya_soil_moisture(dp_id=3)
336336
.add_to_registry()
337337
)
338338

@@ -342,8 +342,8 @@ class TuyaTempHumiditySensorVar04(CustomDevice):
342342
.also_applies_to("_TZE200_ga1maeof", "TS0601")
343343
.also_applies_to("_TZE200_9cqcpkgb", "TS0601")
344344
.also_applies_to("_TZE204_myd45weu", "TS0601")
345-
.temperature(dp_id=5)
346-
.battery(dp_id=15)
347-
.soil_moisture(dp_id=3)
345+
.tuya_temperature(dp_id=5)
346+
.tuya_battery(dp_id=15)
347+
.tuya_soil_moisture(dp_id=3)
348348
.add_to_registry()
349349
)

0 commit comments

Comments
 (0)