From b69576d6de461c0c0382eecc7e6649465c2a4347 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Fri, 27 Jan 2023 22:15:27 -0500 Subject: [PATCH] Add D-link tests (#86825) * Fix D-Link config flow auth * Add tests to D-Link * pyupgrade --- .coveragerc | 3 - tests/components/dlink/conftest.py | 82 +++++++++++++++++++--- tests/components/dlink/test_config_flow.py | 16 +++-- tests/components/dlink/test_init.py | 75 ++++++++++++++++++++ tests/components/dlink/test_switch.py | 77 ++++++++++++++++++++ 5 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 tests/components/dlink/test_init.py create mode 100644 tests/components/dlink/test_switch.py diff --git a/.coveragerc b/.coveragerc index 32b7257ea00..c311950268c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -205,10 +205,7 @@ omit = homeassistant/components/discord/notify.py homeassistant/components/dlib_face_detect/image_processing.py homeassistant/components/dlib_face_identify/image_processing.py - homeassistant/components/dlink/__init__.py homeassistant/components/dlink/data.py - homeassistant/components/dlink/entity.py - homeassistant/components/dlink/switch.py homeassistant/components/dominos/* homeassistant/components/doods/* homeassistant/components/doorbird/__init__.py diff --git a/tests/components/dlink/conftest.py b/tests/components/dlink/conftest.py index 21942c992df..31e79fe61df 100644 --- a/tests/components/dlink/conftest.py +++ b/tests/components/dlink/conftest.py @@ -1,5 +1,6 @@ """Configure pytest for D-Link tests.""" +from collections.abc import Awaitable, Callable, Generator from copy import deepcopy from unittest.mock import MagicMock, patch @@ -10,6 +11,7 @@ from homeassistant.components.dlink.const import CONF_USE_LEGACY_PROTOCOL, DOMAI from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import format_mac +from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -40,6 +42,8 @@ CONF_DHCP_FLOW_NEW_IP = dhcp.DhcpServiceInfo( hostname="dsp-w215", ) +ComponentSetup = Callable[[], Awaitable[None]] + def create_entry(hass: HomeAssistant) -> MockConfigEntry: """Create fixture for adding config entry in Home Assistant.""" @@ -67,24 +71,86 @@ def mocked_plug() -> MagicMock: """Create mocked plug device.""" mocked_plug = MagicMock() mocked_plug.state = "OFF" - mocked_plug.temperature = 0 - mocked_plug.current_consumption = "N/A" - mocked_plug.total_consumption = "N/A" - mocked_plug.authenticated = ("0123456789ABCDEF0123456789ABCDEF", "ABCDefGHiJ") + mocked_plug.temperature = "33" + mocked_plug.current_consumption = "50" + mocked_plug.total_consumption = "1040" + mocked_plug.authenticated = None + mocked_plug.use_legacy_protocol = False + mocked_plug.model_name = "DSP-W215" return mocked_plug @pytest.fixture -def mocked_plug_no_auth(mocked_plug: MagicMock) -> MagicMock: - """Create mocked unauthenticated plug device.""" - mocked_plug = deepcopy(mocked_plug) - mocked_plug.authenticated = None +def mocked_plug_legacy() -> MagicMock: + """Create mocked legacy plug device.""" + mocked_plug = MagicMock() + mocked_plug.state = "OFF" + mocked_plug.temperature = "N/A" + mocked_plug.current_consumption = "N/A" + mocked_plug.total_consumption = "N/A" + mocked_plug.authenticated = ("0123456789ABCDEF0123456789ABCDEF", "ABCDefGHiJ") + mocked_plug.use_legacy_protocol = True + mocked_plug.model_name = "DSP-W215" return mocked_plug +@pytest.fixture +def mocked_plug_legacy_no_auth(mocked_plug_legacy: MagicMock) -> MagicMock: + """Create mocked legacy unauthenticated plug device.""" + mocked_plug_legacy = deepcopy(mocked_plug_legacy) + mocked_plug_legacy.authenticated = None + return mocked_plug_legacy + + def patch_config_flow(mocked_plug: MagicMock): """Patch D-Link Smart Plug config flow.""" return patch( "homeassistant.components.dlink.config_flow.SmartPlug", return_value=mocked_plug, ) + + +def patch_setup(mocked_plug: MagicMock): + """Patch D-Link Smart Plug object.""" + return patch( + "homeassistant.components.dlink.SmartPlug", + return_value=mocked_plug, + ) + + +async def mock_setup_integration( + hass: HomeAssistant, + mocked_plug: MagicMock, +) -> None: + """Set up the D-Link integration in Home Assistant.""" + with patch_setup(mocked_plug): + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + + +@pytest.fixture +async def setup_integration( + hass: HomeAssistant, + config_entry_with_uid: MockConfigEntry, + mocked_plug: MagicMock, +) -> Generator[ComponentSetup, None, None]: + """Set up the D-Link integration in Home Assistant.""" + + async def func() -> None: + await mock_setup_integration(hass, mocked_plug) + + return func + + +@pytest.fixture +async def setup_integration_legacy( + hass: HomeAssistant, + config_entry_with_uid: MockConfigEntry, + mocked_plug_legacy: MagicMock, +) -> Generator[ComponentSetup, None, None]: + """Set up the D-Link integration in Home Assistant with different data.""" + + async def func() -> None: + await mock_setup_integration(hass, mocked_plug_legacy) + + return func diff --git a/tests/components/dlink/test_config_flow.py b/tests/components/dlink/test_config_flow.py index 3e5bdf2106a..e9ee29134aa 100644 --- a/tests/components/dlink/test_config_flow.py +++ b/tests/components/dlink/test_config_flow.py @@ -53,10 +53,12 @@ async def test_flow_user_already_configured( async def test_flow_user_cannot_connect( - hass: HomeAssistant, mocked_plug: MagicMock, mocked_plug_no_auth: MagicMock + hass: HomeAssistant, + mocked_plug_legacy: MagicMock, + mocked_plug_legacy_no_auth: MagicMock, ) -> None: """Test user initialized flow with unreachable server.""" - with patch_config_flow(mocked_plug_no_auth): + with patch_config_flow(mocked_plug_legacy_no_auth): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, data=CONF_DATA ) @@ -64,7 +66,7 @@ async def test_flow_user_cannot_connect( assert result["step_id"] == "user" assert result["errors"]["base"] == "cannot_connect" - with patch_config_flow(mocked_plug), _patch_setup_entry(): + with patch_config_flow(mocked_plug_legacy), _patch_setup_entry(): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=CONF_DATA, @@ -127,16 +129,16 @@ async def test_dhcp(hass: HomeAssistant, mocked_plug: MagicMock) -> None: assert result["data"] == CONF_DATA -async def test_dhcp_failed_auth( - hass: HomeAssistant, mocked_plug: MagicMock, mocked_plug_no_auth: MagicMock +async def test_dhcp_failed_legacy_auth( + hass: HomeAssistant, mocked_plug: MagicMock, mocked_plug_legacy_no_auth: MagicMock ) -> None: - """Test we can recovery from failed authentication during dhcp flow.""" + """Test we can recover from failed legacy authentication during dhcp flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_DHCP}, data=CONF_DHCP_FLOW ) assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "confirm_discovery" - with patch_config_flow(mocked_plug_no_auth): + with patch_config_flow(mocked_plug_legacy_no_auth): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=CONF_DHCP_DATA, diff --git a/tests/components/dlink/test_init.py b/tests/components/dlink/test_init.py new file mode 100644 index 00000000000..c931fed78e2 --- /dev/null +++ b/tests/components/dlink/test_init.py @@ -0,0 +1,75 @@ +"""Test D-Link Smart Plug setup.""" +from unittest.mock import MagicMock + +from homeassistant.components.dlink.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + +from .conftest import CONF_DATA, ComponentSetup, patch_setup + +from tests.common import MockConfigEntry + + +async def test_setup_config_and_unload( + hass: HomeAssistant, setup_integration: ComponentSetup +) -> None: + """Test setup and unload.""" + await setup_integration() + + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.state == ConfigEntryState.LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.data == CONF_DATA + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.NOT_LOADED + assert not hass.data.get(DOMAIN) + + +async def test_legacy_setup_config_and_unload( + hass: HomeAssistant, setup_integration_legacy: ComponentSetup +) -> None: + """Test legacy setup and unload.""" + await setup_integration_legacy() + + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.state == ConfigEntryState.LOADED + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.data == CONF_DATA + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.NOT_LOADED + assert not hass.data.get(DOMAIN) + + +async def test_async_setup_entry_not_ready( + hass: HomeAssistant, + config_entry_with_uid: MockConfigEntry, + mocked_plug_legacy_no_auth: MagicMock, +) -> None: + """Test that it throws ConfigEntryNotReady when exception occurs during legacy setup.""" + with patch_setup(mocked_plug_legacy_no_auth): + await hass.config_entries.async_setup(config_entry_with_uid.entry_id) + assert config_entry_with_uid.state == ConfigEntryState.SETUP_RETRY + + +async def test_device_info( + hass: HomeAssistant, setup_integration: ComponentSetup +) -> None: + """Test device info.""" + await setup_integration() + + entry = hass.config_entries.async_entries(DOMAIN)[0] + device_registry = dr.async_get(hass) + device = device_registry.async_get_device({(DOMAIN, entry.entry_id)}) + + assert device.connections == {("mac", "aa:bb:cc:dd:ee:ff")} + assert device.identifiers == {(DOMAIN, entry.entry_id)} + assert device.manufacturer == "D-Link" + assert device.model == "DSP-W215" + assert device.name == "Mock Title" diff --git a/tests/components/dlink/test_switch.py b/tests/components/dlink/test_switch.py new file mode 100644 index 00000000000..66c40892a66 --- /dev/null +++ b/tests/components/dlink/test_switch.py @@ -0,0 +1,77 @@ +"""Switch tests for the D-Link Smart Plug integration.""" +from collections.abc import Awaitable, Callable + +from homeassistant.components.dlink import DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from .conftest import ComponentSetup + +from tests.components.repairs import get_repairs + + +async def test_switch_state( + hass: HomeAssistant, + hass_ws_client: Callable[[HomeAssistant], Awaitable[None]], + setup_integration: ComponentSetup, +) -> None: + """Test we get the switch status.""" + assert await async_setup_component( + hass, + SWITCH_DOMAIN, + { + SWITCH_DOMAIN: { + "platform": DOMAIN, + "host": "1.2.3.4", + "username": "admin", + "password": "123456", + "use_legacy_protocol": True, + } + }, + ) + await hass.async_block_till_done() + issues = await get_repairs(hass, hass_ws_client) + assert len(issues) == 1 + assert issues[0]["issue_id"] == "deprecated_yaml" + + await setup_integration() + + entity_id = "switch.mock_title_switch" + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + assert state.attributes["total_consumption"] == 1040.0 + assert state.attributes["temperature"] == 33 + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + assert hass.states.get(entity_id).state == STATE_ON + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + assert hass.states.get(entity_id).state == STATE_OFF + + +async def test_switch_no_value( + hass: HomeAssistant, setup_integration_legacy: ComponentSetup +) -> None: + """Test we handle 'N/A' being passed by the pypi package.""" + await setup_integration_legacy() + + state = hass.states.get("switch.mock_title_switch") + assert state.state == STATE_OFF + assert state.attributes["total_consumption"] is None + assert state.attributes["temperature"] is None