Add QNAP QSW Update platform (#71019)

* qnap_qsw: add Update platform

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

* qnap_qsw: update: allow init if firmware coordinator fails

QSW API can return an error if update servers aren't reachable and this
prevents the integration from loading.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

* tests: qnap_qsw: achieve 100% coverage

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
This commit is contained in:
Álvaro Fernández Rojas 2022-06-30 21:09:08 +02:00 committed by GitHub
parent 0caeeb56c5
commit 768b98ae77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 286 additions and 37 deletions

View File

@ -8,10 +8,15 @@ from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import DOMAIN from .const import DOMAIN, QSW_COORD_DATA, QSW_COORD_FW
from .coordinator import QswUpdateCoordinator from .coordinator import QswDataCoordinator, QswFirmwareCoordinator
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.SENSOR,
Platform.UPDATE,
]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -24,10 +29,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
qsw = QnapQswApi(aiohttp_client.async_get_clientsession(hass), options) qsw = QnapQswApi(aiohttp_client.async_get_clientsession(hass), options)
coordinator = QswUpdateCoordinator(hass, qsw) coord_data = QswDataCoordinator(hass, qsw)
await coordinator.async_config_entry_first_refresh() await coord_data.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator coord_fw = QswFirmwareCoordinator(hass, qsw)
await coord_fw.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
QSW_COORD_DATA: coord_data,
QSW_COORD_FW: coord_fw,
}
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)

View File

@ -16,8 +16,8 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_MESSAGE, DOMAIN from .const import ATTR_MESSAGE, DOMAIN, QSW_COORD_DATA
from .coordinator import QswUpdateCoordinator from .coordinator import QswDataCoordinator
from .entity import QswEntityDescription, QswSensorEntity from .entity import QswEntityDescription, QswSensorEntity
@ -48,7 +48,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Add QNAP QSW binary sensors from a config_entry.""" """Add QNAP QSW binary sensors from a config_entry."""
coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: QswDataCoordinator = hass.data[DOMAIN][entry.entry_id][QSW_COORD_DATA]
async_add_entities( async_add_entities(
QswBinarySensor(coordinator, description, entry) QswBinarySensor(coordinator, description, entry)
for description in BINARY_SENSOR_TYPES for description in BINARY_SENSOR_TYPES
@ -66,7 +66,7 @@ class QswBinarySensor(QswSensorEntity, BinarySensorEntity):
def __init__( def __init__(
self, self,
coordinator: QswUpdateCoordinator, coordinator: QswDataCoordinator,
description: QswBinarySensorEntityDescription, description: QswBinarySensorEntityDescription,
entry: ConfigEntry, entry: ConfigEntry,
) -> None: ) -> None:

View File

@ -17,9 +17,9 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, QSW_REBOOT from .const import DOMAIN, QSW_COORD_DATA, QSW_REBOOT
from .coordinator import QswUpdateCoordinator from .coordinator import QswDataCoordinator
from .entity import QswEntity from .entity import QswDataEntity
@dataclass @dataclass
@ -49,20 +49,20 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Add QNAP QSW buttons from a config_entry.""" """Add QNAP QSW buttons from a config_entry."""
coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: QswDataCoordinator = hass.data[DOMAIN][entry.entry_id][QSW_COORD_DATA]
async_add_entities( async_add_entities(
QswButton(coordinator, description, entry) for description in BUTTON_TYPES QswButton(coordinator, description, entry) for description in BUTTON_TYPES
) )
class QswButton(QswEntity, ButtonEntity): class QswButton(QswDataEntity, ButtonEntity):
"""Define a QNAP QSW button.""" """Define a QNAP QSW button."""
entity_description: QswButtonDescription entity_description: QswButtonDescription
def __init__( def __init__(
self, self,
coordinator: QswUpdateCoordinator, coordinator: QswDataCoordinator,
description: QswButtonDescription, description: QswButtonDescription,
entry: ConfigEntry, entry: ConfigEntry,
) -> None: ) -> None:

View File

@ -10,5 +10,8 @@ MANUFACTURER: Final = "QNAP"
RPM: Final = "rpm" RPM: Final = "rpm"
QSW_COORD_DATA: Final = "coordinator-data"
QSW_COORD_FW: Final = "coordinator-firmware"
QSW_REBOOT = "reboot" QSW_REBOOT = "reboot"
QSW_TIMEOUT_SEC: Final = 25 QSW_TIMEOUT_SEC: Final = 25
QSW_UPDATE: Final = "update"

View File

@ -5,7 +5,7 @@ from datetime import timedelta
import logging import logging
from typing import Any from typing import Any
from aioqsw.exceptions import QswError from aioqsw.exceptions import APIError, QswError
from aioqsw.localapi import QnapQswApi from aioqsw.localapi import QnapQswApi
import async_timeout import async_timeout
@ -14,12 +14,13 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import DOMAIN, QSW_TIMEOUT_SEC from .const import DOMAIN, QSW_TIMEOUT_SEC
SCAN_INTERVAL = timedelta(seconds=60) DATA_SCAN_INTERVAL = timedelta(seconds=60)
FW_SCAN_INTERVAL = timedelta(hours=12)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class QswUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): class QswDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching data from the QNAP QSW device.""" """Class to manage fetching data from the QNAP QSW device."""
def __init__(self, hass: HomeAssistant, qsw: QnapQswApi) -> None: def __init__(self, hass: HomeAssistant, qsw: QnapQswApi) -> None:
@ -30,7 +31,7 @@ class QswUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
hass, hass,
_LOGGER, _LOGGER,
name=DOMAIN, name=DOMAIN,
update_interval=SCAN_INTERVAL, update_interval=DATA_SCAN_INTERVAL,
) )
async def _async_update_data(self) -> dict[str, Any]: async def _async_update_data(self) -> dict[str, Any]:
@ -41,3 +42,29 @@ class QswUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
except QswError as error: except QswError as error:
raise UpdateFailed(error) from error raise UpdateFailed(error) from error
return self.qsw.data() return self.qsw.data()
class QswFirmwareCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching firmware data from the QNAP QSW device."""
def __init__(self, hass: HomeAssistant, qsw: QnapQswApi) -> None:
"""Initialize."""
self.qsw = qsw
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=FW_SCAN_INTERVAL,
)
async def _async_update_data(self) -> dict[str, Any]:
"""Update firmware data via library."""
async with async_timeout.timeout(QSW_TIMEOUT_SEC):
try:
await self.qsw.check_firmware()
except APIError as error:
_LOGGER.warning(error)
except QswError as error:
raise UpdateFailed(error) from error
return self.qsw.data()

View File

@ -10,8 +10,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN from .const import DOMAIN, QSW_COORD_DATA, QSW_COORD_FW
from .coordinator import QswUpdateCoordinator from .coordinator import QswDataCoordinator, QswFirmwareCoordinator
TO_REDACT_CONFIG = [ TO_REDACT_CONFIG = [
CONF_USERNAME, CONF_USERNAME,
@ -29,9 +29,12 @@ async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for a config entry.""" """Return diagnostics for a config entry."""
coordinator: QswUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] entry_data = hass.data[DOMAIN][config_entry.entry_id]
coord_data: QswDataCoordinator = entry_data[QSW_COORD_DATA]
coord_fw: QswFirmwareCoordinator = entry_data[QSW_COORD_FW]
return { return {
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT_CONFIG), "config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT_CONFIG),
"coord_data": async_redact_data(coordinator.data, TO_REDACT_DATA), "coord_data": async_redact_data(coord_data.data, TO_REDACT_DATA),
"coord_fw": async_redact_data(coord_fw.data, TO_REDACT_DATA),
} }

View File

@ -20,15 +20,15 @@ from homeassistant.helpers.entity import DeviceInfo, EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import MANUFACTURER from .const import MANUFACTURER
from .coordinator import QswUpdateCoordinator from .coordinator import QswDataCoordinator, QswFirmwareCoordinator
class QswEntity(CoordinatorEntity[QswUpdateCoordinator]): class QswDataEntity(CoordinatorEntity[QswDataCoordinator]):
"""Define an QNAP QSW entity.""" """Define an QNAP QSW entity."""
def __init__( def __init__(
self, self,
coordinator: QswUpdateCoordinator, coordinator: QswDataCoordinator,
entry: ConfigEntry, entry: ConfigEntry,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
@ -72,7 +72,7 @@ class QswEntityDescription(EntityDescription, QswEntityDescriptionMixin):
attributes: dict[str, list[str]] | None = None attributes: dict[str, list[str]] | None = None
class QswSensorEntity(QswEntity): class QswSensorEntity(QswDataEntity):
"""Base class for QSW sensor entities.""" """Base class for QSW sensor entities."""
entity_description: QswEntityDescription entity_description: QswEntityDescription
@ -91,3 +91,38 @@ class QswSensorEntity(QswEntity):
key: self.get_device_value(val[0], val[1]) key: self.get_device_value(val[0], val[1])
for key, val in self.entity_description.attributes.items() for key, val in self.entity_description.attributes.items()
} }
class QswFirmwareEntity(CoordinatorEntity[QswFirmwareCoordinator]):
"""Define a QNAP QSW firmware entity."""
def __init__(
self,
coordinator: QswFirmwareCoordinator,
entry: ConfigEntry,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
configuration_url=entry.data[CONF_URL],
connections={
(
CONNECTION_NETWORK_MAC,
self.get_device_value(QSD_SYSTEM_BOARD, QSD_MAC),
)
},
manufacturer=MANUFACTURER,
model=self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT),
name=self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT),
sw_version=self.get_device_value(QSD_FIRMWARE_INFO, QSD_FIRMWARE),
)
def get_device_value(self, key: str, subkey: str) -> Any:
"""Return device value by key."""
value = None
if key in self.coordinator.data:
data = self.coordinator.data[key]
if subkey in data:
value = data[subkey]
return value

View File

@ -26,8 +26,8 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_MAX, DOMAIN, RPM from .const import ATTR_MAX, DOMAIN, QSW_COORD_DATA, RPM
from .coordinator import QswUpdateCoordinator from .coordinator import QswDataCoordinator
from .entity import QswEntityDescription, QswSensorEntity from .entity import QswEntityDescription, QswSensorEntity
@ -82,7 +82,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Add QNAP QSW sensors from a config_entry.""" """Add QNAP QSW sensors from a config_entry."""
coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: QswDataCoordinator = hass.data[DOMAIN][entry.entry_id][QSW_COORD_DATA]
async_add_entities( async_add_entities(
QswSensor(coordinator, description, entry) QswSensor(coordinator, description, entry)
for description in SENSOR_TYPES for description in SENSOR_TYPES
@ -100,7 +100,7 @@ class QswSensor(QswSensorEntity, SensorEntity):
def __init__( def __init__(
self, self,
coordinator: QswUpdateCoordinator, coordinator: QswDataCoordinator,
description: QswSensorEntityDescription, description: QswSensorEntityDescription,
entry: ConfigEntry, entry: ConfigEntry,
) -> None: ) -> None:

View File

@ -0,0 +1,89 @@
"""Support for the QNAP QSW update."""
from __future__ import annotations
from typing import Final
from aioqsw.const import (
QSD_DESCRIPTION,
QSD_FIRMWARE_CHECK,
QSD_FIRMWARE_INFO,
QSD_PRODUCT,
QSD_SYSTEM_BOARD,
QSD_VERSION,
)
from homeassistant.components.update import (
UpdateDeviceClass,
UpdateEntity,
UpdateEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, QSW_COORD_FW, QSW_UPDATE
from .coordinator import QswFirmwareCoordinator
from .entity import QswFirmwareEntity
UPDATE_TYPES: Final[tuple[UpdateEntityDescription, ...]] = (
UpdateEntityDescription(
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
key=QSW_UPDATE,
name="Firmware Update",
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Add QNAP QSW updates from a config_entry."""
coordinator: QswFirmwareCoordinator = hass.data[DOMAIN][entry.entry_id][
QSW_COORD_FW
]
async_add_entities(
QswUpdate(coordinator, description, entry) for description in UPDATE_TYPES
)
class QswUpdate(QswFirmwareEntity, UpdateEntity):
"""Define a QNAP QSW update."""
entity_description: UpdateEntityDescription
def __init__(
self,
coordinator: QswFirmwareCoordinator,
description: UpdateEntityDescription,
entry: ConfigEntry,
) -> None:
"""Initialize."""
super().__init__(coordinator, entry)
self._attr_name = (
f"{self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT)} {description.name}"
)
self._attr_unique_id = f"{entry.unique_id}_{description.key}"
self.entity_description = description
self._attr_installed_version = self.get_device_value(
QSD_FIRMWARE_INFO, QSD_VERSION
)
self._async_update_attrs()
@callback
def _handle_coordinator_update(self) -> None:
"""Update attributes when the coordinator updates."""
self._async_update_attrs()
super()._handle_coordinator_update()
@callback
def _async_update_attrs(self) -> None:
"""Update attributes."""
self._attr_latest_version = self.get_device_value(
QSD_FIRMWARE_CHECK, QSD_VERSION
)
self._attr_release_summary = self.get_device_value(
QSD_FIRMWARE_CHECK, QSD_DESCRIPTION
)

View File

@ -2,10 +2,13 @@
from unittest.mock import patch from unittest.mock import patch
from aioqsw.exceptions import QswError from aioqsw.exceptions import APIError, QswError
from homeassistant.components.qnap_qsw.const import DOMAIN from homeassistant.components.qnap_qsw.const import DOMAIN
from homeassistant.components.qnap_qsw.coordinator import SCAN_INTERVAL from homeassistant.components.qnap_qsw.coordinator import (
DATA_SCAN_INTERVAL,
FW_SCAN_INTERVAL,
)
from homeassistant.const import STATE_UNAVAILABLE from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -14,6 +17,7 @@ from .util import (
CONFIG, CONFIG,
FIRMWARE_CONDITION_MOCK, FIRMWARE_CONDITION_MOCK,
FIRMWARE_INFO_MOCK, FIRMWARE_INFO_MOCK,
FIRMWARE_UPDATE_CHECK_MOCK,
SYSTEM_BOARD_MOCK, SYSTEM_BOARD_MOCK,
SYSTEM_SENSOR_MOCK, SYSTEM_SENSOR_MOCK,
SYSTEM_TIME_MOCK, SYSTEM_TIME_MOCK,
@ -37,6 +41,9 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None:
"homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info", "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info",
return_value=FIRMWARE_INFO_MOCK, return_value=FIRMWARE_INFO_MOCK,
) as mock_firmware_info, patch( ) as mock_firmware_info, patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check",
return_value=FIRMWARE_UPDATE_CHECK_MOCK,
) as mock_firmware_update_check, patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board",
return_value=SYSTEM_BOARD_MOCK, return_value=SYSTEM_BOARD_MOCK,
) as mock_system_board, patch( ) as mock_system_board, patch(
@ -57,14 +64,16 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None:
mock_firmware_condition.assert_called_once() mock_firmware_condition.assert_called_once()
mock_firmware_info.assert_called_once() mock_firmware_info.assert_called_once()
mock_firmware_update_check.assert_called_once()
mock_system_board.assert_called_once() mock_system_board.assert_called_once()
mock_system_sensor.assert_called_once() mock_system_sensor.assert_called_once()
mock_system_time.assert_called_once() mock_system_time.assert_called_once()
mock_users_verification.assert_not_called() mock_users_verification.assert_called_once()
mock_users_login.assert_called_once() mock_users_login.assert_called_once()
mock_firmware_condition.reset_mock() mock_firmware_condition.reset_mock()
mock_firmware_info.reset_mock() mock_firmware_info.reset_mock()
mock_firmware_update_check.reset_mock()
mock_system_board.reset_mock() mock_system_board.reset_mock()
mock_system_sensor.reset_mock() mock_system_sensor.reset_mock()
mock_system_time.reset_mock() mock_system_time.reset_mock()
@ -72,12 +81,28 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None:
mock_users_login.reset_mock() mock_users_login.reset_mock()
mock_system_sensor.side_effect = QswError mock_system_sensor.side_effect = QswError
async_fire_time_changed(hass, utcnow() + SCAN_INTERVAL) async_fire_time_changed(hass, utcnow() + DATA_SCAN_INTERVAL)
await hass.async_block_till_done() await hass.async_block_till_done()
mock_system_sensor.assert_called_once() mock_system_sensor.assert_called_once()
mock_users_verification.assert_called_once() mock_users_verification.assert_called()
mock_users_login.assert_not_called() mock_users_login.assert_not_called()
state = hass.states.get("sensor.qsw_m408_4c_temperature") state = hass.states.get("sensor.qsw_m408_4c_temperature")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
mock_firmware_update_check.side_effect = APIError
async_fire_time_changed(hass, utcnow() + FW_SCAN_INTERVAL)
await hass.async_block_till_done()
mock_firmware_update_check.assert_called_once()
mock_firmware_update_check.reset_mock()
mock_firmware_update_check.side_effect = QswError
async_fire_time_changed(hass, utcnow() + FW_SCAN_INTERVAL)
await hass.async_block_till_done()
mock_firmware_update_check.assert_called_once()
update = hass.states.get("update.qsw_m408_4c_firmware_update")
assert update.state == STATE_UNAVAILABLE

View File

@ -20,6 +20,9 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.qnap_qsw.QnapQswApi.check_firmware",
return_value=None,
), patch(
"homeassistant.components.qnap_qsw.QnapQswApi.validate", "homeassistant.components.qnap_qsw.QnapQswApi.validate",
return_value=None, return_value=None,
), patch( ), patch(

View File

@ -0,0 +1,26 @@
"""The sensor tests for the QNAP QSW platform."""
from aioqsw.const import API_RESULT, API_VERSION
from homeassistant.const import STATE_OFF
from homeassistant.core import HomeAssistant
from .util import FIRMWARE_INFO_MOCK, FIRMWARE_UPDATE_CHECK_MOCK, async_init_integration
async def test_qnap_qsw_update(hass: HomeAssistant) -> None:
"""Test creation of update entities."""
await async_init_integration(hass)
update = hass.states.get("update.qsw_m408_4c_firmware_update")
assert update is not None
assert update.state == STATE_OFF
assert (
update.attributes.get("installed_version")
== FIRMWARE_INFO_MOCK[API_RESULT][API_VERSION]
)
assert (
update.attributes.get("latest_version")
== FIRMWARE_UPDATE_CHECK_MOCK[API_RESULT][API_VERSION]
)

View File

@ -12,6 +12,8 @@ from aioqsw.const import (
API_COMMIT_CPSS, API_COMMIT_CPSS,
API_COMMIT_ISS, API_COMMIT_ISS,
API_DATE, API_DATE,
API_DESCRIPTION,
API_DOWNLOAD_URL,
API_ERROR_CODE, API_ERROR_CODE,
API_ERROR_MESSAGE, API_ERROR_MESSAGE,
API_FAN1_SPEED, API_FAN1_SPEED,
@ -20,6 +22,7 @@ from aioqsw.const import (
API_MAX_SWITCH_TEMP, API_MAX_SWITCH_TEMP,
API_MESSAGE, API_MESSAGE,
API_MODEL, API_MODEL,
API_NEWER,
API_NUMBER, API_NUMBER,
API_PORT_NUM, API_PORT_NUM,
API_PRODUCT, API_PRODUCT,
@ -90,6 +93,24 @@ FIRMWARE_INFO_MOCK = {
}, },
} }
FIRMWARE_UPDATE_CHECK_MOCK = {
API_ERROR_CODE: 200,
API_ERROR_MESSAGE: "OK",
API_RESULT: {
API_VERSION: "1.2.0",
API_NUMBER: "29649",
API_BUILD_NUMBER: "20220128",
API_DATE: "Fri, 28 Jan 2022 01:17:39 +0800",
API_DESCRIPTION: "",
API_DOWNLOAD_URL: [
"https://download.qnap.com/Storage/Networking/QSW408FW/QSW-M408AC3-FW.v1.2.0_S20220128_29649.img",
"https://eu1.qnap.com/Storage/Networking/QSW408FW/QSW-M408AC3-FW.v1.2.0_S20220128_29649.img",
"https://us1.qnap.com/Storage/Networking/QSW408FW/QSW-M408AC3-FW.v1.2.0_S20220128_29649.img",
],
API_NEWER: False,
},
}
SYSTEM_COMMAND_MOCK = { SYSTEM_COMMAND_MOCK = {
API_ERROR_CODE: 200, API_ERROR_CODE: 200,
API_ERROR_MESSAGE: "OK", API_ERROR_MESSAGE: "OK",
@ -146,6 +167,9 @@ async def async_init_integration(
), patch( ), patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info", "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_info",
return_value=FIRMWARE_INFO_MOCK, return_value=FIRMWARE_INFO_MOCK,
), patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_update_check",
return_value=FIRMWARE_UPDATE_CHECK_MOCK,
), patch( ), patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_system_board", "homeassistant.components.qnap_qsw.QnapQswApi.get_system_board",
return_value=SYSTEM_BOARD_MOCK, return_value=SYSTEM_BOARD_MOCK,
@ -155,6 +179,9 @@ async def async_init_integration(
), patch( ), patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_system_time", "homeassistant.components.qnap_qsw.QnapQswApi.get_system_time",
return_value=SYSTEM_TIME_MOCK, return_value=SYSTEM_TIME_MOCK,
), patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_users_verification",
return_value=USERS_VERIFICATION_MOCK,
), patch( ), patch(
"homeassistant.components.qnap_qsw.QnapQswApi.post_users_login", "homeassistant.components.qnap_qsw.QnapQswApi.post_users_login",
return_value=USERS_LOGIN_MOCK, return_value=USERS_LOGIN_MOCK,