diff --git a/.coveragerc b/.coveragerc index 6f410b62cf1..a7a6298a69f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -946,8 +946,6 @@ omit = homeassistant/components/russound_rnet/media_player.py homeassistant/components/sabnzbd/* homeassistant/components/saj/sensor.py - homeassistant/components/samsungtv/bridge.py - homeassistant/components/samsungtv/diagnostics.py homeassistant/components/satel_integra/* homeassistant/components/schluter/* homeassistant/components/scrape/sensor.py diff --git a/homeassistant/components/brunt/__init__.py b/homeassistant/components/brunt/__init__.py index 988a96ce08e..5fe3f7d0012 100644 --- a/homeassistant/components/brunt/__init__.py +++ b/homeassistant/components/brunt/__init__.py @@ -11,7 +11,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DATA_BAPI, DATA_COOR, DOMAIN, PLATFORMS, REGULAR_INTERVAL @@ -21,11 +20,9 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Brunt using config flow.""" - session = async_get_clientsession(hass) bapi = BruntClientAsync( username=entry.data[CONF_USERNAME], password=entry.data[CONF_PASSWORD], - session=session, ) try: await bapi.async_login() diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 56f49307662..11b122e2b5a 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -187,7 +187,11 @@ class CloudAlexaConfig(alexa_config.AbstractConfig): self._alexa_sync_unsub = None return - if ALEXA_DOMAIN not in self.hass.config.components and self.enabled: + if ( + ALEXA_DOMAIN not in self.hass.config.components + and self.enabled + and self.hass.is_running + ): await async_setup_component(self.hass, ALEXA_DOMAIN, {}) if self.should_report_state != self.is_reporting_states: diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 4ae3b44f1fe..7988a648901 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -181,7 +181,11 @@ class CloudGoogleConfig(AbstractConfig): self.async_disable_local_sdk() return - if self.enabled and GOOGLE_DOMAIN not in self.hass.config.components: + if ( + self.enabled + and GOOGLE_DOMAIN not in self.hass.config.components + and self.hass.is_running + ): await async_setup_component(self.hass, GOOGLE_DOMAIN, {}) if self.should_report_state != self.is_reporting_state: diff --git a/homeassistant/components/goalfeed/manifest.json b/homeassistant/components/goalfeed/manifest.json index 5b064551cf9..aed773350f6 100644 --- a/homeassistant/components/goalfeed/manifest.json +++ b/homeassistant/components/goalfeed/manifest.json @@ -2,7 +2,7 @@ "domain": "goalfeed", "name": "Goalfeed", "documentation": "https://www.home-assistant.io/integrations/goalfeed", - "requirements": ["pysher==1.0.1"], + "requirements": ["pysher==1.0.7"], "codeowners": [], "iot_class": "cloud_push" } diff --git a/homeassistant/components/hue/manifest.json b/homeassistant/components/hue/manifest.json index a21a3ace72b..af346d1f8f6 100644 --- a/homeassistant/components/hue/manifest.json +++ b/homeassistant/components/hue/manifest.json @@ -3,7 +3,7 @@ "name": "Philips Hue", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hue", - "requirements": ["aiohue==4.1.2"], + "requirements": ["aiohue==4.2.0"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/homeassistant/components/hue/scene.py b/homeassistant/components/hue/scene.py index 8e894b4e295..c21a96e4d9a 100644 --- a/homeassistant/components/hue/scene.py +++ b/homeassistant/components/hue/scene.py @@ -72,7 +72,7 @@ async def async_setup_entry( vol.Coerce(int), vol.Range(min=0, max=255) ), }, - "async_activate", + "_async_activate", ) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index aad73cd9f1a..5b41c916f8e 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -20,6 +20,7 @@ from homeassistant.const import ( CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_VALUE_TEMPLATE, + STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -105,7 +106,7 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize the MQTT binary sensor.""" - self._state = None + self._state: bool | None = None self._expiration_trigger = None self._delay_listener = None expire_after = config.get(CONF_EXPIRE_AFTER) @@ -124,6 +125,9 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): and expire_after > 0 and (last_state := await self.async_get_last_state()) is not None and last_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + # We might have set up a trigger already after subscribing from + # super().async_added_to_hass(), then we should not restore state + and not self._expiration_trigger ): expiration_at = last_state.last_changed + timedelta(seconds=expire_after) if expiration_at < (time_now := dt_util.utcnow()): @@ -131,12 +135,8 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): _LOGGER.debug("Skip state recovery after reload for %s", self.entity_id) return self._expired = False - self._state = last_state.state + self._state = last_state.state == STATE_ON - if self._expiration_trigger: - # We might have set up a trigger already after subscribing from - # super().async_added_to_hass() - self._expiration_trigger() self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at ) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 6dddf496e02..13f58a8494a 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -171,6 +171,9 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): and expire_after > 0 and (last_state := await self.async_get_last_state()) is not None and last_state.state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] + # We might have set up a trigger already after subscribing from + # super().async_added_to_hass(), then we should not restore state + and not self._expiration_trigger ): expiration_at = last_state.last_changed + timedelta(seconds=expire_after) if expiration_at < (time_now := dt_util.utcnow()): @@ -180,10 +183,6 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): self._expired = False self._state = last_state.state - if self._expiration_trigger: - # We might have set up a trigger already after subscribing from - # super().async_added_to_hass() - self._expiration_trigger() self._expiration_trigger = async_track_point_in_utc_time( self.hass, self._value_is_expired, expiration_at ) diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index d509da91304..d4d23a03389 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -358,6 +358,13 @@ class SamsungTVWSBridge(SamsungTVBridge): self._notify_callback() except (WebSocketException, OSError): self._remote = None + else: + if self.token != self._remote.token: + LOGGER.debug( + "SamsungTVWSBridge has provided a new token %s", + self._remote.token, + ) + self.token = self._remote.token return self._remote def stop(self) -> None: diff --git a/homeassistant/components/tuya/base.py b/homeassistant/components/tuya/base.py index 15e57f223e9..c6050255901 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -41,15 +41,15 @@ class IntegerTypeData: @property def step_scaled(self) -> float: """Return the step scaled.""" - return self.scale_value(self.step) + return self.step / (10 ** self.scale) def scale_value(self, value: float | int) -> float: """Scale a value.""" - return value * 1.0 / (10 ** self.scale) + return value * self.step / (10 ** self.scale) def scale_value_back(self, value: float | int) -> int: """Return raw value for scaled.""" - return int(value * (10 ** self.scale)) + return int((value * (10 ** self.scale)) / self.step) def remap_value_to( self, @@ -82,7 +82,7 @@ class IntegerTypeData: min=int(parsed["min"]), max=int(parsed["max"]), scale=float(parsed["scale"]), - step=float(parsed["step"]), + step=max(float(parsed["step"]), 1), unit=parsed.get("unit"), type=parsed.get("type"), ) diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index a97a27a7453..c29c46b6bf8 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -155,8 +155,12 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): self._attr_temperature_unit = TEMP_CELSIUS # Figure out current temperature, use preferred unit or what is available - celsius_type = self.find_dpcode(DPCode.TEMP_CURRENT, dptype=DPType.INTEGER) - farhenheit_type = self.find_dpcode(DPCode.TEMP_CURRENT_F, dptype=DPType.INTEGER) + celsius_type = self.find_dpcode( + (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER + ) + farhenheit_type = self.find_dpcode( + (DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F), dptype=DPType.INTEGER + ) if farhenheit_type and ( prefered_temperature_unit == TEMP_FAHRENHEIT or (prefered_temperature_unit == TEMP_CELSIUS and not celsius_type) diff --git a/homeassistant/components/tuya/const.py b/homeassistant/components/tuya/const.py index 6d6a2aa2937..4d7f9d8e166 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -345,6 +345,11 @@ class DPCode(StrEnum): TOTAL_CLEAN_COUNT = "total_clean_count" TOTAL_CLEAN_TIME = "total_clean_time" TOTAL_FORWARD_ENERGY = "total_forward_energy" + TOTAL_TIME = "total_time" + TOTAL_PM = "total_pm" + TVOC = "tvoc" + UPPER_TEMP = "upper_temp" + UPPER_TEMP_F = "upper_temp_f" UV = "uv" # UV sterilization VA_BATTERY = "va_battery" VA_HUMIDITY = "va_humidity" diff --git a/homeassistant/const.py b/homeassistant/const.py index c206a4f3f7a..87f4ff4798f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 2 -PATCH_VERSION: Final = "7" +PATCH_VERSION: Final = "8" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/requirements_all.txt b/requirements_all.txt index e94d0c1acae..be01347cb8b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -191,7 +191,7 @@ aiohomekit==0.6.11 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.1.2 +aiohue==4.2.0 # homeassistant.components.homewizard aiohwenergy==0.8.0 @@ -1831,7 +1831,7 @@ pyserial==3.5 pysesame2==1.0.1 # homeassistant.components.goalfeed -pysher==1.0.1 +pysher==1.0.7 # homeassistant.components.sia pysiaalarm==3.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8985459f394..587ec2ec374 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ aiohomekit==0.6.11 aiohttp_cors==0.7.0 # homeassistant.components.hue -aiohue==4.1.2 +aiohue==4.2.0 # homeassistant.components.homewizard aiohwenergy==0.8.0 diff --git a/setup.cfg b/setup.cfg index a9634ea3b6f..b08c1185f15 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.2.7 +version = 2022.2.8 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 diff --git a/tests/components/samsungtv/__init__.py b/tests/components/samsungtv/__init__.py index 89768221665..4ad1622c6ca 100644 --- a/tests/components/samsungtv/__init__.py +++ b/tests/components/samsungtv/__init__.py @@ -1,14 +1 @@ """Tests for the samsungtv component.""" -from homeassistant.components.samsungtv.const import DOMAIN as SAMSUNGTV_DOMAIN -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry - - -async def setup_samsungtv(hass: HomeAssistant, config: dict): - """Set up mock Samsung TV.""" - - entry = MockConfigEntry(domain=SAMSUNGTV_DOMAIN, data=config) - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index 05c51fdf591..14f33524f52 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -2,26 +2,29 @@ from unittest.mock import Mock, patch import pytest +from samsungctl import Remote +from samsungtvws import SamsungTVWS import homeassistant.util.dt as dt_util -RESULT_ALREADY_CONFIGURED = "already_configured" -RESULT_ALREADY_IN_PROGRESS = "already_in_progress" + +@pytest.fixture(autouse=True) +def fake_host_fixture() -> None: + """Patch gethostbyname.""" + with patch( + "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", + return_value="fake_host", + ): + yield @pytest.fixture(name="remote") def remote_fixture(): """Patch the samsungctl Remote.""" - with patch( - "homeassistant.components.samsungtv.bridge.Remote" - ) as remote_class, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - remote = Mock() + with patch("homeassistant.components.samsungtv.bridge.Remote") as remote_class: + remote = Mock(Remote) remote.__enter__ = Mock() remote.__exit__ = Mock() - remote.port.return_value = 55000 remote_class.return_value = remote yield remote @@ -31,14 +34,10 @@ def remotews_fixture(): """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews_class, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - remotews = Mock() - remotews.__enter__ = Mock() + ) as remotews_class: + remotews = Mock(SamsungTVWS) + remotews.__enter__ = Mock(return_value=remotews) remotews.__exit__ = Mock() - remotews.port.return_value = 8002 remotews.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "device": { @@ -49,8 +48,8 @@ def remotews_fixture(): "networkType": "wireless", }, } + remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews - remotews_class().__enter__().token = "FAKE_TOKEN" yield remotews @@ -59,16 +58,13 @@ def remotews_no_device_info_fixture(): """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews_class, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - remotews = Mock() - remotews.__enter__ = Mock() + ) as remotews_class: + remotews = Mock(SamsungTVWS) + remotews.__enter__ = Mock(return_value=remotews) remotews.__exit__ = Mock() remotews.rest_device_info.return_value = None + remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews - remotews_class().__enter__().token = "FAKE_TOKEN" yield remotews @@ -77,12 +73,9 @@ def remotews_soundbar_fixture(): """Patch the samsungtvws SamsungTVWS.""" with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews_class, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - remotews = Mock() - remotews.__enter__ = Mock() + ) as remotews_class: + remotews = Mock(SamsungTVWS) + remotews.__enter__ = Mock(return_value=remotews) remotews.__exit__ = Mock() remotews.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -94,8 +87,8 @@ def remotews_soundbar_fixture(): "type": "Samsung SoundBar", }, } + remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews - remotews_class().__enter__().token = "FAKE_TOKEN" yield remotews diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index c2a258b2afa..4eedb7c2107 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -1,8 +1,9 @@ """Tests for Samsung TV config flow.""" import socket -from unittest.mock import Mock, PropertyMock, call, patch +from unittest.mock import Mock, call, patch from samsungctl.exceptions import AccessDenied, UnhandledResponse +from samsungtvws import SamsungTVWS from samsungtvws.exceptions import ConnectionFailure, HttpApiError from websocket import WebSocketException, WebSocketProtocolException @@ -43,10 +44,9 @@ from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry -from tests.components.samsungtv.conftest import ( - RESULT_ALREADY_CONFIGURED, - RESULT_ALREADY_IN_PROGRESS, -) + +RESULT_ALREADY_CONFIGURED = "already_configured" +RESULT_ALREADY_IN_PROGRESS = "already_in_progress" MOCK_IMPORT_DATA = { CONF_HOST: "fake_host", @@ -232,9 +232,7 @@ async def test_user_websocket(hass: HomeAssistant, remotews: Mock): assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" -async def test_user_legacy_missing_auth( - hass: HomeAssistant, remote: Mock, remotews: Mock -): +async def test_user_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): """Test starting a flow by user with authentication.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -248,7 +246,7 @@ async def test_user_legacy_missing_auth( assert result["reason"] == RESULT_AUTH_MISSING -async def test_user_legacy_not_supported(hass: HomeAssistant, remote: Mock): +async def test_user_legacy_not_supported(hass: HomeAssistant): """Test starting a flow by user for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -262,7 +260,7 @@ async def test_user_legacy_not_supported(hass: HomeAssistant, remote: Mock): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_user_websocket_not_supported(hass: HomeAssistant, remotews: Mock): +async def test_user_websocket_not_supported(hass: HomeAssistant): """Test starting a flow by user for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -279,7 +277,7 @@ async def test_user_websocket_not_supported(hass: HomeAssistant, remotews: Mock) assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_user_not_successful(hass: HomeAssistant, remotews: Mock): +async def test_user_not_successful(hass: HomeAssistant): """Test starting a flow by user but no connection found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -295,7 +293,7 @@ async def test_user_not_successful(hass: HomeAssistant, remotews: Mock): assert result["reason"] == RESULT_CANNOT_CONNECT -async def test_user_not_successful_2(hass: HomeAssistant, remotews: Mock): +async def test_user_not_successful_2(hass: HomeAssistant): """Test starting a flow by user but no connection found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -374,9 +372,7 @@ async def test_ssdp_noprefix(hass: HomeAssistant, remote: Mock, no_mac_address: assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172df" -async def test_ssdp_legacy_missing_auth( - hass: HomeAssistant, remote: Mock, remotews: Mock -): +async def test_ssdp_legacy_missing_auth(hass: HomeAssistant, remotews: Mock): """Test starting a flow from discovery with authentication.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -452,7 +448,7 @@ async def test_ssdp_websocket_success_populates_mac_address( assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" -async def test_ssdp_websocket_not_supported(hass: HomeAssistant, remote: Mock): +async def test_ssdp_websocket_not_supported(hass: HomeAssistant): """Test starting a flow from discovery for not supported device.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -482,9 +478,7 @@ async def test_ssdp_model_not_supported(hass: HomeAssistant, remote: Mock): assert result["reason"] == RESULT_NOT_SUPPORTED -async def test_ssdp_not_successful( - hass: HomeAssistant, remote: Mock, no_mac_address: Mock -): +async def test_ssdp_not_successful(hass: HomeAssistant, no_mac_address: Mock): """Test starting a flow from discovery but no device found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -512,9 +506,7 @@ async def test_ssdp_not_successful( assert result["reason"] == RESULT_CANNOT_CONNECT -async def test_ssdp_not_successful_2( - hass: HomeAssistant, remote: Mock, no_mac_address: Mock -): +async def test_ssdp_not_successful_2(hass: HomeAssistant, no_mac_address: Mock): """Test starting a flow from discovery but no device found.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -604,15 +596,11 @@ async def test_import_legacy(hass: HomeAssistant, remote: Mock, no_mac_address: """Test importing from yaml with hostname.""" no_mac_address.return_value = "aa:bb:cc:dd:ee:ff" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_DATA, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_DATA, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake" @@ -634,15 +622,11 @@ async def test_import_legacy_without_name( no_mac_address: Mock, ): """Test importing from yaml without a name.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_DATA_WITHOUT_NAME, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_DATA_WITHOUT_NAME, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake_host" @@ -658,15 +642,11 @@ async def test_import_legacy_without_name( async def test_import_websocket(hass: HomeAssistant, remotews: Mock): """Test importing from yaml with hostname.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_WSDATA, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_WSDATA, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake" @@ -680,15 +660,11 @@ async def test_import_websocket(hass: HomeAssistant, remotews: Mock): async def test_import_websocket_without_port(hass: HomeAssistant, remotews: Mock): """Test importing from yaml with hostname by no port.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=MOCK_IMPORT_WSDATA, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=MOCK_IMPORT_WSDATA, + ) await hass.async_block_till_done() assert result["type"] == "create_entry" assert result["title"] == "fake" @@ -817,21 +793,14 @@ async def test_zeroconf_and_dhcp_same_time(hass: HomeAssistant, remotews: Mock): assert result2["reason"] == "already_in_progress" -async def test_autodetect_websocket(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_autodetect_websocket(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ), patch( - "homeassistant.components.samsungtv.bridge.SamsungTVWS" - ) as remotews: - enter = Mock() - type(enter).token = PropertyMock(return_value="123456789") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + ), patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remotews: + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock(return_value=False) remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -845,6 +814,7 @@ async def test_autodetect_websocket(hass: HomeAssistant, remote: Mock, remotews: "type": "Samsung SmartTV", }, } + remote.token = "123456789" remotews.return_value = remote result = await hass.config_entries.flow.async_init( @@ -865,23 +835,18 @@ async def test_autodetect_websocket(hass: HomeAssistant, remote: Mock, remotews: assert entries[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" -async def test_websocket_no_mac(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_websocket_no_mac(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ), patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS" ) as remotews, patch( "getmac.get_mac_address", return_value="gg:hh:ii:ll:mm:nn" ): - enter = Mock() - type(enter).token = PropertyMock(return_value="123456789") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock(return_value=False) remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -893,6 +858,7 @@ async def test_websocket_no_mac(hass: HomeAssistant, remote: Mock, remotews: Moc "type": "Samsung SmartTV", }, } + remote.token = "123456789" remotews.return_value = remote result = await hass.config_entries.flow.async_init( @@ -914,15 +880,12 @@ async def test_websocket_no_mac(hass: HomeAssistant, remote: Mock, remotews: Moc assert entries[0].data[CONF_MAC] == "gg:hh:ii:ll:mm:nn" -async def test_autodetect_auth_missing(hass: HomeAssistant, remote: Mock): +async def test_autodetect_auth_missing(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=[AccessDenied("Boom")], - ) as remote, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remote: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA ) @@ -932,15 +895,12 @@ async def test_autodetect_auth_missing(hass: HomeAssistant, remote: Mock): assert remote.call_args_list == [call(AUTODETECT_LEGACY)] -async def test_autodetect_not_supported(hass: HomeAssistant, remote: Mock): +async def test_autodetect_not_supported(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", side_effect=[UnhandledResponse("Boom")], - ) as remote, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remote: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA ) @@ -962,7 +922,7 @@ async def test_autodetect_legacy(hass: HomeAssistant, remote: Mock): assert result["data"][CONF_PORT] == LEGACY_PORT -async def test_autodetect_none(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_autodetect_none(hass: HomeAssistant): """Test for send key with autodetection of protocol.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", @@ -970,10 +930,7 @@ async def test_autodetect_none(hass: HomeAssistant, remote: Mock, remotews: Mock ) as remote, patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", side_effect=OSError("Boom"), - ) as remotews, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + ) as remotews: result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_USER_DATA ) @@ -990,7 +947,7 @@ async def test_autodetect_none(hass: HomeAssistant, remote: Mock, remotews: Mock ] -async def test_update_old_entry(hass: HomeAssistant, remote: Mock, remotews: Mock): +async def test_update_old_entry(hass: HomeAssistant, remotews: Mock): """Test update of old entry.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: remote().rest_device_info.return_value = { @@ -1266,9 +1223,6 @@ async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", side_effect=ConnectionFailure, - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -1289,7 +1243,7 @@ async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): assert result3["reason"] == "reauth_successful" -async def test_form_reauth_websocket_not_supported(hass, remotews: Mock): +async def test_form_reauth_websocket_not_supported(hass): """Test reauthenticate websocket when the device is not supported.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY) entry.add_to_hass(hass) @@ -1304,9 +1258,6 @@ async def test_form_reauth_websocket_not_supported(hass, remotews: Mock): with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", side_effect=WebSocketException, - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/samsungtv/test_diagnostics.py b/tests/components/samsungtv/test_diagnostics.py new file mode 100644 index 00000000000..990c25c8f3e --- /dev/null +++ b/tests/components/samsungtv/test_diagnostics.py @@ -0,0 +1,59 @@ +"""Test samsungtv diagnostics.""" +from aiohttp import ClientSession +import pytest + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.components.samsungtv import DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .test_media_player import MOCK_ENTRY_WS_WITH_MAC + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +@pytest.fixture(name="config_entry") +def get_config_entry(hass: HomeAssistant) -> ConfigEntry: + """Create and register mock config entry.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=MOCK_ENTRY_WS_WITH_MAC, + entry_id="123456", + unique_id="any", + ) + config_entry.add_to_hass(hass) + return config_entry + + +@pytest.mark.usefixtures("remotews") +async def test_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry, hass_client: ClientSession +) -> None: + """Test config entry diagnostics.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "entry": { + "data": { + "host": "fake_host", + "ip_address": "test", + "mac": "aa:bb:cc:dd:ee:ff", + "method": "websocket", + "name": "fake", + "port": 8002, + "token": REDACTED, + }, + "disabled_by": None, + "domain": "samsungtv", + "entry_id": "123456", + "options": {}, + "pref_disable_new_entities": False, + "pref_disable_polling": False, + "source": "user", + "title": "Mock Title", + "unique_id": "any", + "version": 2, + } + } diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index bd3e2a51256..e49b8fdc5ee 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -55,27 +55,21 @@ REMOTE_CALL = { async def test_setup(hass: HomeAssistant, remotews: Mock, no_mac_address: Mock): """Test Samsung TV integration is setup.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) + await hass.async_block_till_done() + state = hass.states.get(ENTITY_ID) - await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) - await hass.async_block_till_done() - state = hass.states.get(ENTITY_ID) + # test name and turn_on + assert state + assert state.name == "fake_name" + assert ( + state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON + ) - # test name and turn_on - assert state - assert state.name == "fake_name" - assert ( - state.attributes[ATTR_SUPPORTED_FEATURES] - == SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON - ) - - # test host and port - assert await hass.services.async_call( - DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True - ) + # test host and port + assert await hass.services.async_call( + DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True + ) async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant): @@ -88,9 +82,6 @@ async def test_setup_from_yaml_without_port_device_offline(hass: HomeAssistant): ), patch( "homeassistant.components.samsungtv.bridge.SamsungTVWSBridge.device_info", return_value=None, - ), patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", ): await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() @@ -104,12 +95,8 @@ async def test_setup_from_yaml_without_port_device_online( hass: HomeAssistant, remotews: Mock ): """Test import from yaml when the device is online.""" - with patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): - await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) - await hass.async_block_till_done() + await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) + await hass.async_block_till_done() config_entries_domain = hass.config_entries.async_entries(SAMSUNGTV_DOMAIN) assert len(config_entries_domain) == 1 @@ -118,13 +105,13 @@ async def test_setup_from_yaml_without_port_device_online( async def test_setup_duplicate_config(hass: HomeAssistant, remote: Mock, caplog): """Test duplicate setup of platform.""" - DUPLICATE = { + duplicate = { SAMSUNGTV_DOMAIN: [ MOCK_CONFIG[SAMSUNGTV_DOMAIN][0], MOCK_CONFIG[SAMSUNGTV_DOMAIN][0], ] } - await async_setup_component(hass, SAMSUNGTV_DOMAIN, DUPLICATE) + await async_setup_component(hass, SAMSUNGTV_DOMAIN, duplicate) await hass.async_block_till_done() assert hass.states.get(ENTITY_ID) is None assert len(hass.states.async_all("media_player")) == 0 @@ -132,7 +119,7 @@ async def test_setup_duplicate_config(hass: HomeAssistant, remote: Mock, caplog) async def test_setup_duplicate_entries( - hass: HomeAssistant, remote: Mock, remotews: Mock, no_mac_address: Mock, caplog + hass: HomeAssistant, remote: Mock, remotews: Mock, no_mac_address: Mock ): """Test duplicate setup of platform.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, MOCK_CONFIG) diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index f64634acd3b..3f00475138e 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -2,10 +2,11 @@ import asyncio from datetime import timedelta import logging -from unittest.mock import DEFAULT as DEFAULT_MOCK, Mock, PropertyMock, call, patch +from unittest.mock import DEFAULT as DEFAULT_MOCK, Mock, call, patch import pytest from samsungctl import exceptions +from samsungtvws import SamsungTVWS from samsungtvws.exceptions import ConnectionFailure from websocket import WebSocketException @@ -117,6 +118,9 @@ MOCK_CONFIG_NOTURNON = { ] } +# Fake mac address in all mediaplayer tests. +pytestmark = pytest.mark.usefixtures("no_mac_address") + @pytest.fixture(name="delay") def delay_fixture(): @@ -127,11 +131,6 @@ def delay_fixture(): yield delay -@pytest.fixture(autouse=True) -def mock_no_mac_address(no_mac_address): - """Fake mac address in all mediaplayer tests.""" - - async def setup_samsungtv(hass, config): """Set up mock Samsung TV.""" await async_setup_component(hass, SAMSUNGTV_DOMAIN, config) @@ -150,13 +149,11 @@ async def test_setup_without_turnon(hass, remote): assert hass.states.get(ENTITY_ID_NOTURNON) -async def test_setup_websocket(hass, remotews, mock_now): +async def test_setup_websocket(hass, remotews): """Test setup of platform.""" with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: - enter = Mock() - type(enter).token = PropertyMock(return_value="987654321") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock() remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -168,6 +165,7 @@ async def test_setup_websocket(hass, remotews, mock_now): "networkType": "wireless", }, } + remote.token = "123456789" remote_class.return_value = remote await setup_samsungtv(hass, MOCK_CONFIGWS) @@ -202,10 +200,8 @@ async def test_setup_websocket_2(hass, mock_now): assert entry is config_entries[0] with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: - enter = Mock() - type(enter).token = PropertyMock(return_value="987654321") - remote = Mock() - remote.__enter__ = Mock(return_value=enter) + remote = Mock(SamsungTVWS) + remote.__enter__ = Mock(return_value=remote) remote.__exit__ = Mock() remote.rest_device_info.return_value = { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", @@ -217,6 +213,7 @@ async def test_setup_websocket_2(hass, mock_now): "networkType": "wireless", }, } + remote.token = "987654321" remote_class.return_value = remote assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {}) await hass.async_block_till_done() @@ -742,7 +739,7 @@ async def test_play_media(hass, remote): assert len(sleeps) == 3 -async def test_play_media_invalid_type(hass, remote): +async def test_play_media_invalid_type(hass): """Test for play_media with invalid media type.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: url = "https://example.com" @@ -764,7 +761,7 @@ async def test_play_media_invalid_type(hass, remote): assert remote.call_count == 1 -async def test_play_media_channel_as_string(hass, remote): +async def test_play_media_channel_as_string(hass): """Test for play_media with invalid channel as string.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: url = "https://example.com" @@ -786,7 +783,7 @@ async def test_play_media_channel_as_string(hass, remote): assert remote.call_count == 1 -async def test_play_media_channel_as_non_positive(hass, remote): +async def test_play_media_channel_as_non_positive(hass): """Test for play_media with invalid channel as non positive integer.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: await setup_samsungtv(hass, MOCK_CONFIG) @@ -823,7 +820,7 @@ async def test_select_source(hass, remote): assert remote.close.call_args_list == [call()] -async def test_select_source_invalid_source(hass, remote): +async def test_select_source_invalid_source(hass): """Test for select_source with invalid source.""" with patch("homeassistant.components.samsungtv.bridge.Remote") as remote: await setup_samsungtv(hass, MOCK_CONFIG)