Sabnzbd config flow (#68138)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Shai Ungar 2022-04-27 09:09:10 +03:00 committed by GitHub
parent b1a6521abd
commit 3f5027834b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 463 additions and 332 deletions

View File

@ -992,7 +992,8 @@ omit =
homeassistant/components/rtorrent/sensor.py
homeassistant/components/russound_rio/media_player.py
homeassistant/components/russound_rnet/media_player.py
homeassistant/components/sabnzbd/*
homeassistant/components/sabnzbd/__init__.py
homeassistant/components/sabnzbd/sensor.py
homeassistant/components/saj/sensor.py
homeassistant/components/satel_integra/*
homeassistant/components/schluter/*

View File

@ -855,6 +855,8 @@ build.json @home-assistant/supervisor
/tests/components/rtsp_to_webrtc/ @allenporter
/homeassistant/components/ruckus_unleashed/ @gabe565
/tests/components/ruckus_unleashed/ @gabe565
/homeassistant/components/sabnzbd/ @shaiu
/tests/components/sabnzbd/ @shaiu
/homeassistant/components/safe_mode/ @home-assistant/core
/tests/components/safe_mode/ @home-assistant/core
/homeassistant/components/saj/ @fredericvl

View File

@ -58,7 +58,6 @@ class ServiceDetails(NamedTuple):
# These have no config flows
SERVICE_HANDLERS = {
SERVICE_ENIGMA2: ServiceDetails("media_player", "enigma2"),
SERVICE_SABNZBD: ServiceDetails("sabnzbd", None),
"yamaha": ServiceDetails("media_player", "yamaha"),
"frontier_silicon": ServiceDetails("media_player", "frontier_silicon"),
"openhome": ServiceDetails("media_player", "openhome"),
@ -97,6 +96,7 @@ MIGRATED_SERVICE_HANDLERS = [
SERVICE_XIAOMI_GW,
"volumio",
SERVICE_YEELIGHT,
SERVICE_SABNZBD,
"nanoleaf_aurora",
]

View File

@ -1,143 +1,35 @@
"""Support for monitoring an SABnzbd NZB client."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
import logging
from pysabnzbd import SabnzbdApi, SabnzbdApiException
from pysabnzbd import SabnzbdApiException
import voluptuous as vol
from homeassistant.components import configurator
from homeassistant.components.discovery import SERVICE_SABNZBD
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_NAME,
CONF_PATH,
CONF_PORT,
CONF_SENSORS,
CONF_SSL,
DATA_GIGABYTES,
DATA_MEGABYTES,
DATA_RATE_MEGABYTES_PER_SECOND,
Platform,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_PATH, CONF_URL
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.json import load_json, save_json
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = "sabnzbd"
DATA_SABNZBD = "sabznbd"
_CONFIGURING: dict[str, str] = {}
ATTR_SPEED = "speed"
BASE_URL_FORMAT = "{}://{}:{}/"
CONFIG_FILE = "sabnzbd.conf"
DEFAULT_HOST = "localhost"
DEFAULT_NAME = "SABnzbd"
DEFAULT_PORT = 8080
DEFAULT_SPEED_LIMIT = "100"
DEFAULT_SSL = False
UPDATE_INTERVAL = timedelta(seconds=30)
SERVICE_PAUSE = "pause"
SERVICE_RESUME = "resume"
SERVICE_SET_SPEED = "set_speed"
SIGNAL_SABNZBD_UPDATED = "sabnzbd_updated"
@dataclass
class SabnzbdRequiredKeysMixin:
"""Mixin for required keys."""
field_name: str
@dataclass
class SabnzbdSensorEntityDescription(SensorEntityDescription, SabnzbdRequiredKeysMixin):
"""Describes Sabnzbd sensor entity."""
SENSOR_TYPES: tuple[SabnzbdSensorEntityDescription, ...] = (
SabnzbdSensorEntityDescription(
key="current_status",
name="Status",
field_name="status",
),
SabnzbdSensorEntityDescription(
key="speed",
name="Speed",
native_unit_of_measurement=DATA_RATE_MEGABYTES_PER_SECOND,
field_name="kbpersec",
),
SabnzbdSensorEntityDescription(
key="queue_size",
name="Queue",
native_unit_of_measurement=DATA_MEGABYTES,
field_name="mb",
),
SabnzbdSensorEntityDescription(
key="queue_remaining",
name="Left",
native_unit_of_measurement=DATA_MEGABYTES,
field_name="mbleft",
),
SabnzbdSensorEntityDescription(
key="disk_size",
name="Disk",
native_unit_of_measurement=DATA_GIGABYTES,
field_name="diskspacetotal1",
),
SabnzbdSensorEntityDescription(
key="disk_free",
name="Disk Free",
native_unit_of_measurement=DATA_GIGABYTES,
field_name="diskspace1",
),
SabnzbdSensorEntityDescription(
key="queue_count",
name="Queue Count",
field_name="noofslots_total",
),
SabnzbdSensorEntityDescription(
key="day_size",
name="Daily Total",
native_unit_of_measurement=DATA_GIGABYTES,
field_name="day_size",
),
SabnzbdSensorEntityDescription(
key="week_size",
name="Weekly Total",
native_unit_of_measurement=DATA_GIGABYTES,
field_name="week_size",
),
SabnzbdSensorEntityDescription(
key="month_size",
name="Monthly Total",
native_unit_of_measurement=DATA_GIGABYTES,
field_name="month_size",
),
SabnzbdSensorEntityDescription(
key="total_size",
name="Total",
native_unit_of_measurement=DATA_GIGABYTES,
field_name="total_size",
),
from .const import (
ATTR_SPEED,
DEFAULT_NAME,
DEFAULT_SPEED_LIMIT,
DOMAIN,
KEY_API,
KEY_NAME,
SERVICE_PAUSE,
SERVICE_RESUME,
SERVICE_SET_SPEED,
SIGNAL_SABNZBD_UPDATED,
UPDATE_INTERVAL,
)
from .sab import get_client
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
PLATFORMS = ["sensor"]
_LOGGER = logging.getLogger(__name__)
SPEED_LIMIT_SCHEMA = vol.Schema(
{vol.Optional(ATTR_SPEED, default=DEFAULT_SPEED_LIMIT): cv.string}
@ -147,15 +39,10 @@ CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PATH): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SENSORS): vol.All(
cv.ensure_list, [vol.In(SENSOR_KEYS)]
),
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
vol.Required(CONF_URL): str,
vol.Optional(CONF_PATH): str,
}
)
},
@ -163,72 +50,39 @@ CONFIG_SCHEMA = vol.Schema(
)
async def async_check_sabnzbd(sab_api):
"""Check if we can reach SABnzbd."""
try:
await sab_api.check_available()
return True
except SabnzbdApiException:
_LOGGER.error("Connection to SABnzbd API failed")
return False
async def async_configure_sabnzbd(
hass, config, use_ssl, name=DEFAULT_NAME, api_key=None
):
"""Try to configure Sabnzbd and request api key if configuration fails."""
host = config[CONF_HOST]
port = config[CONF_PORT]
web_root = config.get(CONF_PATH)
uri_scheme = "https" if use_ssl else "http"
base_url = BASE_URL_FORMAT.format(uri_scheme, host, port)
if api_key is None:
conf = await hass.async_add_executor_job(
load_json, hass.config.path(CONFIG_FILE)
)
api_key = conf.get(base_url, {}).get(CONF_API_KEY, "")
sab_api = SabnzbdApi(
base_url, api_key, web_root=web_root, session=async_get_clientsession(hass)
)
if await async_check_sabnzbd(sab_api):
async_setup_sabnzbd(hass, sab_api, config, name)
else:
async_request_configuration(hass, config, base_url, web_root)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the SABnzbd component."""
hass.data.setdefault(DOMAIN, {})
async def sabnzbd_discovered(service: str, info: DiscoveryInfoType | None) -> None:
"""Handle service discovery."""
if not info:
return
ssl = info.get("properties", {}).get("https", "0") == "1"
await async_configure_sabnzbd(hass, info, ssl)
if hass.config_entries.async_entries(DOMAIN):
return True
discovery.async_listen(hass, SERVICE_SABNZBD, sabnzbd_discovered)
if DOMAIN in config:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config[DOMAIN],
)
)
if (conf := config.get(DOMAIN)) is not None:
use_ssl = conf[CONF_SSL]
name = conf.get(CONF_NAME)
api_key = conf.get(CONF_API_KEY)
await async_configure_sabnzbd(hass, conf, use_ssl, name, api_key)
return True
@callback
def async_setup_sabnzbd(hass, sab_api, config, name):
"""Set up SABnzbd sensors and services."""
sab_api_data = SabnzbdApiData(sab_api, name, config.get(CONF_SENSORS, {}))
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the SabNzbd Component."""
sab_api = await get_client(hass, entry.data)
if not sab_api:
raise ConfigEntryNotReady
if config.get(CONF_SENSORS):
hass.data[DATA_SABNZBD] = sab_api_data
hass.async_create_task(
discovery.async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, config)
)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
KEY_API: sab_api,
KEY_NAME: entry.data[CONF_NAME],
}
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
sab_api_data = SabnzbdApiData(sab_api)
async def async_service_handler(service: ServiceCall) -> None:
"""Handle service calls."""
@ -254,7 +108,6 @@ def async_setup_sabnzbd(hass, sab_api, config, name):
async def async_update_sabnzbd(now):
"""Refresh SABnzbd queue data."""
try:
await sab_api.refresh_data()
async_dispatcher_send(hass, SIGNAL_SABNZBD_UPDATED, None)
@ -263,57 +116,15 @@ def async_setup_sabnzbd(hass, sab_api, config, name):
async_track_time_interval(hass, async_update_sabnzbd, UPDATE_INTERVAL)
@callback
def async_request_configuration(hass, config, host, web_root):
"""Request configuration steps from the user."""
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.async_notify_errors(
hass, _CONFIGURING[host], "Failed to register, please try again."
)
return
async def async_configuration_callback(data):
"""Handle configuration changes."""
api_key = data.get(CONF_API_KEY)
sab_api = SabnzbdApi(
host, api_key, web_root=web_root, session=async_get_clientsession(hass)
)
if not await async_check_sabnzbd(sab_api):
return
def success():
"""Signal successful setup."""
conf = load_json(hass.config.path(CONFIG_FILE))
conf[host] = {CONF_API_KEY: api_key}
save_json(hass.config.path(CONFIG_FILE), conf)
req_config = _CONFIGURING.pop(host)
configurator.request_done(hass, req_config)
hass.async_add_job(success)
async_setup_sabnzbd(hass, sab_api, config, config.get(CONF_NAME, DEFAULT_NAME))
_CONFIGURING[host] = configurator.async_request_config(
hass,
DEFAULT_NAME,
async_configuration_callback,
description="Enter the API Key",
submit_caption="Confirm",
fields=[{"id": CONF_API_KEY, "name": "API Key", "type": ""}],
)
return True
class SabnzbdApiData:
"""Class for storing/refreshing sabnzbd api queue data."""
def __init__(self, sab_api, name, sensors):
def __init__(self, sab_api):
"""Initialize component."""
self.sab_api = sab_api
self.name = name
self.sensors = sensors
async def async_pause_queue(self):
"""Pause Sabnzbd queue."""

View File

@ -0,0 +1,79 @@
"""Adds config flow for SabNzbd."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_NAME,
CONF_PATH,
CONF_PORT,
CONF_SSL,
CONF_URL,
)
from homeassistant.data_entry_flow import FlowResult
from .const import DEFAULT_NAME, DOMAIN
from .sab import get_client
_LOGGER = logging.getLogger(__name__)
USER_SCHEMA = vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
vol.Required(CONF_URL): str,
vol.Optional(CONF_PATH): str,
}
)
class SABnzbdConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Sabnzbd config flow."""
VERSION = 1
async def _async_validate_input(self, user_input):
"""Validate the user input allows us to connect."""
errors = {}
sab_api = await get_client(self.hass, user_input)
if not sab_api:
errors["base"] = "cannot_connect"
return errors
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:
errors = await self._async_validate_input(user_input)
if not errors:
return self.async_create_entry(
title=user_input[CONF_API_KEY][:12], data=user_input
)
return self.async_show_form(
step_id="user",
data_schema=USER_SCHEMA,
errors=errors,
)
async def async_step_import(self, import_data):
"""Import sabnzbd config from configuration.yaml."""
import_data[CONF_URL] = (
("https://" if import_data[CONF_SSL] else "http://")
+ import_data[CONF_HOST]
+ ":"
+ str(import_data[CONF_PORT])
)
return await self.async_step_user(import_data)

View File

@ -0,0 +1,25 @@
"""Constants for the Sabnzbd component."""
from datetime import timedelta
DOMAIN = "sabnzbd"
DATA_SABNZBD = "sabnzbd"
ATTR_SPEED = "speed"
BASE_URL_FORMAT = "{}://{}:{}/"
CONFIG_FILE = "sabnzbd.conf"
DEFAULT_HOST = "localhost"
DEFAULT_NAME = "SABnzbd"
DEFAULT_PORT = 8080
DEFAULT_SPEED_LIMIT = "100"
DEFAULT_SSL = False
UPDATE_INTERVAL = timedelta(seconds=30)
SERVICE_PAUSE = "pause"
SERVICE_RESUME = "resume"
SERVICE_SET_SPEED = "set_speed"
SIGNAL_SABNZBD_UPDATED = "sabnzbd_updated"
KEY_API = "api"
KEY_NAME = "name"

View File

@ -0,0 +1,10 @@
"""Errors for the Sabnzbd component."""
from homeassistant.exceptions import HomeAssistantError
class AuthenticationError(HomeAssistantError):
"""Wrong Username or Password."""
class UnknownError(HomeAssistantError):
"""Unknown Error."""

View File

@ -5,7 +5,8 @@
"requirements": ["pysabnzbd==1.1.1"],
"dependencies": ["configurator"],
"after_dependencies": ["discovery"],
"codeowners": [],
"codeowners": ["@shaiu"],
"iot_class": "local_polling",
"config_flow": true,
"loggers": ["pysabnzbd"]
}

View File

@ -0,0 +1,27 @@
"""Support for the Sabnzbd service."""
from pysabnzbd import SabnzbdApi, SabnzbdApiException
from homeassistant.const import CONF_API_KEY, CONF_PATH, CONF_URL
from homeassistant.core import _LOGGER, HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
async def get_client(hass: HomeAssistant, data):
"""Get Sabnzbd client."""
web_root = data.get(CONF_PATH)
api_key = data[CONF_API_KEY]
url = data[CONF_URL]
sab_api = SabnzbdApi(
url,
api_key,
web_root=web_root,
session=async_get_clientsession(hass, False),
)
try:
await sab_api.check_available()
except SabnzbdApiException as exception:
_LOGGER.error("Connection to SABnzbd API failed: %s", exception.message)
return False
return sab_api

View File

@ -1,39 +1,120 @@
"""Support for monitoring an SABnzbd NZB client."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from dataclasses import dataclass
from . import (
DATA_SABNZBD,
SENSOR_TYPES,
SIGNAL_SABNZBD_UPDATED,
SabnzbdSensorEntityDescription,
from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import DOMAIN, SIGNAL_SABNZBD_UPDATED, SabnzbdApiData
from ...config_entries import ConfigEntry
from ...const import DATA_GIGABYTES, DATA_MEGABYTES, DATA_RATE_MEGABYTES_PER_SECOND
from ...core import HomeAssistant
from ...helpers.entity_platform import AddEntitiesCallback
from .const import KEY_API, KEY_NAME
@dataclass
class SabnzbdRequiredKeysMixin:
"""Mixin for required keys."""
key: str
@dataclass
class SabnzbdSensorEntityDescription(SensorEntityDescription, SabnzbdRequiredKeysMixin):
"""Describes Sabnzbd sensor entity."""
SENSOR_TYPES: tuple[SabnzbdSensorEntityDescription, ...] = (
SabnzbdSensorEntityDescription(
key="status",
name="Status",
),
SabnzbdSensorEntityDescription(
key="kbpersec",
name="Speed",
native_unit_of_measurement=DATA_RATE_MEGABYTES_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
),
SabnzbdSensorEntityDescription(
key="mb",
name="Queue",
native_unit_of_measurement=DATA_MEGABYTES,
state_class=SensorStateClass.MEASUREMENT,
),
SabnzbdSensorEntityDescription(
key="mbleft",
name="Left",
native_unit_of_measurement=DATA_MEGABYTES,
state_class=SensorStateClass.MEASUREMENT,
),
SabnzbdSensorEntityDescription(
key="diskspacetotal1",
name="Disk",
native_unit_of_measurement=DATA_GIGABYTES,
state_class=SensorStateClass.MEASUREMENT,
),
SabnzbdSensorEntityDescription(
key="diskspace1",
name="Disk Free",
native_unit_of_measurement=DATA_GIGABYTES,
state_class=SensorStateClass.MEASUREMENT,
),
SabnzbdSensorEntityDescription(
key="noofslots_total",
name="Queue Count",
state_class=SensorStateClass.TOTAL,
),
SabnzbdSensorEntityDescription(
key="day_size",
name="Daily Total",
native_unit_of_measurement=DATA_GIGABYTES,
entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SabnzbdSensorEntityDescription(
key="week_size",
name="Weekly Total",
native_unit_of_measurement=DATA_GIGABYTES,
entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SabnzbdSensorEntityDescription(
key="month_size",
name="Monthly Total",
native_unit_of_measurement=DATA_GIGABYTES,
entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SabnzbdSensorEntityDescription(
key="total_size",
name="Total",
native_unit_of_measurement=DATA_GIGABYTES,
state_class=SensorStateClass.TOTAL_INCREASING,
),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
async def async_setup_platform(
async def async_setup_entry(
hass: HomeAssistant,
config: ConfigType,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the SABnzbd sensors."""
if discovery_info is None:
return
"""Set up a Sabnzbd sensor entry."""
sab_api = hass.data[DOMAIN][config_entry.entry_id][KEY_API]
client_name = hass.data[DOMAIN][config_entry.entry_id][KEY_NAME]
sab_api_data = SabnzbdApiData(sab_api)
sab_api_data = hass.data[DATA_SABNZBD]
sensors = sab_api_data.sensors
client_name = sab_api_data.name
async_add_entities(
[
SabnzbdSensor(sab_api_data, client_name, description)
for description in SENSOR_TYPES
if description.key in sensors
]
[SabnzbdSensor(sab_api_data, client_name, sensor) for sensor in SENSOR_TYPES]
)
@ -62,7 +143,7 @@ class SabnzbdSensor(SensorEntity):
def update_state(self, args):
"""Get the latest data and updates the states."""
self._attr_native_value = self._sabnzbd_api.get_queue_field(
self.entity_description.field_name
self.entity_description.key
)
if self.entity_description.key == "speed":

View File

@ -0,0 +1,18 @@
{
"config": {
"step": {
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"name": "[%key:common::config_flow::data::name%]",
"url": "[%key:common::config_flow::data::url%]",
"path": "[%key:common::config_flow::data::path%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]"
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"error": {
"cannot_connect": "Failed to connect",
"invalid_api_key": "Invalid API key"
},
"step": {
"user": {
"data": {
"api_key": "API Key",
"name": "Name",
"url": "URL",
"path": "Path"
},
"description": "If you need help with the configuration have a look here: https://www.home-assistant.io/integrations/sabnzbd/",
"title": "Sabnzbd"
}
}
}
}

View File

@ -290,6 +290,7 @@ FLOWS = {
"rpi_power",
"rtsp_to_webrtc",
"ruckus_unleashed",
"sabnzbd",
"samsungtv",
"screenlogic",
"season",

View File

@ -45,7 +45,8 @@
"latitude": "Latitude",
"location": "Location",
"pin": "PIN Code",
"mode": "Mode"
"mode": "Mode",
"path": "Path"
},
"create_entry": {
"authenticated": "Successfully authenticated"

View File

@ -1186,6 +1186,9 @@ pyrituals==0.0.6
# homeassistant.components.ruckus_unleashed
pyruckus==0.12
# homeassistant.components.sabnzbd
pysabnzbd==1.1.1
# homeassistant.components.sensibo
pysensibo==1.0.12

View File

@ -16,18 +16,12 @@ from tests.common import async_fire_time_changed, mock_coro
SERVICE = "yamaha"
SERVICE_COMPONENT = "media_player"
# sabnzbd is the last no platform integration to be migrated
# drop these tests once it is migrated
SERVICE_NO_PLATFORM = "sabnzbd"
SERVICE_NO_PLATFORM_COMPONENT = "sabnzbd"
SERVICE_INFO = {"key": "value"} # Can be anything
UNKNOWN_SERVICE = "this_service_will_never_be_supported"
BASE_CONFIG = {discovery.DOMAIN: {"ignore": [], "enable": []}}
IGNORE_CONFIG = {discovery.DOMAIN: {"ignore": [SERVICE_NO_PLATFORM]}}
@pytest.fixture(autouse=True)
def netdisco_mock():
@ -88,63 +82,6 @@ async def test_load_platform(hass):
)
async def test_load_component(hass):
"""Test load a component."""
def discover(netdisco, zeroconf_instance, suppress_mdns_types):
"""Fake discovery."""
return [(SERVICE_NO_PLATFORM, SERVICE_INFO)]
mock_discover, mock_platform = await mock_discovery(hass, discover)
assert mock_discover.called
assert not mock_platform.called
mock_discover.assert_called_with(
hass,
SERVICE_NO_PLATFORM,
SERVICE_INFO,
SERVICE_NO_PLATFORM_COMPONENT,
BASE_CONFIG,
)
async def test_ignore_service(hass):
"""Test ignore service."""
def discover(netdisco, zeroconf_instance, suppress_mdns_types):
"""Fake discovery."""
return [(SERVICE_NO_PLATFORM, SERVICE_INFO)]
mock_discover, mock_platform = await mock_discovery(hass, discover, IGNORE_CONFIG)
assert not mock_discover.called
assert not mock_platform.called
async def test_discover_duplicates(hass):
"""Test load a component."""
def discover(netdisco, zeroconf_instance, suppress_mdns_types):
"""Fake discovery."""
return [
(SERVICE_NO_PLATFORM, SERVICE_INFO),
(SERVICE_NO_PLATFORM, SERVICE_INFO),
]
mock_discover, mock_platform = await mock_discovery(hass, discover)
assert mock_discover.called
assert mock_discover.call_count == 1
assert not mock_platform.called
mock_discover.assert_called_with(
hass,
SERVICE_NO_PLATFORM,
SERVICE_INFO,
SERVICE_NO_PLATFORM_COMPONENT,
BASE_CONFIG,
)
async def test_discover_config_flow(hass):
"""Test discovery triggering a config flow."""
discovery_info = {"hello": "world"}

View File

@ -0,0 +1 @@
"""Tests for Sabnzbd."""

View File

@ -0,0 +1,113 @@
"""Define tests for the Sabnzbd config flow."""
from unittest.mock import patch
from pysabnzbd import SabnzbdApiException
from homeassistant import data_entry_flow
from homeassistant.components.sabnzbd import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.const import (
CONF_API_KEY,
CONF_HOST,
CONF_NAME,
CONF_PATH,
CONF_PORT,
CONF_SSL,
CONF_URL,
)
from tests.common import MockConfigEntry
VALID_CONFIG = {
CONF_NAME: "Sabnzbd",
CONF_API_KEY: "edc3eee7330e4fdda04489e3fbc283d0",
CONF_URL: "http://localhost:8080",
CONF_PATH: "",
}
VALID_CONFIG_OLD = {
CONF_NAME: "Sabnzbd",
CONF_API_KEY: "edc3eee7330e4fdda04489e3fbc283d0",
CONF_HOST: "localhost",
CONF_PORT: 8080,
CONF_PATH: "",
CONF_SSL: False,
}
async def test_create_entry(hass):
"""Test that the user step works."""
with patch(
"homeassistant.components.sabnzbd.sab.SabnzbdApi.check_available",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=VALID_CONFIG,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "edc3eee7330e"
assert result["data"][CONF_NAME] == "Sabnzbd"
assert result["data"][CONF_API_KEY] == "edc3eee7330e4fdda04489e3fbc283d0"
assert result["data"][CONF_PATH] == ""
async def test_auth_error(hass):
"""Test that the user step fails."""
with patch(
"homeassistant.components.sabnzbd.sab.SabnzbdApi.check_available",
side_effect=SabnzbdApiException("Some error"),
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=VALID_CONFIG,
)
assert result["errors"] == {"base": "cannot_connect"}
async def test_integration_already_exists(hass):
"""Test we only allow a single config flow."""
with patch(
"homeassistant.components.sabnzbd.sab.SabnzbdApi.check_available",
return_value=True,
):
MockConfigEntry(
domain=DOMAIN,
unique_id="123456",
data=VALID_CONFIG,
).add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=VALID_CONFIG,
)
assert result["type"] == "create_entry"
async def test_import_flow(hass) -> None:
"""Test the import configuration flow."""
with patch(
"homeassistant.components.sabnzbd.sab.SabnzbdApi.check_available",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=VALID_CONFIG_OLD,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "edc3eee7330e"
assert result["data"][CONF_NAME] == "Sabnzbd"
assert result["data"][CONF_API_KEY] == "edc3eee7330e4fdda04489e3fbc283d0"
assert result["data"][CONF_HOST] == "localhost"
assert result["data"][CONF_PORT] == 8080
assert result["data"][CONF_PATH] == ""
assert result["data"][CONF_SSL] is False