Make Synology DSM integration fully async (#85904)

This commit is contained in:
Michael 2023-01-16 00:19:08 +01:00 committed by GitHub
parent 65ca62c991
commit a7ebec4d02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 235 additions and 169 deletions

View File

@ -84,12 +84,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# For SSDP compat # For SSDP compat
if not entry.data.get(CONF_MAC): if not entry.data.get(CONF_MAC):
network = await hass.async_add_executor_job(getattr, api.dsm, "network")
hass.config_entries.async_update_entry( 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) coordinator_central = SynologyDSMCentralUpdateCoordinator(hass, entry, api)
await coordinator_central.async_config_entry_first_refresh() await coordinator_central.async_config_entry_first_refresh()

View File

@ -1,7 +1,7 @@
"""Support for Synology DSM buttons.""" """Support for Synology DSM buttons."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
from typing import Any, Final from typing import Any, Final
@ -27,7 +27,7 @@ LOGGER = logging.getLogger(__name__)
class SynologyDSMbuttonDescriptionMixin: class SynologyDSMbuttonDescriptionMixin:
"""Mixin to describe a Synology DSM button entity.""" """Mixin to describe a Synology DSM button entity."""
press_action: Callable[[SynoApi], Any] press_action: Callable[[SynoApi], Callable[[], Coroutine[Any, Any, None]]]
@dataclass @dataclass
@ -43,14 +43,14 @@ BUTTONS: Final = [
name="Reboot", name="Reboot",
device_class=ButtonDeviceClass.RESTART, device_class=ButtonDeviceClass.RESTART,
entity_category=EntityCategory.CONFIG, entity_category=EntityCategory.CONFIG,
press_action=lambda syno_api: syno_api.async_reboot(), press_action=lambda syno_api: syno_api.async_reboot,
), ),
SynologyDSMbuttonDescription( SynologyDSMbuttonDescription(
key="shutdown", key="shutdown",
name="Shutdown", name="Shutdown",
icon="mdi:power", icon="mdi:power",
entity_category=EntityCategory.CONFIG, 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.entity_description.key,
self.syno_api.network.hostname, self.syno_api.network.hostname,
) )
await self.entity_description.press_action(self.syno_api) await self.entity_description.press_action(self.syno_api)()

View File

@ -143,7 +143,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
self._listen_source_updates() self._listen_source_updates()
await super().async_added_to_hass() await super().async_added_to_hass()
def camera_image( async def async_camera_image(
self, width: int | None = None, height: int | None = None self, width: int | None = None, height: int | None = None
) -> bytes | None: ) -> bytes | None:
"""Return bytes of camera image.""" """Return bytes of camera image."""
@ -154,7 +154,7 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
if not self.available: if not self.available:
return None return None
try: 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 ( except (
SynologyDSMAPIErrorException, SynologyDSMAPIErrorException,
SynologyDSMRequestException, SynologyDSMRequestException,
@ -178,22 +178,22 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
return self.camera_data.live_view.rtsp # type: ignore[no-any-return] 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.""" """Enable motion detection in the camera."""
_LOGGER.debug( _LOGGER.debug(
"SynoDSMCamera.enable_motion_detection(%s)", "SynoDSMCamera.enable_motion_detection(%s)",
self.camera_data.name, self.camera_data.name,
) )
self._api.surveillance_station.enable_motion_detection( await self._api.surveillance_station.enable_motion_detection(
self.entity_description.key self.entity_description.key
) )
def disable_motion_detection(self) -> None: async def async_disable_motion_detection(self) -> None:
"""Disable motion detection in camera.""" """Disable motion detection in camera."""
_LOGGER.debug( _LOGGER.debug(
"SynoDSMCamera.disable_motion_detection(%s)", "SynoDSMCamera.disable_motion_detection(%s)",
self.camera_data.name, self.camera_data.name,
) )
self._api.surveillance_station.disable_motion_detection( await self._api.surveillance_station.disable_motion_detection(
self.entity_description.key self.entity_description.key
) )

View File

@ -30,6 +30,7 @@ from homeassistant.const import (
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_DEVICE_TOKEN, SYNOLOGY_CONNECTION_EXCEPTIONS from .const import CONF_DEVICE_TOKEN, SYNOLOGY_CONNECTION_EXCEPTIONS
@ -71,21 +72,18 @@ class SynoApi:
async def async_setup(self) -> None: async def async_setup(self) -> None:
"""Start interacting with the NAS.""" """Start interacting with the NAS."""
await self._hass.async_add_executor_job(self._setup) session = async_get_clientsession(self._hass, self._entry.data[CONF_VERIFY_SSL])
def _setup(self) -> None:
"""Start interacting with the NAS in the executor."""
self.dsm = SynologyDSM( self.dsm = SynologyDSM(
session,
self._entry.data[CONF_HOST], self._entry.data[CONF_HOST],
self._entry.data[CONF_PORT], self._entry.data[CONF_PORT],
self._entry.data[CONF_USERNAME], self._entry.data[CONF_USERNAME],
self._entry.data[CONF_PASSWORD], self._entry.data[CONF_PASSWORD],
self._entry.data[CONF_SSL], self._entry.data[CONF_SSL],
self._entry.data[CONF_VERIFY_SSL],
timeout=self._entry.options.get(CONF_TIMEOUT), timeout=self._entry.options.get(CONF_TIMEOUT),
device_token=self._entry.data.get(CONF_DEVICE_TOKEN), device_token=self._entry.data.get(CONF_DEVICE_TOKEN),
) )
self.dsm.login() await self.dsm.login()
# check if surveillance station is used # check if surveillance station is used
self._with_surveillance_station = bool( self._with_surveillance_station = bool(
@ -93,7 +91,7 @@ class SynoApi:
) )
if self._with_surveillance_station: if self._with_surveillance_station:
try: try:
self.dsm.surveillance_station.update() await self.dsm.surveillance_station.update()
except SYNOLOGY_CONNECTION_EXCEPTIONS: except SYNOLOGY_CONNECTION_EXCEPTIONS:
self._with_surveillance_station = False self._with_surveillance_station = False
self.dsm.reset(SynoSurveillanceStation.API_KEY) self.dsm.reset(SynoSurveillanceStation.API_KEY)
@ -110,16 +108,16 @@ class SynoApi:
# check if upgrade is available # check if upgrade is available
try: try:
self.dsm.upgrade.update() await self.dsm.upgrade.update()
except SYNOLOGY_CONNECTION_EXCEPTIONS as ex: except SYNOLOGY_CONNECTION_EXCEPTIONS as ex:
self._with_upgrade = False self._with_upgrade = False
self.dsm.reset(SynoCoreUpgrade.API_KEY) self.dsm.reset(SynoCoreUpgrade.API_KEY)
LOGGER.debug("Disabled fetching upgrade data during setup: %s", ex) LOGGER.debug("Disabled fetching upgrade data during setup: %s", ex)
self._fetch_device_configuration() await self._fetch_device_configuration()
try: try:
self._update() await self._update()
except SYNOLOGY_CONNECTION_EXCEPTIONS as err: except SYNOLOGY_CONNECTION_EXCEPTIONS as err:
LOGGER.debug( LOGGER.debug(
"Connection error during setup of '%s' with exception: %s", "Connection error during setup of '%s' with exception: %s",
@ -210,11 +208,11 @@ class SynoApi:
self.dsm.reset(self.utilisation) self.dsm.reset(self.utilisation)
self.utilisation = None self.utilisation = None
def _fetch_device_configuration(self) -> None: async def _fetch_device_configuration(self) -> None:
"""Fetch initial device config.""" """Fetch initial device config."""
self.information = self.dsm.information self.information = self.dsm.information
self.network = self.dsm.network self.network = self.dsm.network
self.network.update() await self.network.update()
if self._with_security: if self._with_security:
LOGGER.debug("Enable security api updates for '%s'", self._entry.unique_id) 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: async def _syno_api_executer(self, api_call: Callable) -> None:
"""Synology api call wrapper.""" """Synology api call wrapper."""
try: try:
await self._hass.async_add_executor_job(api_call) await api_call()
except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err: except (SynologyDSMAPIErrorException, SynologyDSMRequestException) as err:
LOGGER.debug( LOGGER.debug(
"Error from '%s': %s", self._entry.unique_id, err, exc_info=True "Error from '%s': %s", self._entry.unique_id, err, exc_info=True
@ -274,7 +272,7 @@ class SynoApi:
async def async_update(self) -> None: async def async_update(self) -> None:
"""Update function for updating API information.""" """Update function for updating API information."""
try: try:
await self._hass.async_add_executor_job(self._update) await self._update()
except SYNOLOGY_CONNECTION_EXCEPTIONS as err: except SYNOLOGY_CONNECTION_EXCEPTIONS as err:
LOGGER.debug( LOGGER.debug(
"Connection error during update of '%s' with exception: %s", "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) 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.""" """Update function for updating API information."""
LOGGER.debug("Start data update for '%s'", self._entry.unique_id) LOGGER.debug("Start data update for '%s'", self._entry.unique_id)
self._setup_api_requests() self._setup_api_requests()
self.dsm.update(self._with_information) await self.dsm.update(self._with_information)

View File

@ -35,6 +35,7 @@ from homeassistant.const import (
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.helpers.typing import DiscoveryInfoType
@ -172,15 +173,12 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN):
else: else:
port = DEFAULT_PORT port = DEFAULT_PORT
api = SynologyDSM( session = async_get_clientsession(self.hass, verify_ssl)
host, port, username, password, use_ssl, verify_ssl, timeout=30 api = SynologyDSM(session, host, port, username, password, use_ssl, timeout=30)
)
errors = {} errors = {}
try: try:
serial = await self.hass.async_add_executor_job( serial = await _login_and_fetch_syno_info(api, otp_code)
_login_and_fetch_syno_info, api, otp_code
)
except SynologyDSMLogin2SARequiredException: except SynologyDSMLogin2SARequiredException:
return await self.async_step_2sa(user_input) return await self.async_step_2sa(user_input)
except SynologyDSMLogin2SAFailedException: except SynologyDSMLogin2SAFailedException:
@ -386,13 +384,13 @@ class SynologyDSMOptionsFlowHandler(OptionsFlow):
return self.async_show_form(step_id="init", data_schema=data_schema) 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.""" """Login to the NAS and fetch basic data."""
# These do i/o # These do i/o
api.login(otp_code) await api.login(otp_code)
api.utilisation.update() await api.utilisation.update()
api.storage.update() await api.storage.update()
api.network.update() await api.network.update()
if ( if (
not api.information.serial not api.information.serial

View File

@ -5,7 +5,6 @@ from datetime import timedelta
import logging import logging
from typing import Any, TypeVar from typing import Any, TypeVar
import async_timeout
from synology_dsm.api.surveillance_station.camera import SynoCamera from synology_dsm.api.surveillance_station.camera import SynoCamera
from synology_dsm.exceptions import SynologyDSMAPIErrorException from synology_dsm.exceptions import SynologyDSMAPIErrorException
@ -64,20 +63,14 @@ class SynologyDSMSwitchUpdateCoordinator(
async def async_setup(self) -> None: async def async_setup(self) -> None:
"""Set up the coordinator initial data.""" """Set up the coordinator initial data."""
info = await self.hass.async_add_executor_job( info = await self.api.dsm.surveillance_station.get_info()
self.api.dsm.surveillance_station.get_info
)
self.version = info["data"]["CMSMinVersion"] self.version = info["data"]["CMSMinVersion"]
async def _async_update_data(self) -> dict[str, dict[str, Any]]: async def _async_update_data(self) -> dict[str, dict[str, Any]]:
"""Fetch all data from api.""" """Fetch all data from api."""
surveillance_station = self.api.surveillance_station surveillance_station = self.api.surveillance_station
return { return {
"switches": { "switches": {"home_mode": await surveillance_station.get_home_mode_status()}
"home_mode": await self.hass.async_add_executor_job(
surveillance_station.get_home_mode_status
)
}
} }
@ -131,8 +124,7 @@ class SynologyDSMCameraUpdateCoordinator(
} }
try: try:
async with async_timeout.timeout(30): await surveillance_station.update()
await self.hass.async_add_executor_job(surveillance_station.update)
except SynologyDSMAPIErrorException as err: except SynologyDSMAPIErrorException as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err raise UpdateFailed(f"Error communicating with API: {err}") from err

View File

@ -2,7 +2,7 @@
"domain": "synology_dsm", "domain": "synology_dsm",
"name": "Synology DSM", "name": "Synology DSM",
"documentation": "https://www.home-assistant.io/integrations/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"], "codeowners": ["@hacf-fr", "@Quentame", "@mib1185"],
"config_flow": true, "config_flow": true,
"ssdp": [ "ssdp": [

View File

@ -87,9 +87,7 @@ class SynoDSMSurveillanceHomeModeToggle(
"SynoDSMSurveillanceHomeModeToggle.turn_on(%s)", "SynoDSMSurveillanceHomeModeToggle.turn_on(%s)",
self._api.information.serial, self._api.information.serial,
) )
await self.hass.async_add_executor_job( await self._api.dsm.surveillance_station.set_home_mode(True)
self._api.dsm.surveillance_station.set_home_mode, True
)
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
@ -98,9 +96,7 @@ class SynoDSMSurveillanceHomeModeToggle(
"SynoDSMSurveillanceHomeModeToggle.turn_off(%s)", "SynoDSMSurveillanceHomeModeToggle.turn_off(%s)",
self._api.information.serial, self._api.information.serial,
) )
await self.hass.async_add_executor_job( await self._api.dsm.surveillance_station.set_home_mode(False)
self._api.dsm.surveillance_station.set_home_mode, False
)
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
@property @property

View File

@ -1433,7 +1433,7 @@ py-schluter==0.1.7
py-sucks==0.9.8 py-sucks==0.9.8
# homeassistant.components.synology_dsm # homeassistant.components.synology_dsm
py-synologydsm-api==1.0.8 py-synologydsm-api==2.0.1
# homeassistant.components.zabbix # homeassistant.components.zabbix
py-zabbix==1.1.7 py-zabbix==1.1.7

View File

@ -1042,7 +1042,7 @@ py-melissa-climate==2.1.4
py-nightscout==1.2.2 py-nightscout==1.2.2
# homeassistant.components.synology_dsm # homeassistant.components.synology_dsm
py-synologydsm-api==1.0.8 py-synologydsm-api==2.0.1
# homeassistant.components.seventeentrack # homeassistant.components.seventeentrack
py17track==2021.12.2 py17track==2021.12.2

View File

@ -1,5 +1,5 @@
"""Configure Synology DSM tests.""" """Configure Synology DSM tests."""
from unittest.mock import patch from unittest.mock import AsyncMock, patch
import pytest import pytest
@ -21,3 +21,17 @@ def bypass_setup_fixture(request):
"homeassistant.components.synology_dsm.async_setup_entry", return_value=True "homeassistant.components.synology_dsm.async_setup_entry", return_value=True
): ):
yield 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

View File

@ -1,5 +1,5 @@
"""Tests for the Synology DSM config flow.""" """Tests for the Synology DSM config flow."""
from unittest.mock import MagicMock, Mock, patch from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest import pytest
from synology_dsm.exceptions import ( from synology_dsm.exceptions import (
@ -59,60 +59,89 @@ from tests.common import MockConfigEntry
@pytest.fixture(name="service") @pytest.fixture(name="service")
def mock_controller_service(): def mock_controller_service():
"""Mock a successful service.""" """Mock a successful service."""
with patch( with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm:
"homeassistant.components.synology_dsm.config_flow.SynologyDSM"
) as service_mock: dsm.login = AsyncMock(return_value=True)
service_mock.return_value.information.serial = SERIAL dsm.update = AsyncMock(return_value=True)
service_mock.return_value.utilisation.cpu_user_load = 1
service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] dsm.surveillance_station.update = AsyncMock(return_value=True)
service_mock.return_value.storage.volumes_ids = ["volume_1"] dsm.upgrade.update = AsyncMock(return_value=True)
service_mock.return_value.network.macs = MACS dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
yield service_mock 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") @pytest.fixture(name="service_2sa")
def mock_controller_service_2sa(): def mock_controller_service_2sa():
"""Mock a successful service with 2SA login.""" """Mock a successful service with 2SA login."""
with patch( with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm:
"homeassistant.components.synology_dsm.config_flow.SynologyDSM" dsm.login = AsyncMock(
) as service_mock:
service_mock.return_value.login = Mock(
side_effect=SynologyDSMLogin2SARequiredException(USERNAME) side_effect=SynologyDSMLogin2SARequiredException(USERNAME)
) )
service_mock.return_value.information.serial = SERIAL dsm.update = AsyncMock(return_value=True)
service_mock.return_value.utilisation.cpu_user_load = 1
service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] dsm.surveillance_station.update = AsyncMock(return_value=True)
service_mock.return_value.storage.volumes_ids = ["volume_1"] dsm.upgrade.update = AsyncMock(return_value=True)
service_mock.return_value.network.macs = MACS dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
yield service_mock 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") @pytest.fixture(name="service_vdsm")
def mock_controller_service_vdsm(): def mock_controller_service_vdsm():
"""Mock a successful service.""" """Mock a successful service."""
with patch( with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm:
"homeassistant.components.synology_dsm.config_flow.SynologyDSM"
) as service_mock: dsm.login = AsyncMock(return_value=True)
service_mock.return_value.information.serial = SERIAL dsm.update = AsyncMock(return_value=True)
service_mock.return_value.utilisation.cpu_user_load = 1
service_mock.return_value.storage.disks_ids = [] dsm.surveillance_station.update = AsyncMock(return_value=True)
service_mock.return_value.storage.volumes_ids = ["volume_1"] dsm.upgrade.update = AsyncMock(return_value=True)
service_mock.return_value.network.macs = MACS dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
yield service_mock 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") @pytest.fixture(name="service_failed")
def mock_controller_service_failed(): def mock_controller_service_failed():
"""Mock a failed service.""" """Mock a failed service."""
with patch( with patch("homeassistant.components.synology_dsm.config_flow.SynologyDSM") as dsm:
"homeassistant.components.synology_dsm.config_flow.SynologyDSM"
) as service_mock: dsm.login = AsyncMock(return_value=True)
service_mock.return_value.information.serial = None dsm.update = AsyncMock(return_value=True)
service_mock.return_value.utilisation.cpu_user_load = None
service_mock.return_value.storage.disks_ids = [] dsm.surveillance_station.update = AsyncMock(return_value=True)
service_mock.return_value.storage.volumes_ids = [] dsm.upgrade.update = AsyncMock(return_value=True)
service_mock.return_value.network.macs = [] dsm.utilisation = Mock(cpu_user_load=None, update=AsyncMock(return_value=True))
yield service_mock 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): async def test_user(hass: HomeAssistant, service: MagicMock):
@ -123,6 +152,10 @@ async def test_user(hass: HomeAssistant, service: MagicMock):
assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service,
):
# test with all provided # test with all provided
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -150,7 +183,11 @@ async def test_user(hass: HomeAssistant, service: MagicMock):
assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_DISKS) is None
assert result["data"].get(CONF_VOLUMES) is None assert result["data"].get(CONF_VOLUMES) is None
service.return_value.information.serial = SERIAL_2 service.information.serial = SERIAL_2
with patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service,
):
# test without port + False SSL # test without port + False SSL
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -180,6 +217,10 @@ async def test_user(hass: HomeAssistant, service: MagicMock):
async def test_user_2sa(hass: HomeAssistant, service_2sa: MagicMock): async def test_user_2sa(hass: HomeAssistant, service_2sa: MagicMock):
"""Test user with 2sa authentication config.""" """Test user with 2sa authentication config."""
with patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service_2sa,
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_USER}, context={"source": SOURCE_USER},
@ -200,8 +241,13 @@ async def test_user_2sa(hass: HomeAssistant, service_2sa: MagicMock):
assert result["errors"] == {CONF_OTP_CODE: "otp_failed"} assert result["errors"] == {CONF_OTP_CODE: "otp_failed"}
# Successful login with 2SA code # Successful login with 2SA code
service_2sa.return_value.login = Mock(return_value=True) service_2sa.login = AsyncMock(return_value=True)
service_2sa.return_value.device_token = DEVICE_TOKEN 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 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_OTP_CODE: "123456"} result["flow_id"], {CONF_OTP_CODE: "123456"}
) )
@ -223,12 +269,20 @@ async def test_user_2sa(hass: HomeAssistant, service_2sa: MagicMock):
async def test_user_vdsm(hass: HomeAssistant, service_vdsm: MagicMock): async def test_user_vdsm(hass: HomeAssistant, service_vdsm: MagicMock):
"""Test user config.""" """Test user config."""
with patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service_vdsm,
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=None DOMAIN, context={"source": SOURCE_USER}, data=None
) )
assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
with patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service_vdsm,
):
# test with all provided # test with all provided
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -292,6 +346,10 @@ async def test_reauth(hass: HomeAssistant, service: MagicMock):
assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "reauth_confirm" 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 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
@ -318,6 +376,9 @@ async def test_reconfig_user(hass: HomeAssistant, service: MagicMock):
with patch( with patch(
"homeassistant.config_entries.ConfigEntries.async_reload", "homeassistant.config_entries.ConfigEntries.async_reload",
return_value=True, return_value=True,
), patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -375,6 +436,10 @@ async def test_unknown_failed(hass: HomeAssistant, service: MagicMock):
async def test_missing_data_after_login(hass: HomeAssistant, service_failed: MagicMock): async def test_missing_data_after_login(hass: HomeAssistant, service_failed: MagicMock):
"""Test when we have errors during connection.""" """Test when we have errors during connection."""
with patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service_failed,
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_USER}, context={"source": SOURCE_USER},
@ -404,6 +469,10 @@ async def test_form_ssdp(hass: HomeAssistant, service: MagicMock):
assert result["step_id"] == "link" assert result["step_id"] == "link"
assert result["errors"] == {} assert result["errors"] == {}
with patch(
"homeassistant.components.synology_dsm.config_flow.SynologyDSM",
return_value=service,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD} result["flow_id"], {CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}
) )

View File

@ -1,5 +1,5 @@
"""Tests for the Synology DSM component.""" """Tests for the Synology DSM component."""
from unittest.mock import patch from unittest.mock import MagicMock, patch
import pytest import pytest
from synology_dsm.exceptions import SynologyDSMLoginInvalidException from synology_dsm.exceptions import SynologyDSMLoginInvalidException
@ -22,11 +22,12 @@ from tests.common import MockConfigEntry
@pytest.mark.no_bypass_setup @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.""" """Test if all services are registered."""
with patch("homeassistant.components.synology_dsm.common.SynologyDSM"), patch( with patch(
"homeassistant.components.synology_dsm.PLATFORMS", return_value=[] "homeassistant.components.synology_dsm.common.SynologyDSM",
): return_value=mock_dsm,
), patch("homeassistant.components.synology_dsm.PLATFORMS", return_value=[]):
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data={ data={