mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Add nina integration (#56647)
* Added nina integration * Improvements implemented * Fixed lint errors * Added tests * Improvements implemented * Use client session from HA * Added custom coordinator * Fixed tests * Fix pylint errors * Library updated to 0.1.4 * Optimization of static attributes * Removed unused code * Switched to BinarySensorDeviceClass * Switched to Platform Enum * Improve repetition * Improve repetition * Fix corona filter * Removed intermediate variable Co-authored-by: Marvin Wichmann <marvin@fam-wichmann.de> * Fix black formatting Co-authored-by: Marvin Wichmann <marvin@fam-wichmann.de>
This commit is contained in:
parent
cf371ea8dd
commit
9f7b8d3009
@ -353,6 +353,7 @@ homeassistant/components/nextcloud/* @meichthys
|
|||||||
homeassistant/components/nfandroidtv/* @tkdrob
|
homeassistant/components/nfandroidtv/* @tkdrob
|
||||||
homeassistant/components/nightscout/* @marciogranzotto
|
homeassistant/components/nightscout/* @marciogranzotto
|
||||||
homeassistant/components/nilu/* @hfurubotten
|
homeassistant/components/nilu/* @hfurubotten
|
||||||
|
homeassistant/components/nina/* @DeerMaximum
|
||||||
homeassistant/components/nissan_leaf/* @filcole
|
homeassistant/components/nissan_leaf/* @filcole
|
||||||
homeassistant/components/nmap_tracker/* @bdraco
|
homeassistant/components/nmap_tracker/* @bdraco
|
||||||
homeassistant/components/nmbs/* @thibmaek
|
homeassistant/components/nmbs/* @thibmaek
|
||||||
|
106
homeassistant/components/nina/__init__.py
Normal file
106
homeassistant/components/nina/__init__.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
"""The Nina integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from async_timeout import timeout
|
||||||
|
from pynina import ApiError, Nina, Warning as NinaWarning
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
_LOGGER,
|
||||||
|
ATTR_EXPIRES,
|
||||||
|
ATTR_HEADLINE,
|
||||||
|
ATTR_ID,
|
||||||
|
ATTR_SENT,
|
||||||
|
ATTR_START,
|
||||||
|
CONF_FILTER_CORONA,
|
||||||
|
CONF_REGIONS,
|
||||||
|
DOMAIN,
|
||||||
|
SCAN_INTERVAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
PLATFORMS: list[str] = [Platform.BINARY_SENSOR]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up platform from a ConfigEntry."""
|
||||||
|
|
||||||
|
regions: dict[str, str] = entry.data[CONF_REGIONS]
|
||||||
|
|
||||||
|
coordinator = NINADataUpdateCoordinator(
|
||||||
|
hass, regions, entry.data[CONF_FILTER_CORONA]
|
||||||
|
)
|
||||||
|
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class NINADataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
|
"""Class to manage fetching NINA data API."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, regions: dict[str, str], corona_filter: bool
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
self._regions: dict[str, str] = regions
|
||||||
|
self._nina: Nina = Nina(async_get_clientsession(hass))
|
||||||
|
self.warnings: dict[str, Any] = {}
|
||||||
|
self.corona_filter: bool = corona_filter
|
||||||
|
|
||||||
|
for region in regions.keys():
|
||||||
|
self._nina.addRegion(region)
|
||||||
|
|
||||||
|
update_interval: timedelta = SCAN_INTERVAL
|
||||||
|
|
||||||
|
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Update data."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with timeout(10):
|
||||||
|
await self._nina.update()
|
||||||
|
return self._parse_data()
|
||||||
|
except ApiError as err:
|
||||||
|
raise UpdateFailed(err) from err
|
||||||
|
|
||||||
|
def _parse_data(self) -> dict[str, Any]:
|
||||||
|
"""Parse warning data."""
|
||||||
|
|
||||||
|
return_data: dict[str, Any] = {}
|
||||||
|
|
||||||
|
for (
|
||||||
|
region_id
|
||||||
|
) in self._nina.warnings: # pylint: disable=consider-using-dict-items
|
||||||
|
raw_warnings: list[NinaWarning] = self._nina.warnings[region_id]
|
||||||
|
|
||||||
|
warnings_for_regions: list[Any] = []
|
||||||
|
|
||||||
|
for raw_warn in raw_warnings:
|
||||||
|
if "corona" in raw_warn.headline.lower() and self.corona_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
|
warn_obj: dict[str, Any] = {
|
||||||
|
ATTR_ID: raw_warn.id,
|
||||||
|
ATTR_HEADLINE: raw_warn.headline,
|
||||||
|
ATTR_SENT: raw_warn.sent or "",
|
||||||
|
ATTR_START: raw_warn.start or "",
|
||||||
|
ATTR_EXPIRES: raw_warn.expires or "",
|
||||||
|
}
|
||||||
|
warnings_for_regions.append(warn_obj)
|
||||||
|
|
||||||
|
return_data[region_id] = warnings_for_regions
|
||||||
|
|
||||||
|
return return_data
|
94
homeassistant/components/nina/binary_sensor.py
Normal file
94
homeassistant/components/nina/binary_sensor.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
"""NINA sensor platform."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import (
|
||||||
|
BinarySensorDeviceClass,
|
||||||
|
BinarySensorEntity,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from . import NINADataUpdateCoordinator
|
||||||
|
from .const import (
|
||||||
|
ATTR_EXPIRES,
|
||||||
|
ATTR_HEADLINE,
|
||||||
|
ATTR_ID,
|
||||||
|
ATTR_SENT,
|
||||||
|
ATTR_START,
|
||||||
|
CONF_MESSAGE_SLOTS,
|
||||||
|
CONF_REGIONS,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up entries."""
|
||||||
|
|
||||||
|
coordinator: NINADataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
|
regions: dict[str, str] = config_entry.data[CONF_REGIONS]
|
||||||
|
message_slots: int = config_entry.data[CONF_MESSAGE_SLOTS]
|
||||||
|
|
||||||
|
entities: list[NINAMessage] = []
|
||||||
|
|
||||||
|
for ent in coordinator.data:
|
||||||
|
for i in range(0, message_slots):
|
||||||
|
entities.append(NINAMessage(coordinator, ent, regions[ent], i + 1))
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class NINAMessage(CoordinatorEntity, BinarySensorEntity):
|
||||||
|
"""Representation of an NINA warning."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: NINADataUpdateCoordinator,
|
||||||
|
region: str,
|
||||||
|
regionName: str,
|
||||||
|
slotID: int,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
self._region: str = region
|
||||||
|
self._region_name: str = regionName
|
||||||
|
self._slot_id: int = slotID
|
||||||
|
self._warning_index: int = slotID - 1
|
||||||
|
|
||||||
|
self._coordinator: NINADataUpdateCoordinator = coordinator
|
||||||
|
|
||||||
|
self._attr_name: str = f"Warning: {self._region_name} {self._slot_id}"
|
||||||
|
self._attr_unique_id: str = f"{self._region}-{self._slot_id}"
|
||||||
|
self._attr_device_class: str = BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return len(self._coordinator.data[self._region]) > self._warning_index
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Return extra attributes of the sensor."""
|
||||||
|
if (
|
||||||
|
not len(self._coordinator.data[self._region]) > self._warning_index
|
||||||
|
) or not self.is_on:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data: dict[str, Any] = self._coordinator.data[self._region][self._warning_index]
|
||||||
|
|
||||||
|
return {
|
||||||
|
ATTR_HEADLINE: data[ATTR_HEADLINE],
|
||||||
|
ATTR_ID: data[ATTR_ID],
|
||||||
|
ATTR_SENT: data[ATTR_SENT],
|
||||||
|
ATTR_START: data[ATTR_START],
|
||||||
|
ATTR_EXPIRES: data[ATTR_EXPIRES],
|
||||||
|
}
|
138
homeassistant/components/nina/config_flow.py
Normal file
138
homeassistant/components/nina/config_flow.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
"""Config flow for Nina integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pynina import ApiError, Nina
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
_LOGGER,
|
||||||
|
CONF_FILTER_CORONA,
|
||||||
|
CONF_MESSAGE_SLOTS,
|
||||||
|
CONF_REGIONS,
|
||||||
|
CONST_REGION_MAPPING,
|
||||||
|
CONST_REGIONS,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for NINA."""
|
||||||
|
|
||||||
|
VERSION: int = 1
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__()
|
||||||
|
self._all_region_codes_sorted: dict[str, str] = {}
|
||||||
|
self.regions: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
|
for name in CONST_REGIONS:
|
||||||
|
self.regions[name] = {}
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self: ConfigFlow,
|
||||||
|
user_input: dict[str, Any] | None = None,
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors: dict[str, Any] = {}
|
||||||
|
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
|
has_error: bool = False
|
||||||
|
|
||||||
|
if len(self._all_region_codes_sorted) == 0:
|
||||||
|
try:
|
||||||
|
nina: Nina = Nina()
|
||||||
|
|
||||||
|
self._all_region_codes_sorted = self.swap_key_value(
|
||||||
|
await nina.getAllRegionalCodes()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.split_regions()
|
||||||
|
|
||||||
|
except ApiError as err:
|
||||||
|
_LOGGER.warning("NINA setup error: %s", err)
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
has_error = True
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception: %s", err)
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
return self.async_abort(reason="unknown")
|
||||||
|
|
||||||
|
if user_input is not None and not has_error:
|
||||||
|
config: dict[str, Any] = user_input
|
||||||
|
|
||||||
|
config[CONF_REGIONS] = []
|
||||||
|
|
||||||
|
for group in CONST_REGIONS:
|
||||||
|
if group_input := user_input.get(group):
|
||||||
|
config[CONF_REGIONS] += group_input
|
||||||
|
|
||||||
|
if len(config[CONF_REGIONS]) > 0:
|
||||||
|
tmp: dict[str, Any] = {}
|
||||||
|
|
||||||
|
for reg in config[CONF_REGIONS]:
|
||||||
|
tmp[self._all_region_codes_sorted[reg]] = reg.split("_", 1)[0]
|
||||||
|
|
||||||
|
compact: dict[str, Any] = {}
|
||||||
|
|
||||||
|
for key, val in tmp.items():
|
||||||
|
if val in compact:
|
||||||
|
compact[val] = f"{compact[val]} + {key}"
|
||||||
|
break
|
||||||
|
compact[val] = key
|
||||||
|
|
||||||
|
config[CONF_REGIONS] = compact
|
||||||
|
|
||||||
|
return self.async_create_entry(title="NINA", data=config)
|
||||||
|
|
||||||
|
errors["base"] = "no_selection"
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
**{
|
||||||
|
vol.Optional(region): cv.multi_select(self.regions[region])
|
||||||
|
for region in CONST_REGIONS
|
||||||
|
},
|
||||||
|
vol.Required(CONF_MESSAGE_SLOTS, default=5): vol.All(
|
||||||
|
int, vol.Range(min=1, max=20)
|
||||||
|
),
|
||||||
|
vol.Required(CONF_FILTER_CORONA, default=True): cv.boolean,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def swap_key_value(dict_to_sort: dict[str, str]) -> dict[str, str]:
|
||||||
|
"""Swap keys and values in dict."""
|
||||||
|
all_region_codes_swaped: dict[str, str] = {}
|
||||||
|
|
||||||
|
for key, value in dict_to_sort.items():
|
||||||
|
if value not in all_region_codes_swaped:
|
||||||
|
all_region_codes_swaped[value] = key
|
||||||
|
else:
|
||||||
|
for i in range(len(dict_to_sort)):
|
||||||
|
tmp_value: str = value + "_" + str(i)
|
||||||
|
if tmp_value not in all_region_codes_swaped:
|
||||||
|
all_region_codes_swaped[tmp_value] = key
|
||||||
|
break
|
||||||
|
|
||||||
|
return dict(sorted(all_region_codes_swaped.items(), key=lambda ele: ele[1]))
|
||||||
|
|
||||||
|
def split_regions(self) -> None:
|
||||||
|
"""Split regions alphabetical."""
|
||||||
|
for index, name in self._all_region_codes_sorted.items():
|
||||||
|
for region_name, grouping_letters in CONST_REGION_MAPPING.items():
|
||||||
|
if name[0] in grouping_letters:
|
||||||
|
self.regions[region_name][index] = name
|
||||||
|
break
|
53
homeassistant/components/nina/const.py
Normal file
53
homeassistant/components/nina/const.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"""Constants for the Nina integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from logging import Logger, getLogger
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
_LOGGER: Logger = getLogger(__package__)
|
||||||
|
|
||||||
|
SCAN_INTERVAL: timedelta = timedelta(minutes=5)
|
||||||
|
|
||||||
|
DOMAIN: str = "nina"
|
||||||
|
|
||||||
|
CONF_REGIONS: str = "regions"
|
||||||
|
CONF_MESSAGE_SLOTS: str = "slots"
|
||||||
|
CONF_FILTER_CORONA: str = "corona_filter"
|
||||||
|
|
||||||
|
ATTR_HEADLINE: str = "Headline"
|
||||||
|
ATTR_ID: str = "ID"
|
||||||
|
ATTR_SENT: str = "Sent"
|
||||||
|
ATTR_START: str = "Start"
|
||||||
|
ATTR_EXPIRES: str = "Expires"
|
||||||
|
|
||||||
|
CONST_LIST_A_TO_D: list[str] = ["A", "Ä", "B", "C", "D"]
|
||||||
|
CONST_LIST_E_TO_H: list[str] = ["E", "F", "G", "H"]
|
||||||
|
CONST_LIST_I_TO_L: list[str] = ["I", "J", "K", "L"]
|
||||||
|
CONST_LIST_M_TO_Q: list[str] = ["M", "N", "O", "Ö", "P", "Q"]
|
||||||
|
CONST_LIST_R_TO_U: list[str] = ["R", "S", "T", "U", "Ü"]
|
||||||
|
CONST_LIST_V_TO_Z: list[str] = ["V", "W", "X", "Y"]
|
||||||
|
|
||||||
|
CONST_REGION_A_TO_D: Final = "_a_to_d"
|
||||||
|
CONST_REGION_E_TO_H: Final = "_e_to_h"
|
||||||
|
CONST_REGION_I_TO_L: Final = "_i_to_l"
|
||||||
|
CONST_REGION_M_TO_Q: Final = "_m_to_q"
|
||||||
|
CONST_REGION_R_TO_U: Final = "_r_to_u"
|
||||||
|
CONST_REGION_V_TO_Z: Final = "_v_to_z"
|
||||||
|
CONST_REGIONS: Final = [
|
||||||
|
CONST_REGION_A_TO_D,
|
||||||
|
CONST_REGION_E_TO_H,
|
||||||
|
CONST_REGION_I_TO_L,
|
||||||
|
CONST_REGION_M_TO_Q,
|
||||||
|
CONST_REGION_R_TO_U,
|
||||||
|
CONST_REGION_V_TO_Z,
|
||||||
|
]
|
||||||
|
|
||||||
|
CONST_REGION_MAPPING: dict[str, list[str]] = {
|
||||||
|
CONST_REGION_A_TO_D: CONST_LIST_A_TO_D,
|
||||||
|
CONST_REGION_E_TO_H: CONST_LIST_E_TO_H,
|
||||||
|
CONST_REGION_I_TO_L: CONST_LIST_I_TO_L,
|
||||||
|
CONST_REGION_M_TO_Q: CONST_LIST_M_TO_Q,
|
||||||
|
CONST_REGION_R_TO_U: CONST_LIST_R_TO_U,
|
||||||
|
CONST_REGION_V_TO_Z: CONST_LIST_V_TO_Z,
|
||||||
|
}
|
14
homeassistant/components/nina/manifest.json
Normal file
14
homeassistant/components/nina/manifest.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"domain": "nina",
|
||||||
|
"name": "NINA",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/nina",
|
||||||
|
"requirements": [
|
||||||
|
"pynina==0.1.4"
|
||||||
|
],
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": [
|
||||||
|
"@DeerMaximum"
|
||||||
|
],
|
||||||
|
"iot_class": "cloud_polling"
|
||||||
|
}
|
27
homeassistant/components/nina/strings.json
Normal file
27
homeassistant/components/nina/strings.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step":{
|
||||||
|
"user": {
|
||||||
|
"title": "Select city/county",
|
||||||
|
"data" : {
|
||||||
|
"_a_to_d": "City/county (A-D)",
|
||||||
|
"_e_to_h": "City/county (E-H)",
|
||||||
|
"_i_to_l": "City/county (I-L)",
|
||||||
|
"_m_to_q": "City/county (M-Q)",
|
||||||
|
"_r_to_u": "City/county (R-U)",
|
||||||
|
"_v_to_z": "City/county (V-Z)",
|
||||||
|
"slots": "Maximum warnings per city/county",
|
||||||
|
"corona_filter": "Remove Corona Warnings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"no_selection": "Please select at least one city/county",
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
homeassistant/components/nina/translations/en.json
Normal file
27
homeassistant/components/nina/translations/en.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"no_selection": "Please select at least one city/county",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"_a_to_d": "City/county (A-D)",
|
||||||
|
"_e_to_h": "City/county (E-H)",
|
||||||
|
"_i_to_l": "City/county (I-L)",
|
||||||
|
"_m_to_q": "City/county (M-Q)",
|
||||||
|
"_r_to_u": "City/county (R-U)",
|
||||||
|
"_v_to_z": "City/county (V-Z)",
|
||||||
|
"corona_filter": "Remove Corona Warnings",
|
||||||
|
"slots": "Maximum warnings per city/county"
|
||||||
|
},
|
||||||
|
"title": "Select city/county"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -202,6 +202,7 @@ FLOWS = [
|
|||||||
"nexia",
|
"nexia",
|
||||||
"nfandroidtv",
|
"nfandroidtv",
|
||||||
"nightscout",
|
"nightscout",
|
||||||
|
"nina",
|
||||||
"nmap_tracker",
|
"nmap_tracker",
|
||||||
"notion",
|
"notion",
|
||||||
"nuheat",
|
"nuheat",
|
||||||
|
@ -1669,6 +1669,9 @@ pynetgear==0.7.0
|
|||||||
# homeassistant.components.netio
|
# homeassistant.components.netio
|
||||||
pynetio==0.1.9.1
|
pynetio==0.1.9.1
|
||||||
|
|
||||||
|
# homeassistant.components.nina
|
||||||
|
pynina==0.1.4
|
||||||
|
|
||||||
# homeassistant.components.nuki
|
# homeassistant.components.nuki
|
||||||
pynuki==1.4.1
|
pynuki==1.4.1
|
||||||
|
|
||||||
|
@ -1021,6 +1021,9 @@ pymysensors==0.22.1
|
|||||||
# homeassistant.components.netgear
|
# homeassistant.components.netgear
|
||||||
pynetgear==0.7.0
|
pynetgear==0.7.0
|
||||||
|
|
||||||
|
# homeassistant.components.nina
|
||||||
|
pynina==0.1.4
|
||||||
|
|
||||||
# homeassistant.components.nuki
|
# homeassistant.components.nuki
|
||||||
pynuki==1.4.1
|
pynuki==1.4.1
|
||||||
|
|
||||||
|
1
tests/components/nina/__init__.py
Normal file
1
tests/components/nina/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Nina integration."""
|
1
tests/components/nina/fixtures/sample_regions.json
Normal file
1
tests/components/nina/fixtures/sample_regions.json
Normal file
File diff suppressed because one or more lines are too long
44
tests/components/nina/fixtures/sample_warnings.json
Normal file
44
tests/components/nina/fixtures/sample_warnings.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "mow.DE-BW-S-SE018-20211102-18-001",
|
||||||
|
"payload": {
|
||||||
|
"version": 1,
|
||||||
|
"type": "ALERT",
|
||||||
|
"id": "mow.DE-BW-S-SE018-20211102-18-001",
|
||||||
|
"hash": "cae97b1c11bde900017305f681904ad5a6e8fd1c841241ced524b83eaa3522f4",
|
||||||
|
"data": {
|
||||||
|
"headline": "Corona-Verordnung des Landes: Warnstufe durch Landesgesundheitsamt ausgerufen",
|
||||||
|
"provider": "MOWAS",
|
||||||
|
"severity": "Minor",
|
||||||
|
"msgType": "Update",
|
||||||
|
"transKeys": {"event": "BBK-EVC-040"},
|
||||||
|
"area": {"type": "ZGEM", "data": "9956+1102,100001"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"i18nTitle": {
|
||||||
|
"de": "Corona-Verordnung des Landes: Warnstufe durch Landesgesundheitsamt ausgerufen"
|
||||||
|
},
|
||||||
|
"sent": "2021-11-02T20:07:16+01:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mow.DE-NW-BN-SE030-20201014-30-000",
|
||||||
|
"payload": {
|
||||||
|
"version": 1,
|
||||||
|
"type": "ALERT",
|
||||||
|
"id": "mow.DE-NW-BN-SE030-20201014-30-000",
|
||||||
|
"hash": "551db820a43be7e4f39283e1dfb71b212cd520c3ee478d44f43519e9c48fde4c",
|
||||||
|
"data": {
|
||||||
|
"headline": "Ausfall Notruf 112",
|
||||||
|
"provider": "MOWAS",
|
||||||
|
"severity": "Minor",
|
||||||
|
"msgType": "Update",
|
||||||
|
"transKeys": {"event": "BBK-EVC-040"},
|
||||||
|
"area": {"type": "ZGEM", "data": "1+11057,100001"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"i18nTitle": {"de": "Ausfall Notruf 112"},
|
||||||
|
"start": "2021-11-01T05:20:00+01:00",
|
||||||
|
"sent": "2021-10-11T05:20:00+01:00",
|
||||||
|
"expires": "3021-11-22T05:19:00+01:00"
|
||||||
|
}
|
||||||
|
]
|
235
tests/components/nina/test_binary_sensor.py
Normal file
235
tests/components/nina/test_binary_sensor.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
"""Test the Nina binary sensor."""
|
||||||
|
import json
|
||||||
|
from typing import Any, Dict
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pynina import ApiError
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
|
from homeassistant.components.nina.const import (
|
||||||
|
ATTR_EXPIRES,
|
||||||
|
ATTR_HEADLINE,
|
||||||
|
ATTR_ID,
|
||||||
|
ATTR_SENT,
|
||||||
|
ATTR_START,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, load_fixture
|
||||||
|
|
||||||
|
ENTRY_DATA: Dict[str, Any] = {
|
||||||
|
"slots": 5,
|
||||||
|
"corona_filter": True,
|
||||||
|
"regions": {"083350000000": "Aach, Stadt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
ENTRY_DATA_NO_CORONA: Dict[str, Any] = {
|
||||||
|
"slots": 5,
|
||||||
|
"corona_filter": False,
|
||||||
|
"regions": {"083350000000": "Aach, Stadt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the creation and values of the NINA sensors."""
|
||||||
|
|
||||||
|
dummy_response: Dict[str, Any] = json.loads(
|
||||||
|
load_fixture("sample_warnings.json", "nina")
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
return_value=dummy_response,
|
||||||
|
):
|
||||||
|
|
||||||
|
conf_entry: MockConfigEntry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, title="NINA", data=ENTRY_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
entity_registry: er = er.async_get(hass)
|
||||||
|
conf_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(conf_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert conf_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
state_w1 = hass.states.get("binary_sensor.warning_aach_stadt_1")
|
||||||
|
entry_w1 = entity_registry.async_get("binary_sensor.warning_aach_stadt_1")
|
||||||
|
|
||||||
|
assert state_w1.state == STATE_ON
|
||||||
|
assert state_w1.attributes.get(ATTR_HEADLINE) == "Ausfall Notruf 112"
|
||||||
|
assert state_w1.attributes.get(ATTR_ID) == "mow.DE-NW-BN-SE030-20201014-30-000"
|
||||||
|
assert state_w1.attributes.get(ATTR_SENT) == "2021-10-11T05:20:00+01:00"
|
||||||
|
assert state_w1.attributes.get(ATTR_START) == "2021-11-01T05:20:00+01:00"
|
||||||
|
assert state_w1.attributes.get(ATTR_EXPIRES) == "3021-11-22T05:19:00+01:00"
|
||||||
|
|
||||||
|
assert entry_w1.unique_id == "083350000000-1"
|
||||||
|
assert state_w1.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w2 = hass.states.get("binary_sensor.warning_aach_stadt_2")
|
||||||
|
entry_w2 = entity_registry.async_get("binary_sensor.warning_aach_stadt_2")
|
||||||
|
|
||||||
|
assert state_w2.state == STATE_OFF
|
||||||
|
assert state_w2.attributes.get(ATTR_HEADLINE) is None
|
||||||
|
assert state_w2.attributes.get(ATTR_ID) is None
|
||||||
|
assert state_w2.attributes.get(ATTR_SENT) is None
|
||||||
|
assert state_w2.attributes.get(ATTR_START) is None
|
||||||
|
assert state_w2.attributes.get(ATTR_EXPIRES) is None
|
||||||
|
|
||||||
|
assert entry_w2.unique_id == "083350000000-2"
|
||||||
|
assert state_w2.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w3 = hass.states.get("binary_sensor.warning_aach_stadt_3")
|
||||||
|
entry_w3 = entity_registry.async_get("binary_sensor.warning_aach_stadt_3")
|
||||||
|
|
||||||
|
assert state_w3.state == STATE_OFF
|
||||||
|
assert state_w3.attributes.get(ATTR_HEADLINE) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_ID) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_SENT) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_START) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_EXPIRES) is None
|
||||||
|
|
||||||
|
assert entry_w3.unique_id == "083350000000-3"
|
||||||
|
assert state_w3.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w4 = hass.states.get("binary_sensor.warning_aach_stadt_4")
|
||||||
|
entry_w4 = entity_registry.async_get("binary_sensor.warning_aach_stadt_4")
|
||||||
|
|
||||||
|
assert state_w4.state == STATE_OFF
|
||||||
|
assert state_w4.attributes.get(ATTR_HEADLINE) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_ID) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_SENT) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_START) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_EXPIRES) is None
|
||||||
|
|
||||||
|
assert entry_w4.unique_id == "083350000000-4"
|
||||||
|
assert state_w4.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w5 = hass.states.get("binary_sensor.warning_aach_stadt_5")
|
||||||
|
entry_w5 = entity_registry.async_get("binary_sensor.warning_aach_stadt_5")
|
||||||
|
|
||||||
|
assert state_w5.state == STATE_OFF
|
||||||
|
assert state_w5.attributes.get(ATTR_HEADLINE) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_ID) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_SENT) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_START) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_EXPIRES) is None
|
||||||
|
|
||||||
|
assert entry_w5.unique_id == "083350000000-5"
|
||||||
|
assert state_w5.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the creation and values of the NINA sensors without the corona filter."""
|
||||||
|
|
||||||
|
dummy_response: Dict[str, Any] = json.loads(
|
||||||
|
load_fixture("nina/sample_warnings.json")
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
return_value=dummy_response,
|
||||||
|
):
|
||||||
|
|
||||||
|
conf_entry: MockConfigEntry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, title="NINA", data=ENTRY_DATA_NO_CORONA
|
||||||
|
)
|
||||||
|
|
||||||
|
entity_registry: er = er.async_get(hass)
|
||||||
|
conf_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(conf_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert conf_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
state_w1 = hass.states.get("binary_sensor.warning_aach_stadt_1")
|
||||||
|
entry_w1 = entity_registry.async_get("binary_sensor.warning_aach_stadt_1")
|
||||||
|
|
||||||
|
assert state_w1.state == STATE_ON
|
||||||
|
assert (
|
||||||
|
state_w1.attributes.get(ATTR_HEADLINE)
|
||||||
|
== "Corona-Verordnung des Landes: Warnstufe durch Landesgesundheitsamt ausgerufen"
|
||||||
|
)
|
||||||
|
assert state_w1.attributes.get(ATTR_ID) == "mow.DE-BW-S-SE018-20211102-18-001"
|
||||||
|
assert state_w1.attributes.get(ATTR_SENT) == "2021-11-02T20:07:16+01:00"
|
||||||
|
assert state_w1.attributes.get(ATTR_START) == ""
|
||||||
|
assert state_w1.attributes.get(ATTR_EXPIRES) == ""
|
||||||
|
|
||||||
|
assert entry_w1.unique_id == "083350000000-1"
|
||||||
|
assert state_w1.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w2 = hass.states.get("binary_sensor.warning_aach_stadt_2")
|
||||||
|
entry_w2 = entity_registry.async_get("binary_sensor.warning_aach_stadt_2")
|
||||||
|
|
||||||
|
assert state_w2.state == STATE_ON
|
||||||
|
assert state_w2.attributes.get(ATTR_HEADLINE) == "Ausfall Notruf 112"
|
||||||
|
assert state_w2.attributes.get(ATTR_ID) == "mow.DE-NW-BN-SE030-20201014-30-000"
|
||||||
|
assert state_w2.attributes.get(ATTR_SENT) == "2021-10-11T05:20:00+01:00"
|
||||||
|
assert state_w2.attributes.get(ATTR_START) == "2021-11-01T05:20:00+01:00"
|
||||||
|
assert state_w2.attributes.get(ATTR_EXPIRES) == "3021-11-22T05:19:00+01:00"
|
||||||
|
|
||||||
|
assert entry_w2.unique_id == "083350000000-2"
|
||||||
|
assert state_w2.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w3 = hass.states.get("binary_sensor.warning_aach_stadt_3")
|
||||||
|
entry_w3 = entity_registry.async_get("binary_sensor.warning_aach_stadt_3")
|
||||||
|
|
||||||
|
assert state_w3.state == STATE_OFF
|
||||||
|
assert state_w3.attributes.get(ATTR_HEADLINE) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_ID) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_SENT) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_START) is None
|
||||||
|
assert state_w3.attributes.get(ATTR_EXPIRES) is None
|
||||||
|
|
||||||
|
assert entry_w3.unique_id == "083350000000-3"
|
||||||
|
assert state_w3.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w4 = hass.states.get("binary_sensor.warning_aach_stadt_4")
|
||||||
|
entry_w4 = entity_registry.async_get("binary_sensor.warning_aach_stadt_4")
|
||||||
|
|
||||||
|
assert state_w4.state == STATE_OFF
|
||||||
|
assert state_w4.attributes.get(ATTR_HEADLINE) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_ID) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_SENT) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_START) is None
|
||||||
|
assert state_w4.attributes.get(ATTR_EXPIRES) is None
|
||||||
|
|
||||||
|
assert entry_w4.unique_id == "083350000000-4"
|
||||||
|
assert state_w4.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
state_w5 = hass.states.get("binary_sensor.warning_aach_stadt_5")
|
||||||
|
entry_w5 = entity_registry.async_get("binary_sensor.warning_aach_stadt_5")
|
||||||
|
|
||||||
|
assert state_w5.state == STATE_OFF
|
||||||
|
assert state_w5.attributes.get(ATTR_HEADLINE) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_ID) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_SENT) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_START) is None
|
||||||
|
assert state_w5.attributes.get(ATTR_EXPIRES) is None
|
||||||
|
|
||||||
|
assert entry_w5.unique_id == "083350000000-5"
|
||||||
|
assert state_w5.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors_connection_error(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the creation and values of the NINA sensors with no connected."""
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
side_effect=ApiError("Could not connect to Api"),
|
||||||
|
):
|
||||||
|
conf_entry: MockConfigEntry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, title="NINA", data=ENTRY_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
conf_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(conf_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert conf_entry.state == ConfigEntryState.SETUP_RETRY
|
130
tests/components/nina/test_config_flow.py
Normal file
130
tests/components/nina/test_config_flow.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
"""Test the Nina config flow."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pynina import ApiError
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components.nina.const import (
|
||||||
|
CONF_FILTER_CORONA,
|
||||||
|
CONF_MESSAGE_SLOTS,
|
||||||
|
CONST_REGION_A_TO_D,
|
||||||
|
CONST_REGION_E_TO_H,
|
||||||
|
CONST_REGION_I_TO_L,
|
||||||
|
CONST_REGION_M_TO_Q,
|
||||||
|
CONST_REGION_R_TO_U,
|
||||||
|
CONST_REGION_V_TO_Z,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import load_fixture
|
||||||
|
|
||||||
|
DUMMY_DATA: dict[str, Any] = {
|
||||||
|
CONF_MESSAGE_SLOTS: 5,
|
||||||
|
CONST_REGION_A_TO_D: ["095760000000_0", "095760000000_1"],
|
||||||
|
CONST_REGION_E_TO_H: ["010610000000_0", "010610000000_1"],
|
||||||
|
CONST_REGION_I_TO_L: ["071320000000_0", "071320000000_1"],
|
||||||
|
CONST_REGION_M_TO_Q: ["071380000000_0", "071380000000_1"],
|
||||||
|
CONST_REGION_R_TO_U: ["072320000000_0", "072320000000_1"],
|
||||||
|
CONST_REGION_V_TO_Z: ["081270000000_0", "081270000000_1"],
|
||||||
|
CONF_FILTER_CORONA: True,
|
||||||
|
}
|
||||||
|
|
||||||
|
DUMMY_RESPONSE: dict[str, Any] = json.loads(load_fixture("sample_regions.json", "nina"))
|
||||||
|
|
||||||
|
|
||||||
|
async def test_show_set_form(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that the setup form is served."""
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
return_value=DUMMY_RESPONSE,
|
||||||
|
):
|
||||||
|
|
||||||
|
result: dict[str, Any] = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_step_user_connection_error(hass: HomeAssistant) -> None:
|
||||||
|
"""Test starting a flow by user but no connection."""
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
side_effect=ApiError("Could not connect to Api"),
|
||||||
|
):
|
||||||
|
|
||||||
|
result: dict[str, Any] = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}, data=DUMMY_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_step_user_unexpected_exception(hass: HomeAssistant) -> None:
|
||||||
|
"""Test starting a flow by user but with an unexpected exception."""
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
side_effect=Exception("DUMMY"),
|
||||||
|
):
|
||||||
|
|
||||||
|
result: dict[str, Any] = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}, data=DUMMY_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
|
||||||
|
|
||||||
|
async def test_step_user(hass: HomeAssistant) -> None:
|
||||||
|
"""Test starting a flow by user with valid values."""
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
return_value=DUMMY_RESPONSE,
|
||||||
|
):
|
||||||
|
|
||||||
|
result: dict[str, Any] = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}, data=DUMMY_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "NINA"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_step_user_no_selection(hass: HomeAssistant) -> None:
|
||||||
|
"""Test starting a flow by user with no selection."""
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
return_value=DUMMY_RESPONSE,
|
||||||
|
):
|
||||||
|
|
||||||
|
result: dict[str, Any] = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}, data={}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "no_selection"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_step_user_already_configured(hass: HomeAssistant) -> None:
|
||||||
|
"""Test starting a flow by user but it was already configured."""
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
return_value=DUMMY_RESPONSE,
|
||||||
|
):
|
||||||
|
result: dict[str, Any] = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}, data=DUMMY_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}, data=DUMMY_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
46
tests/components/nina/test_init.py
Normal file
46
tests/components/nina/test_init.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"""Test the Nina init file."""
|
||||||
|
import json
|
||||||
|
from typing import Any, Dict
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.components.nina.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, load_fixture
|
||||||
|
|
||||||
|
ENTRY_DATA: Dict[str, Any] = {
|
||||||
|
"slots": 5,
|
||||||
|
"corona_filter": True,
|
||||||
|
"regions": {"083350000000": "Aach, Stadt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def init_integration(hass) -> MockConfigEntry:
|
||||||
|
"""Set up the NINA integration in Home Assistant."""
|
||||||
|
|
||||||
|
dummy_response: Dict[str, Any] = json.loads(
|
||||||
|
load_fixture("sample_warnings.json", "nina")
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"pynina.baseApi.BaseAPI._makeRequest",
|
||||||
|
return_value=dummy_response,
|
||||||
|
):
|
||||||
|
|
||||||
|
entry: MockConfigEntry = MockConfigEntry(
|
||||||
|
domain=DOMAIN, title="NINA", data=ENTRY_DATA
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_entry_not_ready(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the configuration entry."""
|
||||||
|
entry: MockConfigEntry = await init_integration(hass)
|
||||||
|
|
||||||
|
assert entry.state == ConfigEntryState.LOADED
|
Loading…
x
Reference in New Issue
Block a user