|
|
@@ -1,9 +1,11 @@
|
|
|
+import threading
|
|
|
from datetime import datetime, timedelta
|
|
|
-from time import time
|
|
|
+from time import sleep, time
|
|
|
from unittest import IsolatedAsyncioTestCase
|
|
|
from unittest.mock import AsyncMock, call, patch
|
|
|
|
|
|
import pytest
|
|
|
+from homeassistant.const import TEMP_CELSIUS
|
|
|
|
|
|
from custom_components.goldair_climate.const import (
|
|
|
CONF_TYPE_DEHUMIDIFIER,
|
|
|
@@ -62,6 +64,17 @@ class TestDevice(IsolatedAsyncioTestCase):
|
|
|
},
|
|
|
)
|
|
|
|
|
|
+ def test_temperature_unit(self):
|
|
|
+ self.assertEqual(self.subject.temperature_unit, TEMP_CELSIUS)
|
|
|
+
|
|
|
+ async def test_refreshes_state_if_no_cached_state_exists(self):
|
|
|
+ self.subject._cached_state = {}
|
|
|
+ self.subject.async_refresh = AsyncMock()
|
|
|
+
|
|
|
+ await self.subject.async_inferred_type()
|
|
|
+
|
|
|
+ self.subject.async_refresh.assert_awaited()
|
|
|
+
|
|
|
async def test_detects_geco_heater_payload(self):
|
|
|
self.subject._cached_state = GECO_HEATER_PAYLOAD
|
|
|
self.assertEqual(
|
|
|
@@ -90,6 +103,10 @@ class TestDevice(IsolatedAsyncioTestCase):
|
|
|
self.subject._cached_state = FAN_PAYLOAD
|
|
|
self.assertEqual(await self.subject.async_inferred_type(), CONF_TYPE_FAN)
|
|
|
|
|
|
+ async def test_detection_returns_none_when_device_type_could_not_be_detected(self):
|
|
|
+ self.subject._cached_state = {"1": False}
|
|
|
+ self.assertEqual(await self.subject.async_inferred_type(), None)
|
|
|
+
|
|
|
async def test_does_not_refresh_more_often_than_cache_timeout(self):
|
|
|
refresh_task = AsyncMock()
|
|
|
self.subject._cached_state = {"updated_at": time() - 19}
|
|
|
@@ -128,13 +145,13 @@ class TestDevice(IsolatedAsyncioTestCase):
|
|
|
async_job.assert_awaited()
|
|
|
|
|
|
def test_refresh_reloads_status_from_device(self):
|
|
|
- self.subject._api.status.return_value = {"dps": {1: False}}
|
|
|
- self.subject._cached_state = {1: True}
|
|
|
+ self.subject._api.status.return_value = {"dps": {"1": False}}
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
|
|
|
self.subject.refresh()
|
|
|
|
|
|
self.subject._api.status.assert_called_once()
|
|
|
- self.assertEqual(self.subject._cached_state[1], False)
|
|
|
+ self.assertEqual(self.subject._cached_state["1"], False)
|
|
|
self.assertTrue(
|
|
|
time() - 1 <= self.subject._cached_state["updated_at"] <= time()
|
|
|
)
|
|
|
@@ -144,19 +161,19 @@ class TestDevice(IsolatedAsyncioTestCase):
|
|
|
Exception("Error"),
|
|
|
Exception("Error"),
|
|
|
Exception("Error"),
|
|
|
- {"dps": {1: False}},
|
|
|
+ {"dps": {"1": False}},
|
|
|
]
|
|
|
|
|
|
self.subject.refresh()
|
|
|
|
|
|
self.assertEqual(self.subject._api.status.call_count, 4)
|
|
|
- self.assertEqual(self.subject._cached_state[1], False)
|
|
|
+ self.assertEqual(self.subject._cached_state["1"], False)
|
|
|
|
|
|
def test_refresh_clears_cached_state_and_pending_updates_after_failing_four_times(
|
|
|
self,
|
|
|
):
|
|
|
- self.subject._cached_state = {1: True}
|
|
|
- self.subject._pending_updates = {1: False}
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
+ self.subject._pending_updates = {"1": False}
|
|
|
self.subject._api.status.side_effect = [
|
|
|
Exception("Error"),
|
|
|
Exception("Error"),
|
|
|
@@ -187,8 +204,8 @@ class TestDevice(IsolatedAsyncioTestCase):
|
|
|
)
|
|
|
|
|
|
def test_reset_cached_state_clears_cached_state_and_pending_updates(self):
|
|
|
- self.subject._cached_state = {1: True, "updated_at": time()}
|
|
|
- self.subject._pending_updates = {1: False}
|
|
|
+ self.subject._cached_state = {"1": True, "updated_at": time()}
|
|
|
+ self.subject._pending_updates = {"1": False}
|
|
|
|
|
|
self.subject._reset_cached_state()
|
|
|
|
|
|
@@ -196,41 +213,88 @@ class TestDevice(IsolatedAsyncioTestCase):
|
|
|
self.assertEqual(self.subject._pending_updates, {})
|
|
|
|
|
|
def test_get_property_returns_value_from_cached_state(self):
|
|
|
- self.subject._cached_state = {1: True}
|
|
|
- self.assertEqual(self.subject.get_property(1), True)
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
+ self.assertEqual(self.subject.get_property("1"), True)
|
|
|
|
|
|
def test_get_property_returns_pending_update_value(self):
|
|
|
- self.subject._pending_updates = {1: {"value": False, "updated_at": time() - 9}}
|
|
|
- self.assertEqual(self.subject.get_property(1), False)
|
|
|
+ self.subject._pending_updates = {
|
|
|
+ "1": {"value": False, "updated_at": time() - 9}
|
|
|
+ }
|
|
|
+ self.assertEqual(self.subject.get_property("1"), False)
|
|
|
|
|
|
def test_pending_update_value_overrides_cached_value(self):
|
|
|
- self.subject._cached_state = {1: True}
|
|
|
- self.subject._pending_updates = {1: {"value": False, "updated_at": time() - 9}}
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
+ self.subject._pending_updates = {
|
|
|
+ "1": {"value": False, "updated_at": time() - 9}
|
|
|
+ }
|
|
|
|
|
|
- self.assertEqual(self.subject.get_property(1), False)
|
|
|
+ self.assertEqual(self.subject.get_property("1"), False)
|
|
|
|
|
|
def test_expired_pending_update_value_does_not_override_cached_value(self):
|
|
|
- self.subject._cached_state = {1: True}
|
|
|
- self.subject._pending_updates = {1: {"value": False, "updated_at": time() - 10}}
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
+ self.subject._pending_updates = {
|
|
|
+ "1": {"value": False, "updated_at": time() - 10}
|
|
|
+ }
|
|
|
|
|
|
- self.assertEqual(self.subject.get_property(1), True)
|
|
|
+ self.assertEqual(self.subject.get_property("1"), True)
|
|
|
|
|
|
def test_get_property_returns_none_when_value_does_not_exist(self):
|
|
|
- self.subject._cached_state = {1: True}
|
|
|
- self.assertIs(self.subject.get_property(2), None)
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
+ self.assertIs(self.subject.get_property("2"), None)
|
|
|
|
|
|
async def test_async_set_property_schedules_job(self):
|
|
|
async_job = AsyncMock()
|
|
|
self.subject._hass.async_add_executor_job.return_value = awaitable = async_job()
|
|
|
|
|
|
- await self.subject.async_set_property(1, False)
|
|
|
+ await self.subject.async_set_property("1", False)
|
|
|
|
|
|
self.subject._hass.async_add_executor_job.assert_called_once_with(
|
|
|
- self.subject.set_property, 1, False
|
|
|
+ self.subject.set_property, "1", False
|
|
|
)
|
|
|
async_job.assert_awaited()
|
|
|
|
|
|
def test_set_property_immediately_stores_new_value_to_pending_updates(self):
|
|
|
- self.subject.set_property(1, False)
|
|
|
- self.subject._cached_state = {1: True}
|
|
|
- self.assertEqual(self.subject.get_property(1), False)
|
|
|
+ self.subject.set_property("1", False)
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
+ self.assertEqual(self.subject.get_property("1"), False)
|
|
|
+
|
|
|
+ def test_debounces_multiple_set_calls_into_one_api_call(self):
|
|
|
+ with patch("custom_components.goldair_climate.device.Timer") as mock:
|
|
|
+ self.subject.set_property("1", True)
|
|
|
+ mock.assert_called_once_with(1, self.subject._send_pending_updates)
|
|
|
+
|
|
|
+ debounce = self.subject._debounce
|
|
|
+ mock.reset_mock()
|
|
|
+
|
|
|
+ self.subject.set_property("2", False)
|
|
|
+ debounce.cancel.assert_called_once()
|
|
|
+ mock.assert_called_once_with(1, self.subject._send_pending_updates)
|
|
|
+
|
|
|
+ self.subject._api.generate_payload.return_value = "payload"
|
|
|
+ self.subject._send_pending_updates()
|
|
|
+ self.subject._api.generate_payload.assert_called_once_with(
|
|
|
+ "set", {"1": True, "2": False}
|
|
|
+ )
|
|
|
+ self.subject._api._send_receive.assert_called_once_with("payload")
|
|
|
+
|
|
|
+ def test_set_properties_takes_no_action_when_no_properties_are_provided(self):
|
|
|
+ with patch("custom_components.goldair_climate.device.Timer") as mock:
|
|
|
+ self.subject._set_properties({})
|
|
|
+ mock.assert_not_called()
|
|
|
+
|
|
|
+ def test_anticipate_property_value_updates_cached_state(self):
|
|
|
+ self.subject._cached_state = {"1": True}
|
|
|
+ self.subject.anticipate_property_value("1", False)
|
|
|
+ self.assertEqual(self.subject._cached_state["1"], False)
|
|
|
+
|
|
|
+ def test_get_key_for_value_returns_key_from_object_matching_value(self):
|
|
|
+ obj = {"key1": "value1", "key2": "value2"}
|
|
|
+
|
|
|
+ self.assertEqual(GoldairTuyaDevice.get_key_for_value(obj, "value1"), "key1")
|
|
|
+ self.assertEqual(GoldairTuyaDevice.get_key_for_value(obj, "value2"), "key2")
|
|
|
+
|
|
|
+ def test_get_key_for_value_returns_fallback_when_value_not_found(self):
|
|
|
+ obj = {"key1": "value1", "key2": "value2"}
|
|
|
+ self.assertEqual(
|
|
|
+ GoldairTuyaDevice.get_key_for_value(obj, "value3", fallback="fb"), "fb"
|
|
|
+ )
|