mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add FireServiceRota/BrandweerRooster integration (#38206)
Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
1de2554f70
commit
ea52ffc2d9
@ -262,6 +262,9 @@ omit =
|
||||
homeassistant/components/fibaro/*
|
||||
homeassistant/components/filesize/sensor.py
|
||||
homeassistant/components/fints/sensor.py
|
||||
homeassistant/components/fireservicerota/__init__.py
|
||||
homeassistant/components/fireservicerota/const.py
|
||||
homeassistant/components/fireservicerota/sensor.py
|
||||
homeassistant/components/firmata/__init__.py
|
||||
homeassistant/components/firmata/binary_sensor.py
|
||||
homeassistant/components/firmata/board.py
|
||||
|
@ -146,6 +146,7 @@ homeassistant/components/ezviz/* @baqs
|
||||
homeassistant/components/fastdotcom/* @rohankapoorcom
|
||||
homeassistant/components/file/* @fabaff
|
||||
homeassistant/components/filter/* @dgomes
|
||||
homeassistant/components/fireservicerota/* @cyberjunky
|
||||
homeassistant/components/firmata/* @DaAwesomeP
|
||||
homeassistant/components/fixer/* @fabaff
|
||||
homeassistant/components/flick_electric/* @ZephireNZ
|
||||
|
246
homeassistant/components/fireservicerota/__init__.py
Normal file
246
homeassistant/components/fireservicerota/__init__.py
Normal file
@ -0,0 +1,246 @@
|
||||
"""The FireServiceRota integration."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from pyfireservicerota import (
|
||||
ExpiredTokenError,
|
||||
FireServiceRota,
|
||||
FireServiceRotaIncidents,
|
||||
InvalidAuthError,
|
||||
InvalidTokenError,
|
||||
)
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
|
||||
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN, WSS_BWRURL
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SUPPORTED_PLATFORMS = {SENSOR_DOMAIN}
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||
"""Set up the FireServiceRota component."""
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up FireServiceRota from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
coordinator = FireServiceRotaCoordinator(hass, entry)
|
||||
await coordinator.setup()
|
||||
await coordinator.async_availability_update()
|
||||
|
||||
if coordinator.token_refresh_failure:
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
for platform in SUPPORTED_PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload FireServiceRota config entry."""
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id].websocket.stop_listener()
|
||||
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(entry, platform)
|
||||
for platform in SUPPORTED_PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if unload_ok:
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class FireServiceRotaOauth:
|
||||
"""Handle authentication tokens."""
|
||||
|
||||
def __init__(self, hass, entry, fsr):
|
||||
"""Initialize the oauth object."""
|
||||
self._hass = hass
|
||||
self._entry = entry
|
||||
|
||||
self._url = entry.data[CONF_URL]
|
||||
self._username = entry.data[CONF_USERNAME]
|
||||
self._fsr = fsr
|
||||
|
||||
async def async_refresh_tokens(self) -> bool:
|
||||
"""Refresh tokens and update config entry."""
|
||||
_LOGGER.debug("Refreshing authentication tokens after expiration")
|
||||
|
||||
try:
|
||||
token_info = await self._hass.async_add_executor_job(
|
||||
self._fsr.refresh_tokens
|
||||
)
|
||||
|
||||
except (InvalidAuthError, InvalidTokenError):
|
||||
_LOGGER.error("Error refreshing tokens, triggered reauth workflow")
|
||||
self._hass.add_job(
|
||||
self._hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_REAUTH},
|
||||
data={
|
||||
**self._entry.data,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
_LOGGER.debug("Saving new tokens in config entry")
|
||||
self._hass.config_entries.async_update_entry(
|
||||
self._entry,
|
||||
data={
|
||||
"auth_implementation": DOMAIN,
|
||||
CONF_URL: self._url,
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_TOKEN: token_info,
|
||||
},
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class FireServiceRotaWebSocket:
|
||||
"""Define a FireServiceRota websocket manager object."""
|
||||
|
||||
def __init__(self, hass, entry):
|
||||
"""Initialize the websocket object."""
|
||||
self._hass = hass
|
||||
self._entry = entry
|
||||
|
||||
self._fsr_incidents = FireServiceRotaIncidents(on_incident=self._on_incident)
|
||||
self._incident_data = None
|
||||
|
||||
def _construct_url(self) -> str:
|
||||
"""Return URL with latest access token."""
|
||||
return WSS_BWRURL.format(
|
||||
self._entry.data[CONF_URL], self._entry.data[CONF_TOKEN]["access_token"]
|
||||
)
|
||||
|
||||
def incident_data(self) -> object:
|
||||
"""Return incident data."""
|
||||
return self._incident_data
|
||||
|
||||
def _on_incident(self, data) -> None:
|
||||
"""Received new incident, update data."""
|
||||
_LOGGER.debug("Received new incident via websocket: %s", data)
|
||||
self._incident_data = data
|
||||
dispatcher_send(self._hass, f"{DOMAIN}_{self._entry.entry_id}_update")
|
||||
|
||||
def start_listener(self) -> None:
|
||||
"""Start the websocket listener."""
|
||||
_LOGGER.debug("Starting incidents listener")
|
||||
self._fsr_incidents.start(self._construct_url())
|
||||
|
||||
def stop_listener(self) -> None:
|
||||
"""Stop the websocket listener."""
|
||||
_LOGGER.debug("Stopping incidents listener")
|
||||
self._fsr_incidents.stop()
|
||||
|
||||
|
||||
class FireServiceRotaCoordinator(DataUpdateCoordinator):
|
||||
"""Getting the latest data from fireservicerota."""
|
||||
|
||||
def __init__(self, hass, entry):
|
||||
"""Initialize the data object."""
|
||||
self._hass = hass
|
||||
self._entry = entry
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_method=self.async_availability_update,
|
||||
update_interval=MIN_TIME_BETWEEN_UPDATES,
|
||||
)
|
||||
|
||||
self._url = entry.data[CONF_URL]
|
||||
self._tokens = entry.data[CONF_TOKEN]
|
||||
|
||||
self.token_refresh_failure = False
|
||||
self.incident_id = None
|
||||
|
||||
self.fsr = FireServiceRota(base_url=self._url, token_info=self._tokens)
|
||||
|
||||
self.oauth = FireServiceRotaOauth(
|
||||
self._hass,
|
||||
self._entry,
|
||||
self.fsr,
|
||||
)
|
||||
|
||||
self.websocket = FireServiceRotaWebSocket(self._hass, self._entry)
|
||||
|
||||
async def setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
await self._hass.async_add_executor_job(self.websocket.start_listener)
|
||||
|
||||
async def update_call(self, func, *args):
|
||||
"""Perform update call and return data."""
|
||||
if self.token_refresh_failure:
|
||||
return
|
||||
|
||||
try:
|
||||
return await self._hass.async_add_executor_job(func, *args)
|
||||
except (ExpiredTokenError, InvalidTokenError):
|
||||
self.websocket.stop_listener()
|
||||
self.token_refresh_failure = True
|
||||
self.update_interval = None
|
||||
|
||||
if await self.oauth.async_refresh_tokens():
|
||||
self.update_interval = MIN_TIME_BETWEEN_UPDATES
|
||||
self.token_refresh_failure = False
|
||||
self.websocket.start_listener()
|
||||
|
||||
return await self._hass.async_add_executor_job(func, *args)
|
||||
|
||||
async def async_availability_update(self) -> None:
|
||||
"""Get the latest availability data."""
|
||||
_LOGGER.debug("Updating availability data")
|
||||
|
||||
return await self.update_call(
|
||||
self.fsr.get_availability, str(self._hass.config.time_zone)
|
||||
)
|
||||
|
||||
async def async_response_update(self) -> object:
|
||||
"""Get the latest incident response data."""
|
||||
data = self.websocket.incident_data()
|
||||
if data is None or "id" not in data:
|
||||
return
|
||||
|
||||
self.incident_id = data("id")
|
||||
_LOGGER.debug("Updating incident response data for id: %s", self.incident_id)
|
||||
|
||||
return await self.update_call(self.fsr.get_incident_response, self.incident_id)
|
||||
|
||||
async def async_set_response(self, value) -> None:
|
||||
"""Set incident response status."""
|
||||
_LOGGER.debug(
|
||||
"Setting incident response for incident '%s' to status '%s'",
|
||||
self.incident_id,
|
||||
value,
|
||||
)
|
||||
|
||||
await self.update_call(self.fsr.set_incident_response, self.incident_id, value)
|
129
homeassistant/components/fireservicerota/config_flow.py
Normal file
129
homeassistant/components/fireservicerota/config_flow.py
Normal file
@ -0,0 +1,129 @@
|
||||
"""Config flow for FireServiceRota."""
|
||||
from pyfireservicerota import FireServiceRota, InvalidAuthError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_URL, CONF_USERNAME
|
||||
|
||||
from .const import DOMAIN, URL_LIST # pylint: disable=unused-import
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_URL, default="www.brandweerrooster.nl"): vol.In(URL_LIST),
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class FireServiceRotaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a FireServiceRota config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize config flow."""
|
||||
self.api = None
|
||||
self._base_url = None
|
||||
self._username = None
|
||||
self._password = None
|
||||
self._existing_entry = None
|
||||
self._description_placeholders = None
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initiated by the user."""
|
||||
errors = {}
|
||||
|
||||
if user_input is None:
|
||||
return self._show_setup_form(user_input, errors)
|
||||
|
||||
return await self._validate_and_create_entry(user_input, "user")
|
||||
|
||||
async def _validate_and_create_entry(self, user_input, step_id):
|
||||
"""Check if config is valid and create entry if so."""
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
|
||||
extra_inputs = user_input
|
||||
|
||||
if self._existing_entry:
|
||||
extra_inputs = self._existing_entry
|
||||
|
||||
self._username = extra_inputs[CONF_USERNAME]
|
||||
self._base_url = extra_inputs[CONF_URL]
|
||||
|
||||
if self.unique_id is None:
|
||||
await self.async_set_unique_id(self._username)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
try:
|
||||
self.api = FireServiceRota(
|
||||
base_url=self._base_url,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
)
|
||||
token_info = await self.hass.async_add_executor_job(self.api.request_tokens)
|
||||
|
||||
except InvalidAuthError:
|
||||
self.api = None
|
||||
return self.async_show_form(
|
||||
step_id=step_id,
|
||||
data_schema=DATA_SCHEMA,
|
||||
errors={"base": "invalid_auth"},
|
||||
)
|
||||
|
||||
data = {
|
||||
"auth_implementation": DOMAIN,
|
||||
CONF_URL: self._base_url,
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_TOKEN: token_info,
|
||||
}
|
||||
|
||||
if step_id == "user":
|
||||
return self.async_create_entry(title=self._username, data=data)
|
||||
|
||||
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||
if entry.unique_id == self.unique_id:
|
||||
self.hass.config_entries.async_update_entry(entry, data=data)
|
||||
await self.hass.config_entries.async_reload(entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
def _show_setup_form(self, user_input=None, errors=None, step_id="user"):
|
||||
"""Show the setup form to the user."""
|
||||
|
||||
if user_input is None:
|
||||
user_input = {}
|
||||
|
||||
if step_id == "user":
|
||||
schema = {
|
||||
vol.Required(CONF_URL, default="www.brandweerrooster.nl"): vol.In(
|
||||
URL_LIST
|
||||
),
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
else:
|
||||
schema = {vol.Required(CONF_PASSWORD): str}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id=step_id,
|
||||
data_schema=vol.Schema(schema),
|
||||
errors=errors or {},
|
||||
description_placeholders=self._description_placeholders,
|
||||
)
|
||||
|
||||
async def async_step_reauth(self, user_input=None):
|
||||
"""Get new tokens for a config entry that can't authenticate."""
|
||||
|
||||
if not self._existing_entry:
|
||||
await self.async_set_unique_id(user_input[CONF_USERNAME])
|
||||
self._existing_entry = user_input.copy()
|
||||
self._description_placeholders = {"username": user_input[CONF_USERNAME]}
|
||||
user_input = None
|
||||
|
||||
if user_input is None:
|
||||
return self._show_setup_form(step_id=config_entries.SOURCE_REAUTH)
|
||||
|
||||
return await self._validate_and_create_entry(
|
||||
user_input, config_entries.SOURCE_REAUTH
|
||||
)
|
9
homeassistant/components/fireservicerota/const.py
Normal file
9
homeassistant/components/fireservicerota/const.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""Constants for the FireServiceRota integration."""
|
||||
|
||||
DOMAIN = "fireservicerota"
|
||||
|
||||
URL_LIST = {
|
||||
"www.brandweerrooster.nl": "BrandweerRooster",
|
||||
"www.fireservicerota.co.uk": "FireServiceRota",
|
||||
}
|
||||
WSS_BWRURL = "wss://{0}/cable?access_token={1}"
|
8
homeassistant/components/fireservicerota/manifest.json
Normal file
8
homeassistant/components/fireservicerota/manifest.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "fireservicerota",
|
||||
"name": "FireServiceRota",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/fireservicerota",
|
||||
"requirements": ["pyfireservicerota==0.0.40"],
|
||||
"codeowners": ["@cyberjunky"]
|
||||
}
|
128
homeassistant/components/fireservicerota/sensor.py
Normal file
128
homeassistant/components/fireservicerota/sensor.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""Sensor platform for FireServiceRota integration."""
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import DOMAIN as FIRESERVICEROTA_DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||
) -> None:
|
||||
"""Set up FireServiceRota sensor based on a config entry."""
|
||||
coordinator = hass.data[FIRESERVICEROTA_DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities([IncidentsSensor(coordinator)])
|
||||
|
||||
|
||||
class IncidentsSensor(RestoreEntity):
|
||||
"""Representation of FireServiceRota incidents sensor."""
|
||||
|
||||
def __init__(self, coordinator):
|
||||
"""Initialize."""
|
||||
self._coordinator = coordinator
|
||||
self._entry_id = self._coordinator._entry.entry_id
|
||||
self._unique_id = f"{self._coordinator._entry.unique_id}_Incidents"
|
||||
self._state = None
|
||||
self._state_attributes = {}
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return "Incidents"
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the icon to use in the frontend."""
|
||||
if (
|
||||
"prio" in self._state_attributes
|
||||
and self._state_attributes["prio"][0] == "a"
|
||||
):
|
||||
return "mdi:ambulance"
|
||||
|
||||
return "mdi:fire-truck"
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique ID of the sensor."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_state_attributes(self) -> object:
|
||||
"""Return available attributes for sensor."""
|
||||
attr = {}
|
||||
data = self._state_attributes
|
||||
|
||||
if not data:
|
||||
return attr
|
||||
|
||||
for value in (
|
||||
"trigger",
|
||||
"created_at",
|
||||
"message_to_speech_url",
|
||||
"prio",
|
||||
"type",
|
||||
"responder_mode",
|
||||
"can_respond_until",
|
||||
):
|
||||
if data.get(value):
|
||||
attr[value] = data[value]
|
||||
|
||||
if "address" not in data:
|
||||
continue
|
||||
|
||||
for address_value in (
|
||||
"latitude",
|
||||
"longitude",
|
||||
"address_type",
|
||||
"formatted_address",
|
||||
):
|
||||
if address_value in data["address"]:
|
||||
attr[address_value] = data["address"][address_value]
|
||||
|
||||
return attr
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Run when about to be added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
state = await self.async_get_last_state()
|
||||
if state:
|
||||
self._state = state.state
|
||||
self._state_attributes = state.attributes
|
||||
_LOGGER.debug("Restored entity 'Incidents' state to: %s", self._state)
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{FIRESERVICEROTA_DOMAIN}_{self._entry_id}_update",
|
||||
self.coordinator_update,
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
data = self._coordinator.websocket.incident_data()
|
||||
if not data or "body" not in data:
|
||||
return
|
||||
|
||||
self._state = data["body"]
|
||||
self._state_attributes = data
|
||||
self.async_write_ha_state()
|
29
homeassistant/components/fireservicerota/strings.json
Normal file
29
homeassistant/components/fireservicerota/strings.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"url": "Website"
|
||||
}
|
||||
},
|
||||
"reauth": {
|
||||
"description": "Authentication tokens baceame invalid, login to recreate them.",
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "FireServiceRota",
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "Username",
|
||||
"url": "Website"
|
||||
}
|
||||
},
|
||||
"reauth": {
|
||||
"description": "Authentication tokens became invalid, login to recreate them.",
|
||||
"data": {
|
||||
"password": "Password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Invalid authentication."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Account is already configured",
|
||||
"reauth_successful": "Re-authentication was successful"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Successfully authenticated"
|
||||
}
|
||||
}
|
||||
}
|
@ -58,6 +58,7 @@ FLOWS = [
|
||||
"enocean",
|
||||
"epson",
|
||||
"esphome",
|
||||
"fireservicerota",
|
||||
"flick_electric",
|
||||
"flo",
|
||||
"flume",
|
||||
|
@ -1381,6 +1381,9 @@ pyeverlights==0.1.0
|
||||
# homeassistant.components.fido
|
||||
pyfido==2.1.1
|
||||
|
||||
# homeassistant.components.fireservicerota
|
||||
pyfireservicerota==0.0.40
|
||||
|
||||
# homeassistant.components.flexit
|
||||
pyflexit==0.3
|
||||
|
||||
|
@ -684,6 +684,9 @@ pyeverlights==0.1.0
|
||||
# homeassistant.components.fido
|
||||
pyfido==2.1.1
|
||||
|
||||
# homeassistant.components.fireservicerota
|
||||
pyfireservicerota==0.0.40
|
||||
|
||||
# homeassistant.components.flume
|
||||
pyflume==0.5.5
|
||||
|
||||
|
1
tests/components/fireservicerota/__init__.py
Normal file
1
tests/components/fireservicerota/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the FireServiceRota integration."""
|
114
tests/components/fireservicerota/test_config_flow.py
Normal file
114
tests/components/fireservicerota/test_config_flow.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""Test the FireServiceRota config flow."""
|
||||
from pyfireservicerota import InvalidAuthError
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.fireservicerota.const import ( # pylint: disable=unused-import
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||
|
||||
from tests.async_mock import patch
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
MOCK_CONF = {
|
||||
CONF_USERNAME: "my@email.address",
|
||||
CONF_PASSWORD: "mypassw0rd",
|
||||
CONF_URL: "www.brandweerrooster.nl",
|
||||
}
|
||||
|
||||
|
||||
MOCK_DATA = {
|
||||
"auth_implementation": DOMAIN,
|
||||
CONF_URL: MOCK_CONF[CONF_URL],
|
||||
CONF_USERNAME: MOCK_CONF[CONF_USERNAME],
|
||||
"token": {
|
||||
"access_token": "test-access-token",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 1234,
|
||||
"refresh_token": "test-refresh-token",
|
||||
"created_at": 4321,
|
||||
},
|
||||
}
|
||||
|
||||
MOCK_TOKEN_INFO = {
|
||||
"access_token": "test-access-token",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 1234,
|
||||
"refresh_token": "test-refresh-token",
|
||||
"created_at": 4321,
|
||||
}
|
||||
|
||||
|
||||
async def test_show_form(hass):
|
||||
"""Test that the form is served with no input."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
|
||||
async def test_abort_if_already_setup(hass):
|
||||
"""Test abort if already setup."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data=MOCK_CONF, unique_id=MOCK_CONF[CONF_USERNAME]
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_invalid_credentials(hass):
|
||||
"""Test that invalid credentials throws an error."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fireservicerota.FireServiceRota.request_tokens",
|
||||
side_effect=InvalidAuthError,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_step_user(hass):
|
||||
"""Test the start of the config flow."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fireservicerota.config_flow.FireServiceRota"
|
||||
) as MockFireServiceRota, patch(
|
||||
"homeassistant.components.fireservicerota.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.fireservicerota.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
|
||||
mock_fireservicerota = MockFireServiceRota.return_value
|
||||
mock_fireservicerota.request_tokens.return_value = MOCK_TOKEN_INFO
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == MOCK_CONF[CONF_USERNAME]
|
||||
assert result["data"] == {
|
||||
"auth_implementation": "fireservicerota",
|
||||
CONF_URL: "www.brandweerrooster.nl",
|
||||
CONF_USERNAME: "my@email.address",
|
||||
"token": {
|
||||
"access_token": "test-access-token",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 1234,
|
||||
"refresh_token": "test-refresh-token",
|
||||
"created_at": 4321,
|
||||
},
|
||||
}
|
||||
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
Loading…
x
Reference in New Issue
Block a user