diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 0b3001215a1..c17a26794df 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -84,12 +84,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # For SSDP compat if not entry.data.get(CONF_MAC): - network = await hass.async_add_executor_job(getattr, api.dsm, "network") hass.config_entries.async_update_entry( - entry, data={**entry.data, CONF_MAC: network.macs} + entry, data={**entry.data, CONF_MAC: api.dsm.network.macs} ) - # These all create executor jobs so we do not gather here coordinator_central = SynologyDSMCentralUpdateCoordinator(hass, entry, api) await coordinator_central.async_config_entry_first_refresh() diff --git a/homeassistant/components/synology_dsm/button.py b/homeassistant/components/synology_dsm/button.py index a1337e672f6..0b64985e7ad 100644 --- a/homeassistant/components/synology_dsm/button.py +++ b/homeassistant/components/synology_dsm/button.py @@ -1,7 +1,7 @@ """Support for Synology DSM buttons.""" from __future__ import annotations -from collections.abc import Callable +from collections.abc import Callable, Coroutine from dataclasses import dataclass import logging from typing import Any, Final @@ -27,7 +27,7 @@ LOGGER = logging.getLogger(__name__) class SynologyDSMbuttonDescriptionMixin: """Mixin to describe a Synology DSM button entity.""" - press_action: Callable[[SynoApi], Any] + press_action: Callable[[SynoApi], Callable[[], Coroutine[Any, Any, None]]] @dataclass @@ -43,14 +43,14 @@ BUTTONS: Final = [ name="Reboot", device_class=ButtonDeviceClass.RESTART, entity_category=EntityCategory.CONFIG, - press_action=lambda syno_api: syno_api.async_reboot(), + press_action=lambda syno_api: syno_api.async_reboot, ), SynologyDSMbuttonDescription( key="shutdown", name="Shutdown", icon="mdi:power", entity_category=EntityCategory.CONFIG, - press_action=lambda syno_api: syno_api.async_shutdown(), + press_action=lambda syno_api: syno_api.async_shutdown, ), ] @@ -92,4 +92,4 @@ class SynologyDSMButton(ButtonEntity): self.entity_description.key, self.syno_api.network.hostname, ) - await self.entity_description.press_action(self.syno_api) + await self.entity_description.press_action(self.syno_api)() diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index 65520968e1d..b85ef5f2e3a 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -143,7 +143,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C self._listen_source_updates() await super().async_added_to_hass() - def camera_image( + async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return bytes of camera image.""" @@ -154,7 +154,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C if not self.available: return None try: - return self._api.surveillance_station.get_camera_image(self.entity_description.key, self.snapshot_quality) # type: ignore[no-any-return] + return await self._api.surveillance_station.get_camera_image(self.entity_description.key, self.snapshot_quality) # type: ignore[no-any-return] except ( SynologyDSMAPIErrorException, SynologyDSMRequestException, @@ -178,22 +178,22 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C return self.camera_data.live_view.rtsp # type: ignore[no-any-return] - def enable_motion_detection(self) -> None: + async def async_enable_motion_detection(self) -> None: """Enable motion detection in the camera.""" _LOGGER.debug( "SynoDSMCamera.enable_motion_detection(%s)", self.camera_data.name, ) - self._api.surveillance_station.enable_motion_detection( + await self._api.surveillance_station.enable_motion_detection( self.entity_description.key ) - def disable_motion_detection(self) -> None: + async def async_disable_motion_detection(self) -> None: """Disable motion detection in camera.""" _LOGGER.debug( "SynoDSMCamera.disable_motion_detection(%s)", self.camera_data.name, ) - self._api.surveillance_station.disable_motion_detection( + await self._api.surveillance_station.disable_motion_detection( self.entity_description.key ) diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index 847fc6061c3..d315b3e49c0 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -30,6 +30,7 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import CONF_DEVICE_TOKEN, SYNOLOGY_CONNECTION_EXCEPTIONS @@ -71,21 +72,18 @@ class SynoApi: async def async_setup(self) -> None: """Start interacting with the NAS.""" - await self._hass.async_add_executor_job(self._setup) - - def _setup(self) -> None: - """Start interacting with the NAS in the executor.""" + session = async_get_clientsession(self._hass, self._entry.data[CONF_VERIFY_SSL]) self.dsm = SynologyDSM( + session, self._entry.data[CONF_HOST], self._entry.data[CONF_PORT], self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], - self._entry.data[CONF_VERIFY_SSL], timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get(CONF_DEVICE_TOKEN), ) - self.dsm.login() + await self.dsm.login() # check if surveillance station is used self._with_surveillance_station = bool( @@ -93,7 +91,7 @@ class SynoApi: ) if self._with_surveillance_station: try: - self.dsm.surveillance_station.update() + await self.dsm.surveillance_station.update() except SYNOLOGY_CONNECTION_EXCEPTIONS: self._with_surveillance_station = False self.dsm.reset(SynoSurveillanceStation.API_KEY) @@ -110,16 +108,16 @@ class SynoApi: # check if upgrade is available try: - self.dsm.upgrade.update() + await self.dsm.upgrade.update() except SYNOLOGY_CONNECTION_EXCEPTIONS as ex: self._with_upgrade = False self.dsm.reset(SynoCoreUpgrade.API_KEY) LOGGER.debug("Disabled fetching upgrade data during setup: %s", ex) - self._fetch_device_configuration() + await self._fetch_device_configuration() try: - self._update() + await self._update() except SYNOLOGY_CONNECTION_EXCEPTIONS as err: LOGGER.debug( "Connection error during setup of '%s' with exception: %s", @@ -210,11 +208,11 @@ class SynoApi: self.dsm.reset(self.utilisation) self.utilisation = None - def _fetch_device_configuration(self) -> None: + async def _fetch_device_configuration(self) -> None: """Fetch initial device config.""" self.information = self.dsm.information self.network = self.dsm.network - self.network.update() + await self.network.update() if self._with_security: LOGGER.debug("Enable security api updates for '%s'", self._entry.unique_id) @@ -248,7 +246,7 @@ class SynoApi: async def _syno_api_executer(self, api_call: Callable) -> None: """Synology api call wrapper.""" try: - await self._hass.async_add_executor_job(api_call) + await api_call() except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: LOGGER.debug( "Error from '%s': %s", self._entry.unique_id, err, exc_info=True @@ -274,7 +272,7 @@ class SynoApi: async def async_update(self) -> None: """Update function for updating API information.""" try: - await self._hass.async_add_executor_job(self._update) + await self._update() except SYNOLOGY_CONNECTION_EXCEPTIONS as err: LOGGER.debug( "Connection error during update of '%s' with exception: %s", @@ -286,8 +284,8 @@ class SynoApi: ) await self._hass.config_entries.async_reload(self._entry.entry_id) - def _update(self) -> None: + async def _update(self) -> None: """Update function for updating API information.""" LOGGER.debug("Start data update for '%s'", self._entry.unique_id) self._setup_api_requests() - self.dsm.update(self._with_information) + await self.dsm.update(self._with_information) diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index ba332ca7e7d..90bf43aa611 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -35,6 +35,7 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import DiscoveryInfoType @@ -172,15 +173,12 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): else: port = DEFAULT_PORT - api = SynologyDSM( - host, port, username, password, use_ssl, verify_ssl, timeout=30 - ) + session = async_get_clientsession(self.hass, verify_ssl) + api = SynologyDSM(session, host, port, username, password, use_ssl, timeout=30) errors = {} try: - serial = await self.hass.async_add_executor_job( - _login_and_fetch_syno_info, api, otp_code - ) + serial = await _login_and_fetch_syno_info(api, otp_code) except SynologyDSMLogin2SARequiredException: return await self.async_step_2sa(user_input) except SynologyDSMLogin2SAFailedException: @@ -386,13 +384,13 @@ class SynologyDSMOptionsFlowHandler(OptionsFlow): return self.async_show_form(step_id="init", data_schema=data_schema) -def _login_and_fetch_syno_info(api: SynologyDSM, otp_code: str | None) -> str: +async def _login_and_fetch_syno_info(api: SynologyDSM, otp_code: str | None) -> str: """Login to the NAS and fetch basic data.""" # These do i/o - api.login(otp_code) - api.utilisation.update() - api.storage.update() - api.network.update() + await api.login(otp_code) + await api.utilisation.update() + await api.storage.update() + await api.network.update() if ( not api.information.serial diff --git a/homeassistant/components/synology_dsm/coordinator.py b/homeassistant/components/synology_dsm/coordinator.py index 9090b945d44..3b25d93154a 100644 --- a/homeassistant/components/synology_dsm/coordinator.py +++ b/homeassistant/components/synology_dsm/coordinator.py @@ -5,7 +5,6 @@ from datetime import timedelta import logging from typing import Any, TypeVar -import async_timeout from synology_dsm.api.surveillance_station.camera import SynoCamera from synology_dsm.exceptions import SynologyDSMAPIErrorException @@ -64,20 +63,14 @@ class SynologyDSMSwitchUpdateCoordinator( async def async_setup(self) -> None: """Set up the coordinator initial data.""" - info = await self.hass.async_add_executor_job( - self.api.dsm.surveillance_station.get_info - ) + info = await self.api.dsm.surveillance_station.get_info() self.version = info["data"]["CMSMinVersion"] async def _async_update_data(self) -> dict[str, dict[str, Any]]: """Fetch all data from api.""" surveillance_station = self.api.surveillance_station return { - "switches": { - "home_mode": await self.hass.async_add_executor_job( - surveillance_station.get_home_mode_status - ) - } + "switches": {"home_mode": await surveillance_station.get_home_mode_status()} } @@ -131,8 +124,7 @@ class SynologyDSMCameraUpdateCoordinator( } try: - async with async_timeout.timeout(30): - await self.hass.async_add_executor_job(surveillance_station.update) + await surveillance_station.update() except SynologyDSMAPIErrorException as err: raise UpdateFailed(f"Error communicating with API: {err}") from err diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index 7dcf7cf702a..60add26674d 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["py-synologydsm-api==1.0.8"], + "requirements": ["py-synologydsm-api==2.0.1"], "codeowners": ["@hacf-fr", "@Quentame", "@mib1185"], "config_flow": true, "ssdp": [ diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py index 245b8804488..e44c578f4d2 100644 --- a/homeassistant/components/synology_dsm/switch.py +++ b/homeassistant/components/synology_dsm/switch.py @@ -87,9 +87,7 @@ class SynoDSMSurveillanceHomeModeToggle( "SynoDSMSurveillanceHomeModeToggle.turn_on(%s)", self._api.information.serial, ) - await self.hass.async_add_executor_job( - self._api.dsm.surveillance_station.set_home_mode, True - ) + await self._api.dsm.surveillance_station.set_home_mode(True) await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs: Any) -> None: @@ -98,9 +96,7 @@ class SynoDSMSurveillanceHomeModeToggle( "SynoDSMSurveillanceHomeModeToggle.turn_off(%s)", self._api.information.serial, ) - await self.hass.async_add_executor_job( - self._api.dsm.surveillance_station.set_home_mode, False - ) + await self._api.dsm.surveillance_station.set_home_mode(False) await self.coordinator.async_request_refresh() @property diff --git a/requirements_all.txt b/requirements_all.txt index eba1fad24d0..ddc84a9ca7a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1433,7 +1433,7 @@ py-schluter==0.1.7 py-sucks==0.9.8 # homeassistant.components.synology_dsm -py-synologydsm-api==1.0.8 +py-synologydsm-api==2.0.1 # homeassistant.components.zabbix py-zabbix==1.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f99f272afee..8a00296fe9f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1042,7 +1042,7 @@ py-melissa-climate==2.1.4 py-nightscout==1.2.2 # homeassistant.components.synology_dsm -py-synologydsm-api==1.0.8 +py-synologydsm-api==2.0.1 # homeassistant.components.seventeentrack py17track==2021.12.2 diff --git a/tests/components/synology_dsm/conftest.py b/tests/components/synology_dsm/conftest.py index 74de072e229..315ae7a5c2a 100644 --- a/tests/components/synology_dsm/conftest.py +++ b/tests/components/synology_dsm/conftest.py @@ -1,5 +1,5 @@ """Configure Synology DSM tests.""" -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest @@ -21,3 +21,17 @@ def bypass_setup_fixture(request): "homeassistant.components.synology_dsm.async_setup_entry", return_value=True ): yield + + +@pytest.fixture(name="mock_dsm") +def fixture_dsm(): + """Set up SynologyDSM API fixture.""" + with patch("homeassistant.components.synology_dsm.common.SynologyDSM") as dsm: + dsm.login = AsyncMock(return_value=True) + dsm.update = AsyncMock(return_value=True) + + dsm.network.update = AsyncMock(return_value=True) + dsm.surveillance_station.update = AsyncMock(return_value=True) + dsm.upgrade.update = AsyncMock(return_value=True) + + yield dsm diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 25259ac7ee9..ab70b3f548c 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -1,5 +1,5 @@ """Tests for the Synology DSM config flow.""" -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import AsyncMock, MagicMock, Mock, patch import pytest from synology_dsm.exceptions import ( @@ -59,60 +59,89 @@ from tests.common import MockConfigEntry @pytest.fixture(name="service") def mock_controller_service(): """Mock a successful service.""" - with patch( - "homeassistant.components.synology_dsm.config_flow.SynologyDSM" - ) as service_mock: - service_mock.return_value.information.serial = SERIAL - service_mock.return_value.utilisation.cpu_user_load = 1 - service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] - service_mock.return_value.storage.volumes_ids = ["volume_1"] - service_mock.return_value.network.macs = MACS - yield service_mock + with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm: + + dsm.login = AsyncMock(return_value=True) + dsm.update = AsyncMock(return_value=True) + + dsm.surveillance_station.update = AsyncMock(return_value=True) + dsm.upgrade.update = AsyncMock(return_value=True) + dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) + dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.storage = Mock( + disks_ids=["sda", "sdb", "sdc"], + volumes_ids=["volume_1"], + update=AsyncMock(return_value=True), + ) + dsm.information = Mock(serial=SERIAL) + + yield dsm @pytest.fixture(name="service_2sa") def mock_controller_service_2sa(): """Mock a successful service with 2SA login.""" - with patch( - "homeassistant.components.synology_dsm.config_flow.SynologyDSM" - ) as service_mock: - service_mock.return_value.login = Mock( + with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm: + dsm.login = AsyncMock( side_effect=SynologyDSMLogin2SARequiredException(USERNAME) ) - service_mock.return_value.information.serial = SERIAL - service_mock.return_value.utilisation.cpu_user_load = 1 - service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] - service_mock.return_value.storage.volumes_ids = ["volume_1"] - service_mock.return_value.network.macs = MACS - yield service_mock + dsm.update = AsyncMock(return_value=True) + + dsm.surveillance_station.update = AsyncMock(return_value=True) + dsm.upgrade.update = AsyncMock(return_value=True) + dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) + dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.storage = Mock( + disks_ids=["sda", "sdb", "sdc"], + volumes_ids=["volume_1"], + update=AsyncMock(return_value=True), + ) + dsm.information = Mock(serial=SERIAL) + yield dsm @pytest.fixture(name="service_vdsm") def mock_controller_service_vdsm(): """Mock a successful service.""" - with patch( - "homeassistant.components.synology_dsm.config_flow.SynologyDSM" - ) as service_mock: - service_mock.return_value.information.serial = SERIAL - service_mock.return_value.utilisation.cpu_user_load = 1 - service_mock.return_value.storage.disks_ids = [] - service_mock.return_value.storage.volumes_ids = ["volume_1"] - service_mock.return_value.network.macs = MACS - yield service_mock + with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm: + + dsm.login = AsyncMock(return_value=True) + dsm.update = AsyncMock(return_value=True) + + dsm.surveillance_station.update = AsyncMock(return_value=True) + dsm.upgrade.update = AsyncMock(return_value=True) + dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True)) + dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS) + dsm.storage = Mock( + disks_ids=[], + volumes_ids=["volume_1"], + update=AsyncMock(return_value=True), + ) + dsm.information = Mock(serial=SERIAL) + + yield dsm @pytest.fixture(name="service_failed") def mock_controller_service_failed(): """Mock a failed service.""" - with patch( - "homeassistant.components.synology_dsm.config_flow.SynologyDSM" - ) as service_mock: - service_mock.return_value.information.serial = None - service_mock.return_value.utilisation.cpu_user_load = None - service_mock.return_value.storage.disks_ids = [] - service_mock.return_value.storage.volumes_ids = [] - service_mock.return_value.network.macs = [] - yield service_mock + with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm: + + dsm.login = AsyncMock(return_value=True) + dsm.update = AsyncMock(return_value=True) + + dsm.surveillance_station.update = AsyncMock(return_value=True) + dsm.upgrade.update = AsyncMock(return_value=True) + dsm.utilisation = Mock(cpu_user_load=None, update=AsyncMock(return_value=True)) + dsm.network = Mock(update=AsyncMock(return_value=True), macs=[]) + dsm.storage = Mock( + disks_ids=[], + volumes_ids=[], + update=AsyncMock(return_value=True), + ) + dsm.information = Mock(serial=None) + + yield dsm async def test_user(hass: HomeAssistant, service: MagicMock): @@ -123,19 +152,23 @@ async def test_user(hass: HomeAssistant, service: MagicMock): assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - # test with all provided - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={ - CONF_HOST: HOST, - CONF_PORT: PORT, - CONF_SSL: USE_SSL, - CONF_VERIFY_SSL: VERIFY_SSL, - CONF_USERNAME: USERNAME, - CONF_PASSWORD: PASSWORD, - }, - ) + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service, + ): + # test with all provided + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={ + CONF_HOST: HOST, + CONF_PORT: PORT, + CONF_SSL: USE_SSL, + CONF_VERIFY_SSL: VERIFY_SSL, + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["result"].unique_id == SERIAL assert result["title"] == HOST @@ -150,19 +183,23 @@ async def test_user(hass: HomeAssistant, service: MagicMock): assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None - service.return_value.information.serial = SERIAL_2 - # test without port + False SSL - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={ - CONF_HOST: HOST, - CONF_SSL: False, - CONF_VERIFY_SSL: VERIFY_SSL, - CONF_USERNAME: USERNAME, - CONF_PASSWORD: PASSWORD, - }, - ) + service.information.serial = SERIAL_2 + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service, + ): + # test without port + False SSL + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={ + CONF_HOST: HOST, + CONF_SSL: False, + CONF_VERIFY_SSL: VERIFY_SSL, + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["result"].unique_id == SERIAL_2 assert result["title"] == HOST @@ -180,11 +217,15 @@ async def test_user(hass: HomeAssistant, service: MagicMock): async def test_user_2sa(hass: HomeAssistant, service_2sa: MagicMock): """Test user with 2sa authentication config.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service_2sa, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "2sa" @@ -200,11 +241,16 @@ async def test_user_2sa(hass: HomeAssistant, service_2sa: MagicMock): assert result["errors"] == {CONF_OTP_CODE: "otp_failed"} # Successful login with 2SA code - service_2sa.return_value.login = Mock(return_value=True) - service_2sa.return_value.device_token = DEVICE_TOKEN - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_OTP_CODE: "123456"} - ) + service_2sa.login = AsyncMock(return_value=True) + service_2sa.device_token = DEVICE_TOKEN + + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service_2sa, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_OTP_CODE: "123456"} + ) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["result"].unique_id == SERIAL @@ -223,25 +269,33 @@ async def test_user_2sa(hass: HomeAssistant, service_2sa: MagicMock): async def test_user_vdsm(hass: HomeAssistant, service_vdsm: MagicMock): """Test user config.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=None - ) + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service_vdsm, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=None + ) assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" - # test with all provided - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={ - CONF_HOST: HOST, - CONF_PORT: PORT, - CONF_SSL: USE_SSL, - CONF_VERIFY_SSL: VERIFY_SSL, - CONF_USERNAME: USERNAME, - CONF_PASSWORD: PASSWORD, - }, - ) + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service_vdsm, + ): + # test with all provided + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={ + CONF_HOST: HOST, + CONF_PORT: PORT, + CONF_SSL: USE_SSL, + CONF_VERIFY_SSL: VERIFY_SSL, + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + }, + ) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["result"].unique_id == SERIAL assert result["title"] == HOST @@ -289,9 +343,13 @@ async def test_reauth(hass: HomeAssistant, service: MagicMock): CONF_PASSWORD: PASSWORD, }, ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "reauth_confirm" + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "reauth_confirm" + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service, + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], { @@ -299,8 +357,8 @@ async def test_reauth(hass: HomeAssistant, service: MagicMock): CONF_PASSWORD: PASSWORD, }, ) - assert result["type"] == data_entry_flow.FlowResultType.ABORT - assert result["reason"] == "reauth_successful" + assert result["type"] == data_entry_flow.FlowResultType.ABORT + assert result["reason"] == "reauth_successful" async def test_reconfig_user(hass: HomeAssistant, service: MagicMock): @@ -318,6 +376,9 @@ async def test_reconfig_user(hass: HomeAssistant, service: MagicMock): with patch( "homeassistant.config_entries.ConfigEntries.async_reload", return_value=True, + ), patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -375,11 +436,15 @@ async def test_unknown_failed(hass: HomeAssistant, service: MagicMock): async def test_missing_data_after_login(hass: HomeAssistant, service_failed: MagicMock): """Test when we have errors during connection.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service_failed, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["errors"] == {"base": "missing_data"} @@ -404,9 +469,13 @@ async def test_form_ssdp(hass: HomeAssistant, service: MagicMock): assert result["step_id"] == "link" assert result["errors"] == {} - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} - ) + with patch( + "homeassistant.components.synology_dsm.config_flow.SynologyDSM", + return_value=service, + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} + ) assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["result"].unique_id == SERIAL diff --git a/tests/components/synology_dsm/test_init.py b/tests/components/synology_dsm/test_init.py index da2916b8c81..fe383bbfeab 100644 --- a/tests/components/synology_dsm/test_init.py +++ b/tests/components/synology_dsm/test_init.py @@ -1,5 +1,5 @@ """Tests for the Synology DSM component.""" -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest from synology_dsm.exceptions import SynologyDSMLoginInvalidException @@ -22,11 +22,12 @@ from tests.common import MockConfigEntry @pytest.mark.no_bypass_setup -async def test_services_registered(hass: HomeAssistant): +async def test_services_registered(hass: HomeAssistant, mock_dsm: MagicMock): """Test if all services are registered.""" - with patch("homeassistant.components.synology_dsm.common.SynologyDSM"), patch( - "homeassistant.components.synology_dsm.PLATFORMS", return_value=[] - ): + with patch( + "homeassistant.components.synology_dsm.common.SynologyDSM", + return_value=mock_dsm, + ), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]): entry = MockConfigEntry( domain=DOMAIN, data={