Add config_flow to QNAP (#80450)
* Create config_flow.py
* Update __init__.py
* Create const.py
* Create strings.json
* Update sensor.py
* Update manifest.json
* Update manifest.json
* Add device name to entities
* Correcting health sensor
* Update manifest.json
* Adding integration_type
* Update sensor.py
* Update __init__.py
* Enums
* Update sensor.py
* Removed unused notify_create
* Switch to SensorDeviceClass.TEMPERATURE
* Update enums
* Remove SENSOR_TYPES from const.py
* Add SENSOR_TYPES to sensor.py
* Removed dependancies
* Removed import yaml
* Removed entity_registry_enabled_default
* Update const.py remove dups
* Update manifest.json removed dups
* Update __init__.py
* Update const.py
* Update manifest.json
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py remove unused
* Update sensor.py add docstring
* Update sensor.py add super
* Remove FOLDER sensors
* Remove VOLUME_NAME
* fix cli
* fix cli
* Add config flow tests
* Update requirements_test_all.txt
* Update CODEOWNERS
* Update homeassistant/components/qnap/config_flow.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/config_flow.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/config_flow.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update __init__.py
Change unload to walrus
Remove async_setup
* Update const.py remove PLATFORMS
* Update __init__.py add Platform Enum
As per epenet
* Update __init__.py
* Update config_flow.py
* Update sensor.py
* Update __init__.py
ruff
* Update config_flow.py
Ruff
* Update sensor.py
* Update const.py remove attrs
* Update sensor.py add attrs
* Revert tuple for sensor extend
* Update sensor.py
* Update coordinator.py
* Update coordinator.py
* Update sensor.py
* Update coordinator.py
* Update homeassistant/components/qnap/__init__.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/const.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/__init__.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update coordinator.py
* Update __init__.py
* Update coordinator.py
* Update sensor.py
* Add device_info
* Update sensor.py
* Update sensor.py self._attr_unique_id
* Update sensor.py
* Update sensor.py
* Type Hints
* Move tuples
* Drive sensor name
* Remove caps
* Remove caps
* Add YAML import
* Update sensor.py fix ruff
* Revert tuples
* Update sensor.py as per review
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py
* Update sensor.py
* assert uid is not None
* Update homeassistant/components/qnap/sensor.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/sensor.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update .coveragerc
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/config_flow.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/config_flow.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/const.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update homeassistant/components/qnap/const.py
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
* Update sensor.py
* Update config_flow.py add imports
* Update const.py
* Update sensor.py move confs to const
* Update config_flow.py add const
* Update test_config_flow.py remove const name
* Update test_config_flow.py remove standard result const
* Update test_config_flow.py
* Combine tests
* Update test_config_flow.py
* Update config_flow.py
* Update test_config_flow.py
* Update config_flow.py
* Update test_config_flow.py
* Update test_config_flow.py
* Update sensor.py change UID as requested
* Update sensor.py added check for monitor_device
* fix tests
* Remove rounding
* Revert "Remove rounding"
This reverts commit 61bf653c06
.
---------
Co-authored-by: starkillerOG <starkiller.og@gmail.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
pull/95393/head
parent
43fe30f6ee
commit
7c676c0a7d
|
@ -942,6 +942,8 @@ omit =
|
|||
homeassistant/components/pyload/sensor.py
|
||||
homeassistant/components/qbittorrent/__init__.py
|
||||
homeassistant/components/qbittorrent/sensor.py
|
||||
homeassistant/components/qnap/__init__.py
|
||||
homeassistant/components/qnap/coordinator.py
|
||||
homeassistant/components/qnap/sensor.py
|
||||
homeassistant/components/qrcode/image_processing.py
|
||||
homeassistant/components/quantum_gateway/device_tracker.py
|
||||
|
|
|
@ -970,6 +970,7 @@ build.json @home-assistant/supervisor
|
|||
/homeassistant/components/qld_bushfire/ @exxamalte
|
||||
/tests/components/qld_bushfire/ @exxamalte
|
||||
/homeassistant/components/qnap/ @disforw
|
||||
/tests/components/qnap/ @disforw
|
||||
/homeassistant/components/qnap_qsw/ @Noltari
|
||||
/tests/components/qnap_qsw/ @Noltari
|
||||
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
||||
|
|
|
@ -1 +1,33 @@
|
|||
"""The qnap component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import QnapCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set the config entry up."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
coordinator = QnapCoordinator(hass, config_entry)
|
||||
# Fetch initial data so we have data when entities subscribe
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
hass.data[DOMAIN][config_entry.entry_id] = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
):
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
return unload_ok
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
"""Config flow to configure qnap component."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from qnapstats import QNAPStats
|
||||
from requests.exceptions import ConnectTimeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_MONITORED_CONDITIONS,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SSL,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import (
|
||||
CONF_DRIVES,
|
||||
CONF_NICS,
|
||||
CONF_VOLUMES,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_SSL,
|
||||
DEFAULT_TIMEOUT,
|
||||
DEFAULT_VERIFY_SSL,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
||||
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
}
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QnapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Qnap configuration flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult:
|
||||
"""Set the config entry up from yaml."""
|
||||
import_info.pop(CONF_MONITORED_CONDITIONS, None)
|
||||
import_info.pop(CONF_NICS, None)
|
||||
import_info.pop(CONF_DRIVES, None)
|
||||
import_info.pop(CONF_VOLUMES, None)
|
||||
return await self.async_step_user(import_info)
|
||||
|
||||
async def async_step_user(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> FlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
host = user_input[CONF_HOST]
|
||||
protocol = "https" if user_input[CONF_SSL] else "http"
|
||||
api = QNAPStats(
|
||||
host=f"{protocol}://{host}",
|
||||
port=user_input[CONF_PORT],
|
||||
username=user_input[CONF_USERNAME],
|
||||
password=user_input[CONF_PASSWORD],
|
||||
verify_ssl=user_input[CONF_VERIFY_SSL],
|
||||
timeout=DEFAULT_TIMEOUT,
|
||||
)
|
||||
try:
|
||||
stats = await self.hass.async_add_executor_job(api.get_system_stats)
|
||||
except ConnectTimeout:
|
||||
errors["base"] = "cannot_connect"
|
||||
except TypeError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
_LOGGER.error(error)
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
unique_id = stats["system"]["serial_number"]
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
title = stats["system"]["name"]
|
||||
return self.async_create_entry(title=title, data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=self.add_suggested_values_to_schema(DATA_SCHEMA, user_input),
|
||||
errors=errors,
|
||||
)
|
|
@ -1,6 +1,12 @@
|
|||
"""The Qnap constants."""
|
||||
|
||||
CONF_DRIVES = "drives"
|
||||
CONF_NICS = "nics"
|
||||
CONF_VOLUMES = "volumes"
|
||||
|
||||
DEFAULT_PORT = 8080
|
||||
DEFAULT_TIMEOUT = 5
|
||||
DEFAULT_SSL = False
|
||||
DEFAULT_VERIFY_SSL = True
|
||||
|
||||
DOMAIN = "qnap"
|
||||
|
|
|
@ -7,6 +7,7 @@ from typing import Any
|
|||
|
||||
from qnapstats import QNAPStats
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
|
@ -17,7 +18,6 @@ from homeassistant.const import (
|
|||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
|
@ -30,18 +30,18 @@ _LOGGER = logging.getLogger(__name__)
|
|||
class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
|
||||
"""Custom coordinator for the qnap integration."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: ConfigType) -> None:
|
||||
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize the qnap coordinator."""
|
||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
|
||||
|
||||
protocol = "https" if config[CONF_SSL] else "http"
|
||||
protocol = "https" if config_entry.data[CONF_SSL] else "http"
|
||||
self._api = QNAPStats(
|
||||
f"{protocol}://{config.get(CONF_HOST)}",
|
||||
config.get(CONF_PORT),
|
||||
config.get(CONF_USERNAME),
|
||||
config.get(CONF_PASSWORD),
|
||||
verify_ssl=config.get(CONF_VERIFY_SSL),
|
||||
timeout=config.get(CONF_TIMEOUT),
|
||||
f"{protocol}://{config_entry.data.get(CONF_HOST)}",
|
||||
config_entry.data.get(CONF_PORT),
|
||||
config_entry.data.get(CONF_USERNAME),
|
||||
config_entry.data.get(CONF_PASSWORD),
|
||||
verify_ssl=config_entry.data.get(CONF_VERIFY_SSL),
|
||||
timeout=config_entry.data.get(CONF_TIMEOUT),
|
||||
)
|
||||
|
||||
def _sync_update(self) -> dict[str, dict[str, Any]]:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"domain": "qnap",
|
||||
"name": "QNAP",
|
||||
"codeowners": ["@disforw"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/qnap",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
"""Support for QNAP NAS Sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
|
@ -28,17 +31,25 @@ from homeassistant.const import (
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DEFAULT_PORT, DEFAULT_TIMEOUT
|
||||
from .const import (
|
||||
CONF_DRIVES,
|
||||
CONF_NICS,
|
||||
CONF_VOLUMES,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TIMEOUT,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import QnapCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_DRIVE = "Drive"
|
||||
ATTR_DRIVE_SIZE = "Drive Size"
|
||||
ATTR_IP = "IP Address"
|
||||
ATTR_MAC = "MAC Address"
|
||||
ATTR_MASK = "Mask"
|
||||
|
@ -53,13 +64,6 @@ ATTR_TYPE = "Type"
|
|||
ATTR_UPTIME = "Uptime"
|
||||
ATTR_VOLUME_SIZE = "Volume Size"
|
||||
|
||||
CONF_DRIVES = "drives"
|
||||
CONF_NICS = "nics"
|
||||
CONF_VOLUMES = "volumes"
|
||||
|
||||
NOTIFICATION_ID = "qnap_notification"
|
||||
NOTIFICATION_TITLE = "QNAP Sensor Setup"
|
||||
|
||||
_SYSTEM_MON_COND: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key="status",
|
||||
|
@ -224,72 +228,87 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the QNAP NAS sensor."""
|
||||
coordinator = QnapCoordinator(hass, config)
|
||||
"""Set up the qnap sensor platform from yaml."""
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
)
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entry."""
|
||||
coordinator = QnapCoordinator(hass, config_entry)
|
||||
await coordinator.async_refresh()
|
||||
if not coordinator.last_update_success:
|
||||
raise PlatformNotReady
|
||||
|
||||
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
|
||||
uid = config_entry.unique_id
|
||||
assert uid is not None
|
||||
sensors: list[QNAPSensor] = []
|
||||
|
||||
# Basic sensors
|
||||
sensors.extend(
|
||||
[
|
||||
QNAPSystemSensor(coordinator, description)
|
||||
QNAPSystemSensor(coordinator, description, uid)
|
||||
for description in _SYSTEM_MON_COND
|
||||
if description.key in monitored_conditions
|
||||
]
|
||||
)
|
||||
|
||||
sensors.extend(
|
||||
[
|
||||
QNAPCPUSensor(coordinator, description)
|
||||
for description in _CPU_MON_COND
|
||||
if description.key in monitored_conditions
|
||||
]
|
||||
[QNAPCPUSensor(coordinator, description, uid) for description in _CPU_MON_COND]
|
||||
)
|
||||
|
||||
sensors.extend(
|
||||
[
|
||||
QNAPMemorySensor(coordinator, description)
|
||||
QNAPMemorySensor(coordinator, description, uid)
|
||||
for description in _MEMORY_MON_COND
|
||||
if description.key in monitored_conditions
|
||||
]
|
||||
)
|
||||
|
||||
# Network sensors
|
||||
sensors.extend(
|
||||
[
|
||||
QNAPNetworkSensor(coordinator, description, nic)
|
||||
for nic in config.get(CONF_NICS, coordinator.data["system_stats"]["nics"])
|
||||
QNAPNetworkSensor(coordinator, description, uid, nic)
|
||||
for nic in coordinator.data["system_stats"]["nics"]
|
||||
for description in _NETWORK_MON_COND
|
||||
if description.key in monitored_conditions
|
||||
]
|
||||
)
|
||||
|
||||
# Drive sensors
|
||||
sensors.extend(
|
||||
[
|
||||
QNAPDriveSensor(coordinator, description, drive)
|
||||
for drive in config.get(CONF_DRIVES, coordinator.data["smart_drive_health"])
|
||||
QNAPDriveSensor(coordinator, description, uid, drive)
|
||||
for drive in coordinator.data["smart_drive_health"]
|
||||
for description in _DRIVE_MON_COND
|
||||
if description.key in monitored_conditions
|
||||
]
|
||||
)
|
||||
|
||||
# Volume sensors
|
||||
sensors.extend(
|
||||
[
|
||||
QNAPVolumeSensor(coordinator, description, volume)
|
||||
for volume in config.get(CONF_VOLUMES, coordinator.data["volumes"])
|
||||
QNAPVolumeSensor(coordinator, description, uid, volume)
|
||||
for volume in coordinator.data["volumes"]
|
||||
for description in _VOLUME_MON_COND
|
||||
if description.key in monitored_conditions
|
||||
]
|
||||
)
|
||||
|
||||
add_entities(sensors)
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
||||
def round_nicely(number):
|
||||
|
@ -309,13 +328,24 @@ class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity):
|
|||
self,
|
||||
coordinator: QnapCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
unique_id: str,
|
||||
monitor_device: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self.monitor_device = monitor_device
|
||||
self.device_name = self.coordinator.data["system_stats"]["system"]["name"]
|
||||
self.monitor_device = monitor_device
|
||||
self._attr_unique_id = f"{unique_id}_{description.key}"
|
||||
if monitor_device:
|
||||
self._attr_unique_id = f"{self._attr_unique_id}_{monitor_device}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, unique_id)},
|
||||
name=self.device_name,
|
||||
model=self.coordinator.data["system_stats"]["system"]["model"],
|
||||
sw_version=self.coordinator.data["system_stats"]["firmware"]["version"],
|
||||
manufacturer="QNAP",
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -493,7 +523,5 @@ class QNAPVolumeSensor(QNAPSensor):
|
|||
total_gb = int(data["total_size"]) / 1024 / 1024 / 1024
|
||||
|
||||
return {
|
||||
ATTR_VOLUME_SIZE: (
|
||||
f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"
|
||||
)
|
||||
ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Connect to the QNAP device",
|
||||
"description": "This qnap sensor allows getting various statistics from your QNAP NAS.",
|
||||
"data": {
|
||||
"host": "Hostname",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"port": "Port",
|
||||
"ssl": "Enable SSL",
|
||||
"verify_ssl": "Verify SSL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Cannot connect to host",
|
||||
"invalid_auth": "Bad authentication",
|
||||
"unknown": "Unknown error"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -355,6 +355,7 @@ FLOWS = {
|
|||
"pvpc_hourly_pricing",
|
||||
"qbittorrent",
|
||||
"qingping",
|
||||
"qnap",
|
||||
"qnap_qsw",
|
||||
"rachio",
|
||||
"radarr",
|
||||
|
|
|
@ -4387,7 +4387,7 @@
|
|||
"integrations": {
|
||||
"qnap": {
|
||||
"integration_type": "device",
|
||||
"config_flow": false,
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling",
|
||||
"name": "QNAP"
|
||||
},
|
||||
|
|
|
@ -1636,6 +1636,9 @@ pyzerproc==0.4.8
|
|||
# homeassistant.components.qingping
|
||||
qingping-ble==0.8.2
|
||||
|
||||
# homeassistant.components.qnap
|
||||
qnapstats==0.4.0
|
||||
|
||||
# homeassistant.components.radio_browser
|
||||
radios==0.1.1
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Tests for the QNAP integration."""
|
|
@ -0,0 +1,33 @@
|
|||
"""Setup the QNAP tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
TEST_HOST = "1.2.3.4"
|
||||
TEST_USERNAME = "admin"
|
||||
TEST_PASSWORD = "password"
|
||||
TEST_NAS_NAME = "Test NAS name"
|
||||
TEST_SERIAL = "123456789"
|
||||
|
||||
TEST_SYSTEM_STATS = {"system": {"serial_number": TEST_SERIAL, "name": TEST_NAS_NAME}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.qnap.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def qnap_connect(mock_get_source_ip: None) -> Generator[MagicMock, None, None]:
|
||||
"""Mock qnap connection."""
|
||||
with patch(
|
||||
"homeassistant.components.qnap.config_flow.QNAPStats", autospec=True
|
||||
) as host_mock_class:
|
||||
host_mock = host_mock_class.return_value
|
||||
host_mock.get_system_stats.return_value = TEST_SYSTEM_STATS
|
||||
yield host_mock
|
|
@ -0,0 +1,110 @@
|
|||
"""Test the QNAP config flow."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import ConnectTimeout
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components.qnap import const
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_SSL,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import TEST_HOST, TEST_PASSWORD, TEST_USERNAME
|
||||
|
||||
STANDARD_CONFIG = {
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_HOST: TEST_HOST,
|
||||
}
|
||||
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry", "qnap_connect")
|
||||
|
||||
|
||||
async def test_config_flow(hass: HomeAssistant, qnap_connect: MagicMock) -> None:
|
||||
"""Config flow manually initialized by the user."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {}
|
||||
|
||||
qnap_connect.get_system_stats.side_effect = ConnectTimeout("Test error")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
STANDARD_CONFIG,
|
||||
)
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
qnap_connect.get_system_stats.side_effect = TypeError("Test error")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
STANDARD_CONFIG,
|
||||
)
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
qnap_connect.get_system_stats.side_effect = Exception("Test error")
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
STANDARD_CONFIG,
|
||||
)
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
assert result["errors"] == {"base": "unknown"}
|
||||
|
||||
qnap_connect.get_system_stats.side_effect = None
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
STANDARD_CONFIG,
|
||||
)
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Test NAS name"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_USERNAME: "admin",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_SSL: const.DEFAULT_SSL,
|
||||
CONF_VERIFY_SSL: const.DEFAULT_VERIFY_SSL,
|
||||
CONF_PORT: const.DEFAULT_PORT,
|
||||
}
|
||||
|
||||
|
||||
async def test_config_flow_import(hass: HomeAssistant) -> None:
|
||||
"""Test import of YAML config."""
|
||||
data = STANDARD_CONFIG
|
||||
data[CONF_SSL] = const.DEFAULT_SSL
|
||||
data[CONF_VERIFY_SSL] = const.DEFAULT_VERIFY_SSL
|
||||
data[CONF_PORT] = const.DEFAULT_PORT
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data=data,
|
||||
)
|
||||
|
||||
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Test NAS name"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_USERNAME: "admin",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_SSL: const.DEFAULT_SSL,
|
||||
CONF_VERIFY_SSL: const.DEFAULT_VERIFY_SSL,
|
||||
CONF_PORT: const.DEFAULT_PORT,
|
||||
}
|
Loading…
Reference in New Issue