diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 422165e..01194db 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -18,6 +18,8 @@ jobs: uses: actions/checkout@v3 - name: Check code with Black uses: psf/black@stable + with: + options: "--check --exclude *.json" - name: Set up Python 3.10.8 uses: actions/setup-python@v3 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f331415..40d81a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,16 +2,17 @@ repos: - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 23.12.1 hooks: - id: black args: - --safe - --quiet + - --exclude *.json files: ^genius.*/.+\.py$|^genius.*/tests\.py$ - repo: https://github.com/pycqa/flake8 - rev: 3.8.4 + rev: 7.0.0 hooks: - id: flake8 additional_dependencies: @@ -20,7 +21,7 @@ repos: files: ^genius.*/.+\.py$|^genius.*/tests\.py$ - repo: https://github.com/pycqa/isort - rev: 5.7.0 + rev: 5.13.2 hooks: - id: isort args: ["--profile", "black", "--filter-files"] diff --git a/geniushubclient/zone.py b/geniushubclient/zone.py index cb111b8..f4c4cc9 100644 --- a/geniushubclient/zone.py +++ b/geniushubclient/zone.py @@ -4,19 +4,18 @@ import re from typing import Dict, List # Any, Optional, Set, Tuple -from .const import ( +from geniushubclient.const import ( ATTRS_ZONE, FOOTPRINT_MODES, IDAY_TO_DAY, IMODE_TO_MODE, - ITYPE_TO_TYPE, MODE_TO_IMODE, - TYPE_TO_ITYPE, ZONE_KIT, ZONE_MODE, ZONE_TYPE, ) -from .device import GeniusBase +from geniushubclient.device import GeniusBase +from geniushubclient.zoneclasses.properties import Properties _LOGGER = logging.getLogger(__name__) @@ -39,9 +38,20 @@ class GeniusZone(GeniusBase): def __init__(self, zone_id, raw_json, hub) -> None: super().__init__(zone_id, raw_json, hub, ATTRS_ZONE) + self._properties = Properties() + self.device_objs = [] self.device_by_id = {} + self._update(raw_json, self._hub.api_version) + + def _update(self, json_data: Dict, api_version: int): + """Parse the json for the zone data.""" + try: + self._properties._update(json_data, api_version) + except (AttributeError, LookupError, TypeError, ValueError): + _LOGGER.exception("Failed to convert Zone %s.", self.id) + @property def data(self) -> Dict: """Convert a zone's v3 JSON to the v1 schema.""" @@ -58,7 +68,8 @@ def is_occupied(node) -> bool: # from web app v5.2.4 O = occupancy detected (valid in any mode) A = occupancy detected, sufficient to call for heat (iff in Sense/FP mode) - l = null != i.settings.experimentalFeatures && i.settings.experimentalFeatures.timerPlus, + l = null != i.settings.experimentalFeatures + && i.settings.experimentalFeatures.timerPlus, p = parseInt(n.iMode) === e.zoneModes.Mode_Footprint || l, # sense mode? u = parseInt(n.iFlagExpectedKit) & e.equipmentTypes.Kit_PIR, # has a PIR d = n.trigger.reactive && n.trigger.output, @@ -138,13 +149,14 @@ def footprint_schedule(raw_json) -> Dict: return root - self._data = result = {"id": self._raw["iID"], "name": self._raw["strName"]} + self._data = result = {} + result["id"] = self.id + raw_json = self._raw # TODO: remove raw_json, use self._raw try: # convert zone (v1 attributes) - result["type"] = ITYPE_TO_TYPE[raw_json["iType"]] - if raw_json["iType"] == ZONE_TYPE.TPI and raw_json["zoneSubType"] == 0: - result["type"] = ITYPE_TO_TYPE[ZONE_TYPE.ControlOnOffPID] + properties_data = self._properties._get_v1_data() + result.update(properties_data) result["mode"] = IMODE_TO_MODE[raw_json["iMode"]] @@ -156,12 +168,11 @@ def footprint_schedule(raw_json) -> Dict: if raw_json["iType"] == ZONE_TYPE.Manager: if raw_json["fPV"]: result["temperature"] = raw_json["fPV"] - elif raw_json["iType"] == ZONE_TYPE.OnOffTimer: result["setpoint"] = bool(raw_json["fSP"]) - if self._has_pir: - if TYPE_TO_ITYPE[result["type"]] == ZONE_TYPE.ControlSP: + if self._properties.has_room_sensor: + if raw_json["iType"] == ZONE_TYPE.ControlSP: result["occupied"] = is_occupied(raw_json) else: result["_occupied"] = is_occupied(raw_json) @@ -220,17 +231,20 @@ def footprint_schedule(raw_json) -> Dict: return self._data + @property + def properties(self) -> Properties: + """Return the properties of the zone.""" + return self._properties + @property def _has_pir(self) -> bool: """Return True if the zone has a PIR (movement sensor).""" - if self._hub.api_version == 1: - return "occupied" in self.data - return self._raw["iFlagExpectedKit"] & ZONE_KIT.PIR + return self._properties.has_room_sensor @property def name(self) -> str: """Return the name of the zone, which can change.""" - return self.data["name"] + return self._properties.name @property def devices(self) -> List: diff --git a/tests/data_properties_v3_conversion/__init__.py b/geniushubclient/zoneclasses/__init__.py similarity index 100% rename from tests/data_properties_v3_conversion/__init__.py rename to geniushubclient/zoneclasses/__init__.py diff --git a/geniushubclient/zoneclasses/properties.py b/geniushubclient/zoneclasses/properties.py new file mode 100644 index 0000000..76f5cb3 --- /dev/null +++ b/geniushubclient/zoneclasses/properties.py @@ -0,0 +1,74 @@ +from typing import Dict + +from geniushubclient.const import ITYPE_TO_TYPE, TYPE_TO_ITYPE, ZONE_KIT, ZONE_TYPE + + +class Properties: + """The properties of the zone. + + Intended to be the properties that are relatively static for + the lifecycle of the zone""" + + def __init__(self): + self._name: str = None + self._zone_type: int = None + self._subtype: int = None + self._type_name: str = None + self._has_room_sensor: bool = None + + def _update(self, json_data: Dict, api_version: int) -> None: + """Parse the json and use it to populate the properties data class.""" + + if api_version == 1: + self._update_from_v1_data(json_data) + elif api_version == 3: + self._update_from_v3_data(json_data) + + def _update_from_v1_data(self, json_data: Dict) -> None: + self._name = json_data["name"] + self._type_name = json_data["type"] + self._zone_type = TYPE_TO_ITYPE[self.type_name] + self._has_room_sensor = "occupied" in json_data + + def _update_from_v3_data(self, json_data: Dict) -> None: + self._name = json_data["strName"] + self._zone_type = iType = json_data["iType"] + self._subtype = json_data["zoneSubType"] + self._has_room_sensor = json_data["iFlagExpectedKit"] & ZONE_KIT.PIR + self._type_name = ITYPE_TO_TYPE[iType] + + if iType == ZONE_TYPE.TPI and json_data["zoneSubType"] == 0: + self._zone_type = ZONE_TYPE.ControlOnOffPID + self._type_name = ITYPE_TO_TYPE[ZONE_TYPE.ControlOnOffPID] + + def _get_v1_data(self) -> Dict: + result = {} + result["name"] = self._name + result["type"] = self._type_name + + return result + + @property + def name(self) -> str: + """Return the name of the zone.""" + return self._name + + @property + def zone_type(self) -> int: + """Return the zone type integer representation of the zone.""" + return self._zone_type + + @property + def subtype(self) -> int: + """Return the zone subtype integer representation of the zone.""" + return self._subtype + + @property + def type_name(self) -> str: + """Return the name of the type of zone.""" + return self._type_name + + @property + def has_room_sensor(self) -> bool: + """Return whether the zone has a PIR (movement sensor).""" + return self._has_room_sensor diff --git a/requirements-dev.txt b/requirements-dev.txt index 217cd52..6ceb833 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,14 +1,14 @@ -black==22.8.0 -debugpy==1.2.1 -flake8==3.8.4 -pre-commit==2.11.1 +black==23.12.1 +debugpy==1.8.0 +flake8==7.0.0 +pre-commit==3.6.0 # -flake8-docstrings==1.5.0 -isort==5.7.0 -pycodestyle==2.6.0 -pydocstyle==5.1.1 -pyflakes==2.2.0 -pylint==2.7.2 +flake8-docstrings==1.7.0 +isort==5.13.2 +pycodestyle==2.11.1 +pydocstyle==6.3.0 +pyflakes==3.2.0 +pylint==3.0.3 # check-manifest==0.41 # pyroma==2.6 diff --git a/tests/data_properties_v3_conversion/genius_zone_data_properties_test.py b/tests/data_properties_v3_conversion/genius_zone_data_properties_test.py deleted file mode 100644 index c280566..0000000 --- a/tests/data_properties_v3_conversion/genius_zone_data_properties_test.py +++ /dev/null @@ -1,89 +0,0 @@ -""" - Tests for the GeniusZone class - """ - -import unittest -from unittest.mock import Mock - -from geniushubclient.const import ITYPE_TO_TYPE, ZONE_MODE, ZONE_TYPE -from geniushubclient.zone import GeniusZone - - -class GeniusZoneDataPropertiesTests(unittest.TestCase): - """ - Test for the GeniusZone Class, properties data. - """ - - _device_id = "Device Id" - _zone_name = "Zone Name" - - raw_json = { - "iID": _device_id, - "strName": _zone_name, - "bIsActive": 0, - "bInHeatEnabled": 0, - "bOutRequestHeat": 0, - "fBoostSP": 0, - "fPV": 21.0, - "fPV_offset": 0.0, - "fSP": 14.0, - "iBoostTimeRemaining": 0, - "iFlagExpectedKit": 517, - "iType": ZONE_TYPE.OnOffTimer, - "iMode": ZONE_MODE.Off, - "objFootprint": { - "bIsNight": 0, - "fFootprintAwaySP": 14.0, - "iFootprintTmNightStart": 75600, - "iProfile": 1, - "lstSP": [ - {"fSP": 16.0, "iDay": 0, "iTm": 0}, - {"fSP": 14.0, "iDay": 0, "iTm": 23400}, - {"fSP": 20.0, "iDay": 0, "iTm": 59700}, - {"fSP": 14.0, "iDay": 0, "iTm": 75000}, - {"fSP": 16.0, "iDay": 0, "iTm": 75600}, - ], - "objReactive": {"fActivityLevel": 0.0}, - }, - "objTimer": [{"fSP": 14.0, "iDay": 0, "iTm": -1}], - "trigger": {"reactive": 0, "output": 0}, - "warmupDuration": { - "bEnable": "true", - "bEnableCalcs": "true", - "fRiseRate": 0.5, - "iLagTime": 2420, - "iRiseTime": 300, - "iTotalTime": 2720, - }, - "zoneReactive": {"fActivityLevel": 0}, - "zoneSubType": 1, - } - - def setUp(self): - hub = Mock() - hub.api_version = 3 - self.hub = hub - - def test_when_iType_set_then_data_type_is_set_correctly(self): - "Check that the type_name is set on the properties object" - - for zone_type, zone_type_text in ITYPE_TO_TYPE.items(): - with self.subTest(zone_type=zone_type, zone_type_text=zone_type_text): - self.raw_json["iType"] = zone_type - self.raw_json["zoneSubType"] = 1 - genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) - - self.assertEqual(genius_zone.data["type"], zone_type_text) - - def test_when_iType_TPI_and_subzonetype_zero_then_properties_type_set_to_wet_underfloor( - self, - ): # noqa: E501 - """Check that the type_name is set to wet underfloor when zone type is TPI - and zone subtype is 0""" - - self.raw_json["iType"] = ZONE_TYPE.TPI - self.raw_json["zoneSubType"] = 0 - genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) - - zone_type_text = ITYPE_TO_TYPE[ZONE_TYPE.ControlOnOffPID] - self.assertEqual(genius_zone.data["type"], zone_type_text) diff --git a/tests/zone/__init__.py b/tests/zone/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/zone/data_properties_v3_conversion/__init__.py b/tests/zone/data_properties_v3_conversion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/data_properties_v3_conversion/genius_zone_data_footprint_schedule_test.py b/tests/zone/data_properties_v3_conversion/genius_zone_data_footprint_schedule_test.py similarity index 100% rename from tests/data_properties_v3_conversion/genius_zone_data_footprint_schedule_test.py rename to tests/zone/data_properties_v3_conversion/genius_zone_data_footprint_schedule_test.py diff --git a/tests/data_properties_v3_conversion/genius_zone_data_is_occupied_test.py b/tests/zone/data_properties_v3_conversion/genius_zone_data_is_occupied_test.py similarity index 100% rename from tests/data_properties_v3_conversion/genius_zone_data_is_occupied_test.py rename to tests/zone/data_properties_v3_conversion/genius_zone_data_is_occupied_test.py diff --git a/tests/data_properties_v3_conversion/genius_zone_data_override_test.py b/tests/zone/data_properties_v3_conversion/genius_zone_data_override_test.py similarity index 100% rename from tests/data_properties_v3_conversion/genius_zone_data_override_test.py rename to tests/zone/data_properties_v3_conversion/genius_zone_data_override_test.py diff --git a/tests/data_properties_v3_conversion/genius_zone_data_state_test.py b/tests/zone/data_properties_v3_conversion/genius_zone_data_state_test.py similarity index 98% rename from tests/data_properties_v3_conversion/genius_zone_data_state_test.py rename to tests/zone/data_properties_v3_conversion/genius_zone_data_state_test.py index b41149a..0a4bc74 100644 --- a/tests/data_properties_v3_conversion/genius_zone_data_state_test.py +++ b/tests/zone/data_properties_v3_conversion/genius_zone_data_state_test.py @@ -83,7 +83,7 @@ def test_when_bIsActive_is_true_then_state_bIsActive_true(self): self.assertTrue(genius_zone.data["_state"]["bIsActive"]) def test_when_bOutRequestHeat_is_false_then_output_false(self): - "Check that the bOutRequestHeat is correctly set to false" + "Check that the output is correctly set to false" self.raw_json["bOutRequestHeat"] = 0 @@ -92,7 +92,7 @@ def test_when_bOutRequestHeat_is_false_then_output_false(self): self.assertEqual(genius_zone.data["output"], 0) def test_when_bOutRequestHeat_is_true_then_output_true(self): - "Check that the bOutRequestHeat is correctly set to true" + "Check that the output is correctly set to true" self.raw_json["bOutRequestHeat"] = 1 diff --git a/tests/data_properties_v3_conversion/genius_zone_data_timer_schedule_test.py b/tests/zone/data_properties_v3_conversion/genius_zone_data_timer_schedule_test.py similarity index 100% rename from tests/data_properties_v3_conversion/genius_zone_data_timer_schedule_test.py rename to tests/zone/data_properties_v3_conversion/genius_zone_data_timer_schedule_test.py diff --git a/tests/zone/zone_data_property_test.py b/tests/zone/zone_data_property_test.py new file mode 100644 index 0000000..76860f8 --- /dev/null +++ b/tests/zone/zone_data_property_test.py @@ -0,0 +1,109 @@ +""" +Tests for the GeniusZone class +""" + +import unittest +from unittest.mock import Mock, patch + +from geniushubclient.const import ZONE_MODE, ZONE_TYPE +from geniushubclient.zone import GeniusZone +from geniushubclient.zoneclasses.properties import Properties + + +class GeniusZoneGetV1DataTests(unittest.TestCase): + """ + Test for the GeniusZone Class, dataproperty population. + """ + + _device_id = 27 + _zone_name = "Zone Name" + + raw_json = { + "iID": _device_id, + "strName": _zone_name, + "bIsActive": 0, + "bInHeatEnabled": 0, + "bOutRequestHeat": 0, + "fBoostSP": 0, + "fPV": 21.0, + "fPV_offset": 0.0, + "fSP": 14.0, + "iBoostTimeRemaining": 0, + "iFlagExpectedKit": 2, + "iType": ZONE_TYPE.OnOffTimer, + "iMode": ZONE_MODE.Off, + "objFootprint": { + "bIsNight": 0, + "fFootprintAwaySP": 14.0, + "iFootprintTmNightStart": 75600, + "iProfile": 1, + "lstSP": [ + {"fSP": 16.0, "iDay": 0, "iTm": 0}, + {"fSP": 14.0, "iDay": 0, "iTm": 23400}, + {"fSP": 20.0, "iDay": 0, "iTm": 59700}, + {"fSP": 14.0, "iDay": 0, "iTm": 75000}, + {"fSP": 16.0, "iDay": 0, "iTm": 75600}, + ], + "objReactive": {"fActivityLevel": 0.0}, + }, + "objTimer": [{"fSP": 14.0, "iDay": 0, "iTm": -1}], + "trigger": {"reactive": 0, "output": 0}, + "warmupDuration": { + "bEnable": "true", + "bEnableCalcs": "true", + "fRiseRate": 0.5, + "iLagTime": 2420, + "iRiseTime": 300, + "iTotalTime": 2720, + }, + "zoneReactive": {"fActivityLevel": 0}, + "zoneSubType": 1, + } + + def setUp(self): + self.hub = Mock() + self.hub.api_version = 3 + + def test_data_calls_properties_populate_method(self): + "Check that calling the data property calls the properties get_V1_data method" + + genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) + + with patch.object(genius_zone, "_properties") as mock_properties: + properties = Properties() + mock_properties.return_value = properties + + genius_zone.data + + mock_properties._get_v1_data.assert_called_once() + + def test_update_sets_id(self): + "Check that the data property has id set correctly" + + genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) + + with patch.object(genius_zone._properties, "_get_v1_data") as mock_properties: + mock_properties.return_value = {} + + self.assertEqual(genius_zone.data["id"], self._device_id) + + def test_update_sets_name(self): + "Check that the data property has name set correctly" + + genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) + + with patch.object(genius_zone._properties, "_get_v1_data") as mock_properties: + mock_properties.return_value = {"name": self._zone_name} + + self.assertEqual(genius_zone.data["name"], self._zone_name) + + def test_update_sets_type(self): + "Check that the data property has type set correctly" + + genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) + + with patch.object(genius_zone._properties, "_get_v1_data") as mock_properties: + zone_type = "radiator" + mock_properties.return_value = {"type": zone_type} + + self.assertEqual(genius_zone.data["type"], zone_type) diff --git a/tests/data_properties_v3_conversion/genius_zone_data_test.py b/tests/zone/zone_update_test.py similarity index 64% rename from tests/data_properties_v3_conversion/genius_zone_data_test.py rename to tests/zone/zone_update_test.py index ad896e9..2b18d47 100644 --- a/tests/data_properties_v3_conversion/genius_zone_data_test.py +++ b/tests/zone/zone_update_test.py @@ -1,20 +1,21 @@ """ - Tests for the GeniusZone class - """ +Tests for the GeniusZone class +""" import unittest -from unittest.mock import Mock +from unittest.mock import Mock, patch from geniushubclient.const import ZONE_MODE, ZONE_TYPE from geniushubclient.zone import GeniusZone +from geniushubclient.zoneclasses.properties import Properties -class GeniusZoneDataTests(unittest.TestCase): +class GeniusZoneUpdateTests(unittest.TestCase): """ - Test for the GeniusZone Class, general data. + Test for the GeniusZone Class, update method. """ - _device_id = "Device Id" + _device_id = 27 _zone_name = "Zone Name" raw_json = { @@ -60,24 +61,19 @@ class GeniusZoneDataTests(unittest.TestCase): } def setUp(self): - hub = Mock() - hub.api_version = 3 - self.hub = hub + self.hub = Mock() + self.hub.api_version = 3 - def test_raw_json_is_stored(self): - "Check that the raw json is set on the class" + def test_update_calls_properties_update_method_correctly(self): + "Check that calling the update method calls the properties update method" genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) - self.assertEqual(genius_zone._raw, self.raw_json) - def test_id_is_set_correctly_test(self): - "Check that the id is set on the data object" + with patch.object(genius_zone, "_properties") as mock_properties: + mock_properties.return_value = Properties() - genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) - self.assertEqual(genius_zone.data["id"], self._device_id) + genius_zone._update(self.raw_json, self.hub.api_version) - def test_name_is_set_correctly(self): - "Check that the name is set on the data object" - - genius_zone = GeniusZone(self._device_id, self.raw_json, self.hub) - self.assertEqual(genius_zone.data["name"], self._zone_name) + mock_properties._update.assert_called_once_with( + self.raw_json, self.hub.api_version + ) diff --git a/tests/zone/zoneclasses/__init__.py b/tests/zone/zoneclasses/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/zone/zoneclasses/properties_get_v1_data_test.py b/tests/zone/zoneclasses/properties_get_v1_data_test.py new file mode 100644 index 0000000..5e44211 --- /dev/null +++ b/tests/zone/zoneclasses/properties_get_v1_data_test.py @@ -0,0 +1,46 @@ +""" +Tests for the zone Properties class +""" + +import unittest +from unittest.mock import Mock + +from geniushubclient.const import ITYPE_TO_TYPE +from geniushubclient.zoneclasses.properties import Properties + + +class PropertiesGetDataTests(unittest.TestCase): + """ + Test for the zone Properties Class get_v1_data method + """ + + _device_id = 27 + _zone_name = "Zone Name" + _json_data = {"name": _zone_name, "type": "radiator"} + + def setUp(self): + hub = Mock() + hub.api_version = 3 + self.hub = hub + + def test_sets_name(self): + "Check that the name is returned" + + properties = Properties() + properties._name = self._zone_name + + data = properties._get_v1_data() + + self.assertEqual(data["name"], self._zone_name) + + def test_when_iType_set_then_data_type_is_set_correctly(self): + "Check that the type is returned" + + for zone_type, zone_type_text in ITYPE_TO_TYPE.items(): + with self.subTest(zone_type=zone_type, zone_type_text=zone_type_text): + properties = Properties() + properties._type_name = zone_type_text + + data = properties._get_v1_data() + + self.assertEqual(data["type"], zone_type_text) diff --git a/tests/zone/zoneclasses/properties_update_v1_test.py b/tests/zone/zoneclasses/properties_update_v1_test.py new file mode 100644 index 0000000..e306b8e --- /dev/null +++ b/tests/zone/zoneclasses/properties_update_v1_test.py @@ -0,0 +1,112 @@ +""" +Tests for the zone Properties class +""" + +import unittest +from unittest.mock import Mock + +from geniushubclient.const import ITYPE_TO_TYPE, ZONE_TYPE +from geniushubclient.zoneclasses.properties import Properties + + +class PropertiesUpdateV1Tests(unittest.TestCase): + """Test for the update method zone Properties Class, + using V1 json data""" + + _device_id = 27 + _zone_name = "Zone Name" + + raw_json = { + "id": _device_id, + "name": _zone_name, + "type": ITYPE_TO_TYPE[ZONE_TYPE.OnOffTimer], + } + + def setUp(self): + hub = Mock() + hub.api_version = 1 + self.hub = hub + + def test_update_sets_name(self): + "Check that the name is set" + + self.raw_json["name"] = self._zone_name + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.name, self._zone_name) + + def test_update_when_iType_set_then_properties_zone_type_is_set_correctly(self): + "Check that the zone_type is set on the properties object" + + test_values = { + ZONE_TYPE.Manager, + ZONE_TYPE.OnOffTimer, + ZONE_TYPE.ControlSP, + ZONE_TYPE.ControlOnOffPID, + ZONE_TYPE.TPI, + ZONE_TYPE.Surrogate, + } + + for zone_type in test_values: + with self.subTest(zone_type=zone_type): # noqa: E501 + self.raw_json["type"] = ITYPE_TO_TYPE[zone_type] + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.zone_type, zone_type) + + def test_update_when_iType_set_then_properties_type_name_is_set_correctly(self): + "Check that the type_name is set on the properties object" + + test_values = { + ZONE_TYPE.Manager, + ZONE_TYPE.OnOffTimer, + ZONE_TYPE.ControlSP, + ZONE_TYPE.ControlOnOffPID, + ZONE_TYPE.TPI, + ZONE_TYPE.Surrogate, + } + + for zone_type in test_values: + zone_type_text = ITYPE_TO_TYPE[zone_type] + with self.subTest( + zone_type=zone_type, zone_type_text=zone_type_text + ): # noqa: E501 + self.raw_json["type"] = zone_type_text + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.type_name, zone_type_text) + + def test_update_when_zone_does_not_have_room_sensor_then_has_room_sensor_false( + self, + ): # noqa: E501 + "Check that has_room_sensor set to false when zone does not have a room sensor" + + self.raw_json["type"] = ITYPE_TO_TYPE[ZONE_TYPE.TPI] + # Can't guarantee the running order of the tests, so clear occupied out + # if it's in the dictionary + if "occupied" in self.raw_json: + self.raw_json.pop("occupied") + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertFalse(properties.has_room_sensor) + + def test_update_when_zone_does_have_room_sensor_then_has_room_sensor_true( + self, + ): # noqa: E501 + "Check that has_room_sensor set to true when zone does have a room sensor" + + self.raw_json["type"] = ITYPE_TO_TYPE[ZONE_TYPE.TPI] + self.raw_json["occupied"] = 1 + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertTrue(properties.has_room_sensor) diff --git a/tests/zone/zoneclasses/properties_update_v3_test.py b/tests/zone/zoneclasses/properties_update_v3_test.py new file mode 100644 index 0000000..e352335 --- /dev/null +++ b/tests/zone/zoneclasses/properties_update_v3_test.py @@ -0,0 +1,143 @@ +""" +Tests for the zone Properties class +""" + +import unittest +from unittest.mock import Mock + +from geniushubclient.const import ITYPE_TO_TYPE, ZONE_KIT, ZONE_TYPE +from geniushubclient.zoneclasses.properties import Properties + + +class PropertiesUpdateV3Tests(unittest.TestCase): + """Test for the update method zone Properties Class, + using V3 json data""" + + _device_id = 27 + _zone_name = "Zone Name" + + raw_json = { + "iID": _device_id, + "strName": _zone_name, + "iFlagExpectedKit": 517, + "iType": ZONE_TYPE.OnOffTimer, + "zoneSubType": 1, + } + + def setUp(self): + hub = Mock() + hub.api_version = 3 + self.hub = hub + + def test_update_sets_name(self): + "Check that the name is set" + + self.raw_json["strName"] = self._zone_name + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.name, self._zone_name) + + def test_update_when_iType_set_then_properties_zone_type_is_set_correctly(self): + "Check that the zone_type is set on the properties object" + + test_values = { + ZONE_TYPE.Manager, + ZONE_TYPE.OnOffTimer, + ZONE_TYPE.ControlSP, + ZONE_TYPE.ControlOnOffPID, + ZONE_TYPE.TPI, + ZONE_TYPE.Surrogate, + } + + for zone_type in test_values: + with self.subTest(zone_type=zone_type): # noqa: E501 + self.raw_json["iType"] = zone_type + self.raw_json["zoneSubType"] = 1 + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.zone_type, zone_type) + + def test_update_when_iType_set_then_properties_type_name_is_set_correctly(self): + "Check that the type_name is set on the properties object" + + test_values = { + ZONE_TYPE.Manager, + ZONE_TYPE.OnOffTimer, + ZONE_TYPE.ControlSP, + ZONE_TYPE.ControlOnOffPID, + ZONE_TYPE.TPI, + ZONE_TYPE.Surrogate, + } + + for zone_type in test_values: + zone_type_text = ITYPE_TO_TYPE[zone_type] + with self.subTest( + zone_type=zone_type, zone_type_text=zone_type_text + ): # noqa: E501 + self.raw_json["iType"] = zone_type + self.raw_json["zoneSubType"] = 1 + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.type_name, zone_type_text) + + def test_update_when_iType_TPI_and_subzonetype_zero_then_properties_type_set_to_wet_underfloor( + self, + ): # noqa: E501 + """Check that the zone_type is set to ControlOnOffPID + when zone type is TPI and zone subtype is 0""" + + self.raw_json["iType"] = ZONE_TYPE.TPI + self.raw_json["zoneSubType"] = 0 + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.zone_type, ZONE_TYPE.ControlOnOffPID) + + def test_update_when_iType_TPI_and_subzonetype_zero_then_properties_type_name_set_to_wet_underfloor( + self, + ): # noqa: E501 + """Check that the type_name is set to wet underfloor + when zone type is TPI and zone subtype is 0""" + + self.raw_json["iType"] = ZONE_TYPE.TPI + self.raw_json["zoneSubType"] = 0 + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertEqual(properties.type_name, "wet underfloor") + + def test_update_when_zone_does_not_have_room_sensor_then_has_room_sensor_false( + self, + ): # noqa: E501 + "Check that has_room_sensor set to false when zone does not have a room sensor" + + self.raw_json["iType"] = ZONE_TYPE.TPI + self.raw_json["zoneSubType"] = 0 + self.raw_json["iFlagExpectedKit"] = ZONE_KIT.Valve + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertFalse(properties.has_room_sensor) + + def test_update_when_zone_does_have_room_sensor_then_has_room_sensor_true( + self, + ): # noqa: E501 + "Check that has_room_sensor set to true when zone does have a room sensor" + + self.raw_json["iType"] = ZONE_TYPE.TPI + self.raw_json["zoneSubType"] = 0 + self.raw_json["iFlagExpectedKit"] = ZONE_KIT.PIR + properties = Properties() + + properties._update(self.raw_json, self.hub.api_version) + + self.assertTrue(properties.has_room_sensor)