parent
a75bad3a83
commit
38f183a683
|
@ -1078,6 +1078,9 @@ omit =
|
|||
homeassistant/components/rova/sensor.py
|
||||
homeassistant/components/rpi_camera/*
|
||||
homeassistant/components/rtorrent/sensor.py
|
||||
homeassistant/components/ruuvi_gateway/__init__.py
|
||||
homeassistant/components/ruuvi_gateway/bluetooth.py
|
||||
homeassistant/components/ruuvi_gateway/coordinator.py
|
||||
homeassistant/components/russound_rio/media_player.py
|
||||
homeassistant/components/russound_rnet/media_player.py
|
||||
homeassistant/components/sabnzbd/__init__.py
|
||||
|
|
|
@ -248,6 +248,7 @@ homeassistant.components.rituals_perfume_genie.*
|
|||
homeassistant.components.roku.*
|
||||
homeassistant.components.rpi_power.*
|
||||
homeassistant.components.rtsp_to_webrtc.*
|
||||
homeassistant.components.ruuvi_gateway.*
|
||||
homeassistant.components.ruuvitag_ble.*
|
||||
homeassistant.components.samsungtv.*
|
||||
homeassistant.components.scene.*
|
||||
|
|
|
@ -979,6 +979,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/rtsp_to_webrtc/ @allenporter
|
||||
/homeassistant/components/ruckus_unleashed/ @gabe565
|
||||
/tests/components/ruckus_unleashed/ @gabe565
|
||||
/homeassistant/components/ruuvi_gateway/ @akx
|
||||
/tests/components/ruuvi_gateway/ @akx
|
||||
/homeassistant/components/ruuvitag_ble/ @akx
|
||||
/tests/components/ruuvitag_ble/ @akx
|
||||
/homeassistant/components/sabnzbd/ @shaiu
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
"""The Ruuvi Gateway integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .bluetooth import async_connect_scanner
|
||||
from .const import DOMAIN, SCAN_INTERVAL
|
||||
from .coordinator import RuuviGatewayUpdateCoordinator
|
||||
from .models import RuuviGatewayRuntimeData
|
||||
|
||||
_LOGGER = logging.getLogger(DOMAIN)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Ruuvi Gateway from a config entry."""
|
||||
coordinator = RuuviGatewayUpdateCoordinator(
|
||||
hass,
|
||||
logger=_LOGGER,
|
||||
name=entry.title,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
host=entry.data[CONF_HOST],
|
||||
token=entry.data[CONF_TOKEN],
|
||||
)
|
||||
scanner, unload_scanner = async_connect_scanner(hass, entry, coordinator)
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = RuuviGatewayRuntimeData(
|
||||
update_coordinator=coordinator,
|
||||
scanner=scanner,
|
||||
)
|
||||
entry.async_on_unload(unload_scanner)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, []):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
|
@ -0,0 +1,103 @@
|
|||
"""Bluetooth support for Ruuvi Gateway."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from home_assistant_bluetooth import BluetoothServiceInfoBleak
|
||||
|
||||
from homeassistant.components.bluetooth import (
|
||||
BaseHaRemoteScanner,
|
||||
async_get_advertisement_callback,
|
||||
async_register_scanner,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
|
||||
from .const import OLD_ADVERTISEMENT_CUTOFF
|
||||
from .coordinator import RuuviGatewayUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RuuviGatewayScanner(BaseHaRemoteScanner):
|
||||
"""Scanner for Ruuvi Gateway."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
scanner_id: str,
|
||||
name: str,
|
||||
new_info_callback: Callable[[BluetoothServiceInfoBleak], None],
|
||||
*,
|
||||
coordinator: RuuviGatewayUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize the scanner, using the given update coordinator as data source."""
|
||||
super().__init__(
|
||||
hass,
|
||||
scanner_id,
|
||||
name,
|
||||
new_info_callback,
|
||||
connector=None,
|
||||
connectable=False,
|
||||
)
|
||||
self.coordinator = coordinator
|
||||
|
||||
@callback
|
||||
def _async_handle_new_data(self) -> None:
|
||||
now = datetime.datetime.now()
|
||||
for tag_data in self.coordinator.data:
|
||||
if now - tag_data.datetime > OLD_ADVERTISEMENT_CUTOFF:
|
||||
# Don't process data that is older than 10 minutes
|
||||
continue
|
||||
anno = tag_data.parse_announcement()
|
||||
self._async_on_advertisement(
|
||||
address=tag_data.mac,
|
||||
rssi=tag_data.rssi,
|
||||
local_name=anno.local_name,
|
||||
service_data=anno.service_data,
|
||||
service_uuids=anno.service_uuids,
|
||||
manufacturer_data=anno.manufacturer_data,
|
||||
tx_power=anno.tx_power,
|
||||
details={},
|
||||
)
|
||||
|
||||
@callback
|
||||
def start_polling(self) -> CALLBACK_TYPE:
|
||||
"""Start polling; return a callback to stop polling."""
|
||||
return self.coordinator.async_add_listener(self._async_handle_new_data)
|
||||
|
||||
|
||||
def async_connect_scanner(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
coordinator: RuuviGatewayUpdateCoordinator,
|
||||
) -> tuple[RuuviGatewayScanner, CALLBACK_TYPE]:
|
||||
"""Connect scanner and start polling."""
|
||||
assert entry.unique_id is not None
|
||||
source = str(entry.unique_id)
|
||||
_LOGGER.debug(
|
||||
"%s [%s]: Connecting scanner",
|
||||
entry.title,
|
||||
source,
|
||||
)
|
||||
scanner = RuuviGatewayScanner(
|
||||
hass=hass,
|
||||
scanner_id=source,
|
||||
name=entry.title,
|
||||
new_info_callback=async_get_advertisement_callback(hass),
|
||||
coordinator=coordinator,
|
||||
)
|
||||
unload_callbacks = [
|
||||
async_register_scanner(hass, scanner, connectable=False),
|
||||
scanner.async_setup(),
|
||||
scanner.start_polling(),
|
||||
]
|
||||
|
||||
@callback
|
||||
def _async_unload() -> None:
|
||||
for unloader in unload_callbacks:
|
||||
unloader()
|
||||
|
||||
return (scanner, _async_unload)
|
|
@ -0,0 +1,89 @@
|
|||
"""Config flow for Ruuvi Gateway integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aioruuvigateway.api as gw_api
|
||||
from aioruuvigateway.excs import CannotConnect, InvalidAuth
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import dhcp
|
||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from . import DOMAIN
|
||||
from .schemata import CONFIG_SCHEMA, get_config_schema_with_default_host
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Ruuvi Gateway."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
super().__init__()
|
||||
self.config_schema = CONFIG_SCHEMA
|
||||
|
||||
async def _async_validate(
|
||||
self,
|
||||
user_input: dict[str, Any],
|
||||
) -> tuple[FlowResult | None, dict[str, str]]:
|
||||
"""Validate configuration (either discovered or user input)."""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
try:
|
||||
async with get_async_client(self.hass) as client:
|
||||
resp = await gw_api.get_gateway_history_data(
|
||||
client,
|
||||
host=user_input[CONF_HOST],
|
||||
bearer_token=user_input[CONF_TOKEN],
|
||||
)
|
||||
await self.async_set_unique_id(
|
||||
format_mac(resp.gw_mac), raise_on_progress=False
|
||||
)
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={CONF_HOST: user_input[CONF_HOST]}
|
||||
)
|
||||
info = {"title": f"Ruuvi Gateway {resp.gw_mac_suffix}"}
|
||||
return (
|
||||
self.async_create_entry(title=info["title"], data=user_input),
|
||||
errors,
|
||||
)
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidAuth:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
return (None, errors)
|
||||
|
||||
async def async_step_user(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> FlowResult:
|
||||
"""Handle requesting or validating user input."""
|
||||
if user_input is not None:
|
||||
result, errors = await self._async_validate(user_input)
|
||||
else:
|
||||
result, errors = None, {}
|
||||
if result is not None:
|
||||
return result
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=self.config_schema,
|
||||
errors=(errors or None),
|
||||
)
|
||||
|
||||
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
|
||||
"""Prepare configuration for a DHCP discovered Ruuvi Gateway."""
|
||||
await self.async_set_unique_id(format_mac(discovery_info.macaddress))
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
|
||||
self.config_schema = get_config_schema_with_default_host(host=discovery_info.ip)
|
||||
return await self.async_step_user()
|
|
@ -0,0 +1,12 @@
|
|||
"""Constants for the Ruuvi Gateway integration."""
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.components.bluetooth import (
|
||||
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
|
||||
)
|
||||
|
||||
DOMAIN = "ruuvi_gateway"
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
OLD_ADVERTISEMENT_CUTOFF = timedelta(
|
||||
seconds=FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
"""Update coordinator for Ruuvi Gateway."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from aioruuvigateway.api import get_gateway_history_data
|
||||
from aioruuvigateway.models import TagData
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
|
||||
class RuuviGatewayUpdateCoordinator(DataUpdateCoordinator[list[TagData]]):
|
||||
"""Polls the gateway for data and returns a list of TagData objects that have changed since the last poll."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
logger: logging.Logger,
|
||||
*,
|
||||
name: str,
|
||||
update_interval: timedelta | None = None,
|
||||
host: str,
|
||||
token: str,
|
||||
) -> None:
|
||||
"""Initialize the coordinator using the given configuration (host, token)."""
|
||||
super().__init__(hass, logger, name=name, update_interval=update_interval)
|
||||
self.host = host
|
||||
self.token = token
|
||||
self.last_tag_datas: dict[str, TagData] = {}
|
||||
|
||||
async def _async_update_data(self) -> list[TagData]:
|
||||
changed_tag_datas: list[TagData] = []
|
||||
async with get_async_client(self.hass) as client:
|
||||
data = await get_gateway_history_data(
|
||||
client,
|
||||
host=self.host,
|
||||
bearer_token=self.token,
|
||||
)
|
||||
for tag in data.tags:
|
||||
if (
|
||||
tag.mac not in self.last_tag_datas
|
||||
or self.last_tag_datas[tag.mac].data != tag.data
|
||||
):
|
||||
changed_tag_datas.append(tag)
|
||||
self.last_tag_datas[tag.mac] = tag
|
||||
return changed_tag_datas
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"domain": "ruuvi_gateway",
|
||||
"name": "Ruuvi Gateway",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/ruuvi_gateway",
|
||||
"codeowners": ["@akx"],
|
||||
"requirements": ["aioruuvigateway==0.0.2"],
|
||||
"iot_class": "local_polling",
|
||||
"dhcp": [
|
||||
{
|
||||
"hostname": "ruuvigateway*"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
"""Models for Ruuvi Gateway integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
|
||||
from .bluetooth import RuuviGatewayScanner
|
||||
from .coordinator import RuuviGatewayUpdateCoordinator
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class RuuviGatewayRuntimeData:
|
||||
"""Runtime data for Ruuvi Gateway integration."""
|
||||
|
||||
update_coordinator: RuuviGatewayUpdateCoordinator
|
||||
scanner: RuuviGatewayScanner
|
|
@ -0,0 +1,18 @@
|
|||
"""Schemata for ruuvi_gateway."""
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Required(CONF_TOKEN): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def get_config_schema_with_default_host(host: str) -> vol.Schema:
|
||||
"""Return a config schema with a default host."""
|
||||
return CONFIG_SCHEMA.extend({vol.Required(CONF_HOST, default=host): str})
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host (IP address or DNS name)",
|
||||
"token": "Bearer token (configured during gateway setup)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Account is already configured"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host (IP address or DNS name)",
|
||||
"token": "Bearer token (configured during gateway setup)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -349,6 +349,7 @@ FLOWS = {
|
|||
"rpi_power",
|
||||
"rtsp_to_webrtc",
|
||||
"ruckus_unleashed",
|
||||
"ruuvi_gateway",
|
||||
"ruuvitag_ble",
|
||||
"sabnzbd",
|
||||
"samsungtv",
|
||||
|
|
|
@ -400,6 +400,10 @@ DHCP: list[dict[str, str | bool]] = [
|
|||
"hostname": "roomba-*",
|
||||
"macaddress": "204EF6*",
|
||||
},
|
||||
{
|
||||
"domain": "ruuvi_gateway",
|
||||
"hostname": "ruuvigateway*",
|
||||
},
|
||||
{
|
||||
"domain": "samsungtv",
|
||||
"registered_devices": True,
|
||||
|
|
|
@ -4569,6 +4569,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ruuvi_gateway": {
|
||||
"name": "Ruuvi Gateway",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"ruuvitag_ble": {
|
||||
"name": "RuuviTag BLE",
|
||||
"integration_type": "hub",
|
||||
|
|
10
mypy.ini
10
mypy.ini
|
@ -2234,6 +2234,16 @@ disallow_untyped_defs = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.ruuvi_gateway.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.ruuvitag_ble.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
|
|
@ -260,6 +260,9 @@ aiorecollect==1.0.8
|
|||
# homeassistant.components.ridwell
|
||||
aioridwell==2022.11.0
|
||||
|
||||
# homeassistant.components.ruuvi_gateway
|
||||
aioruuvigateway==0.0.2
|
||||
|
||||
# homeassistant.components.senseme
|
||||
aiosenseme==0.6.1
|
||||
|
||||
|
|
|
@ -235,6 +235,9 @@ aiorecollect==1.0.8
|
|||
# homeassistant.components.ridwell
|
||||
aioridwell==2022.11.0
|
||||
|
||||
# homeassistant.components.ruuvi_gateway
|
||||
aioruuvigateway==0.0.2
|
||||
|
||||
# homeassistant.components.senseme
|
||||
aiosenseme==0.6.1
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Tests for the Ruuvi Gateway integration."""
|
|
@ -0,0 +1,12 @@
|
|||
"""Constants for ruuvi_gateway tests."""
|
||||
from __future__ import annotations
|
||||
|
||||
ASYNC_SETUP_ENTRY = "homeassistant.components.ruuvi_gateway.async_setup_entry"
|
||||
GET_GATEWAY_HISTORY_DATA = "aioruuvigateway.api.get_gateway_history_data"
|
||||
EXPECTED_TITLE = "Ruuvi Gateway EE:FF"
|
||||
BASE_DATA = {
|
||||
"host": "1.1.1.1",
|
||||
"token": "toktok",
|
||||
}
|
||||
GATEWAY_MAC = "AA:BB:CC:DD:EE:FF"
|
||||
GATEWAY_MAC_LOWER = GATEWAY_MAC.lower()
|
|
@ -0,0 +1,154 @@
|
|||
"""Test the Ruuvi Gateway config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from aioruuvigateway.excs import CannotConnect, InvalidAuth
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import dhcp
|
||||
from homeassistant.components.ruuvi_gateway.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from .consts import (
|
||||
BASE_DATA,
|
||||
EXPECTED_TITLE,
|
||||
GATEWAY_MAC_LOWER,
|
||||
GET_GATEWAY_HISTORY_DATA,
|
||||
)
|
||||
from .utils import patch_gateway_ok, patch_setup_entry_ok
|
||||
|
||||
DHCP_IP = "1.2.3.4"
|
||||
DHCP_DATA = {**BASE_DATA, "host": DHCP_IP}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"init_data, init_context, entry",
|
||||
[
|
||||
(
|
||||
None,
|
||||
{"source": config_entries.SOURCE_USER},
|
||||
BASE_DATA,
|
||||
),
|
||||
(
|
||||
dhcp.DhcpServiceInfo(
|
||||
hostname="RuuviGateway1234",
|
||||
ip=DHCP_IP,
|
||||
macaddress="12:34:56:78:90:ab",
|
||||
),
|
||||
{"source": config_entries.SOURCE_DHCP},
|
||||
DHCP_DATA,
|
||||
),
|
||||
],
|
||||
ids=["user", "dhcp"],
|
||||
)
|
||||
async def test_ok_setup(hass: HomeAssistant, init_data, init_context, entry) -> None:
|
||||
"""Test we get the form."""
|
||||
init_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
data=init_data,
|
||||
context=init_context,
|
||||
)
|
||||
assert init_result["type"] == FlowResultType.FORM
|
||||
assert init_result["step_id"] == config_entries.SOURCE_USER
|
||||
assert init_result["errors"] is None
|
||||
|
||||
# Check that we can finalize setup
|
||||
with patch_gateway_ok(), patch_setup_entry_ok() as mock_setup_entry:
|
||||
config_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
entry,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert config_result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert config_result["title"] == EXPECTED_TITLE
|
||||
assert config_result["data"] == entry
|
||||
assert config_result["context"]["unique_id"] == GATEWAY_MAC_LOWER
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||
"""Test we handle invalid auth."""
|
||||
init_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(GET_GATEWAY_HISTORY_DATA, side_effect=InvalidAuth):
|
||||
config_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
BASE_DATA,
|
||||
)
|
||||
|
||||
assert config_result["type"] == FlowResultType.FORM
|
||||
assert config_result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
# Check that we still can finalize setup
|
||||
with patch_gateway_ok(), patch_setup_entry_ok() as mock_setup_entry:
|
||||
config_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
BASE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert config_result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert config_result["title"] == EXPECTED_TITLE
|
||||
assert config_result["data"] == BASE_DATA
|
||||
assert config_result["context"]["unique_id"] == GATEWAY_MAC_LOWER
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
init_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(GET_GATEWAY_HISTORY_DATA, side_effect=CannotConnect):
|
||||
config_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
BASE_DATA,
|
||||
)
|
||||
|
||||
assert config_result["type"] == FlowResultType.FORM
|
||||
assert config_result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
# Check that we still can finalize setup
|
||||
with patch_gateway_ok(), patch_setup_entry_ok() as mock_setup_entry:
|
||||
config_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
BASE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert config_result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert config_result["title"] == EXPECTED_TITLE
|
||||
assert config_result["data"] == BASE_DATA
|
||||
assert config_result["context"]["unique_id"] == GATEWAY_MAC_LOWER
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_unexpected(hass: HomeAssistant) -> None:
|
||||
"""Test we handle unexpected errors."""
|
||||
init_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(GET_GATEWAY_HISTORY_DATA, side_effect=MemoryError):
|
||||
config_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
BASE_DATA,
|
||||
)
|
||||
|
||||
assert config_result["type"] == FlowResultType.FORM
|
||||
assert config_result["errors"] == {"base": "unknown"}
|
||||
|
||||
# Check that we still can finalize setup
|
||||
with patch_gateway_ok(), patch_setup_entry_ok() as mock_setup_entry:
|
||||
config_result = await hass.config_entries.flow.async_configure(
|
||||
init_result["flow_id"],
|
||||
BASE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert config_result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert config_result["title"] == EXPECTED_TITLE
|
||||
assert config_result["data"] == BASE_DATA
|
||||
assert config_result["context"]["unique_id"] == GATEWAY_MAC_LOWER
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
|
@ -0,0 +1,30 @@
|
|||
"""Utilities for ruuvi_gateway tests."""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from unittest.mock import _patch, patch
|
||||
|
||||
from aioruuvigateway.models import HistoryResponse
|
||||
|
||||
from tests.components.ruuvi_gateway.consts import (
|
||||
ASYNC_SETUP_ENTRY,
|
||||
GATEWAY_MAC,
|
||||
GET_GATEWAY_HISTORY_DATA,
|
||||
)
|
||||
|
||||
|
||||
def patch_gateway_ok() -> _patch:
|
||||
"""Patch gateway function to return valid data."""
|
||||
return patch(
|
||||
GET_GATEWAY_HISTORY_DATA,
|
||||
return_value=HistoryResponse(
|
||||
timestamp=int(time.time()),
|
||||
gw_mac=GATEWAY_MAC,
|
||||
tags=[],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def patch_setup_entry_ok() -> _patch:
|
||||
"""Patch setup entry to return True."""
|
||||
return patch(ASYNC_SETUP_ENTRY, return_value=True)
|
Loading…
Reference in New Issue