Add config flow to Streamlabs water (#104962)

* Add config flow to Streamlabs water

* Add config flow to Streamlabs water

* Add config flow to Streamlabs water

* Add issue when import is successful

* Remove import issue when entry already exists

* Remove import issue when entry already exists

* Fix feedback

* Fix feedback

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
pull/95594/head^2
Joost Lekkerkerker 2023-12-26 22:24:28 +01:00 committed by GitHub
parent c7b4f8f780
commit 01ded7daea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 530 additions and 179 deletions

View File

@ -1243,7 +1243,10 @@ omit =
homeassistant/components/stream/fmp4utils.py
homeassistant/components/stream/hls.py
homeassistant/components/stream/worker.py
homeassistant/components/streamlabswater/*
homeassistant/components/streamlabswater/__init__.py
homeassistant/components/streamlabswater/binary_sensor.py
homeassistant/components/streamlabswater/coordinator.py
homeassistant/components/streamlabswater/sensor.py
homeassistant/components/suez_water/__init__.py
homeassistant/components/suez_water/sensor.py
homeassistant/components/supervisord/sensor.py

View File

@ -1,28 +1,31 @@
"""Support for Streamlabs Water Monitor devices."""
import logging
from streamlabswater import streamlabswater
from streamlabswater.streamlabswater import StreamlabsClient
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import discovery
from homeassistant.core import (
DOMAIN as HOMEASSISTANT_DOMAIN,
HomeAssistant,
ServiceCall,
)
from homeassistant.data_entry_flow import FlowResultType
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
DOMAIN = "streamlabswater"
_LOGGER = logging.getLogger(__name__)
from .const import DOMAIN
from .coordinator import StreamlabsCoordinator
ATTR_AWAY_MODE = "away_mode"
SERVICE_SET_AWAY_MODE = "set_away_mode"
AWAY_MODE_AWAY = "away"
AWAY_MODE_HOME = "home"
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR]
CONF_LOCATION_ID = "location_id"
ISSUE_PLACEHOLDER = {"url": "/config/integrations/dashboard/add?domain=streamlabswater"}
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
@ -36,52 +39,77 @@ CONFIG_SCHEMA = vol.Schema(
)
SET_AWAY_MODE_SCHEMA = vol.Schema(
{vol.Required(ATTR_AWAY_MODE): vol.In([AWAY_MODE_AWAY, AWAY_MODE_HOME])}
{
vol.Required(ATTR_AWAY_MODE): vol.In([AWAY_MODE_AWAY, AWAY_MODE_HOME]),
vol.Optional(CONF_LOCATION_ID): cv.string,
}
)
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the streamlabs water integration."""
conf = config[DOMAIN]
api_key = conf.get(CONF_API_KEY)
location_id = conf.get(CONF_LOCATION_ID)
if DOMAIN not in config:
return True
client = streamlabswater.StreamlabsClient(api_key)
locations = client.get_locations().get("locations")
if locations is None:
_LOGGER.error("Unable to retrieve locations. Verify API key")
return False
if location_id is None:
location = locations[0]
location_id = location["locationId"]
_LOGGER.info(
"Streamlabs Water Monitor auto-detected location_id=%s", location_id
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_API_KEY: config[DOMAIN][CONF_API_KEY]},
)
if (
result["type"] == FlowResultType.CREATE_ENTRY
or result["reason"] == "already_configured"
):
async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.7.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "StreamLabs",
},
)
else:
location = next(
(loc for loc in locations if location_id == loc["locationId"]), None
async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_import_issue_${result['reason']}",
breaks_in_ha_version="2024.7.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key=f"deprecated_yaml_import_issue_${result['reason']}",
translation_placeholders=ISSUE_PLACEHOLDER,
)
if location is None:
_LOGGER.error("Supplied location_id is invalid")
return False
return True
location_name = location["name"]
hass.data[DOMAIN] = {
"client": client,
"location_id": location_id,
"location_name": location_name,
}
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up StreamLabs from a config entry."""
for platform in PLATFORMS:
discovery.load_platform(hass, platform, DOMAIN, {}, config)
api_key = entry.data[CONF_API_KEY]
client = StreamlabsClient(api_key)
coordinator = StreamlabsCoordinator(hass, client)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
def set_away_mode(service: ServiceCall) -> None:
"""Set the StreamLabsWater Away Mode."""
away_mode = service.data.get(ATTR_AWAY_MODE)
location_id = (
service.data.get(CONF_LOCATION_ID) or list(coordinator.data.values())[0]
)
client.update_location(location_id, away_mode)
hass.services.register(
@ -89,3 +117,11 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
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, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -1,84 +1,54 @@
"""Support for Streamlabs Water Monitor Away Mode."""
from __future__ import annotations
from datetime import timedelta
from streamlabswater.streamlabswater import StreamlabsClient
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import DOMAIN as STREAMLABSWATER_DOMAIN
from . import StreamlabsCoordinator
from .const import DOMAIN
from .coordinator import StreamlabsData
DEPENDS = ["streamlabswater"]
MIN_TIME_BETWEEN_LOCATION_UPDATES = timedelta(seconds=60)
ATTR_LOCATION_ID = "location_id"
NAME_AWAY_MODE = "Water Away Mode"
def setup_platform(
async def async_setup_entry(
hass: HomeAssistant,
config: ConfigType,
add_devices: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the StreamLabsWater mode sensor."""
client = hass.data[STREAMLABSWATER_DOMAIN]["client"]
location_id = hass.data[STREAMLABSWATER_DOMAIN]["location_id"]
location_name = hass.data[STREAMLABSWATER_DOMAIN]["location_name"]
"""Set up Streamlabs water binary sensor from a config entry."""
coordinator = hass.data[DOMAIN][entry.entry_id]
streamlabs_location_data = StreamlabsLocationData(location_id, client)
streamlabs_location_data.update()
entities = []
add_devices([StreamlabsAwayMode(location_name, streamlabs_location_data)])
for location_id in coordinator.data:
entities.append(StreamlabsAwayMode(coordinator, location_id))
async_add_entities(entities)
class StreamlabsLocationData:
"""Track and query location data."""
def __init__(self, location_id: str, client: StreamlabsClient) -> None:
"""Initialize the location data."""
self._location_id = location_id
self._client = client
self._is_away = None
@Throttle(MIN_TIME_BETWEEN_LOCATION_UPDATES)
def update(self) -> None:
"""Query and store location data."""
location = self._client.get_location(self._location_id)
self._is_away = location["homeAway"] == "away"
def is_away(self) -> bool | None:
"""Return whether away more is enabled."""
return self._is_away
class StreamlabsAwayMode(BinarySensorEntity):
class StreamlabsAwayMode(CoordinatorEntity[StreamlabsCoordinator], BinarySensorEntity):
"""Monitor the away mode state."""
def __init__(
self, location_name: str, streamlabs_location_data: StreamlabsLocationData
) -> None:
def __init__(self, coordinator: StreamlabsCoordinator, location_id: str) -> None:
"""Initialize the away mode device."""
self._location_name = location_name
self._streamlabs_location_data = streamlabs_location_data
self._is_away = None
super().__init__(coordinator)
self._location_id = location_id
@property
def location_data(self) -> StreamlabsData:
"""Returns the data object."""
return self.coordinator.data[self._location_id]
@property
def name(self) -> str:
"""Return the name for away mode."""
return f"{self._location_name} {NAME_AWAY_MODE}"
return f"{self.location_data.name} {NAME_AWAY_MODE}"
@property
def is_on(self) -> bool | None:
def is_on(self) -> bool:
"""Return if away mode is on."""
return self._streamlabs_location_data.is_away()
def update(self) -> None:
"""Retrieve the latest location data and away mode state."""
self._streamlabs_location_data.update()
return self.location_data.is_away

View File

@ -0,0 +1,75 @@
"""Config flow for StreamLabs integration."""
from __future__ import annotations
from typing import Any
from streamlabswater.streamlabswater import StreamlabsClient
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from .const import DOMAIN, LOGGER
async def validate_input(hass: HomeAssistant, api_key: str) -> None:
"""Validate the user input allows us to connect."""
client = StreamlabsClient(api_key)
response = await hass.async_add_executor_job(client.get_locations)
locations = response.get("locations")
if locations is None:
raise CannotConnect
class StreamlabsConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for StreamLabs."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
self._async_abort_entries_match(user_input)
try:
await validate_input(self.hass, user_input[CONF_API_KEY])
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title="Streamlabs", data=user_input)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_API_KEY): str,
}
),
errors=errors,
)
async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
"""Import the yaml config."""
self._async_abort_entries_match(user_input)
try:
await validate_input(self.hass, user_input[CONF_API_KEY])
except CannotConnect:
return self.async_abort(reason="cannot_connect")
except Exception: # pylint: disable=broad-except
LOGGER.exception("Unexpected exception")
return self.async_abort(reason="unknown")
return self.async_create_entry(title="Streamlabs", data=user_input)
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""

View File

@ -0,0 +1,6 @@
"""Constants for the StreamLabs integration."""
import logging
DOMAIN = "streamlabswater"
LOGGER = logging.getLogger(__package__)

View File

@ -0,0 +1,57 @@
"""Coordinator for Streamlabs water integration."""
from dataclasses import dataclass
from datetime import timedelta
from streamlabswater.streamlabswater import StreamlabsClient
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import LOGGER
@dataclass(slots=True)
class StreamlabsData:
"""Class to hold Streamlabs data."""
is_away: bool
name: str
daily_usage: float
monthly_usage: float
yearly_usage: float
class StreamlabsCoordinator(DataUpdateCoordinator[dict[str, StreamlabsData]]):
"""Coordinator for Streamlabs."""
def __init__(
self,
hass: HomeAssistant,
client: StreamlabsClient,
) -> None:
"""Coordinator for Streamlabs."""
super().__init__(
hass,
LOGGER,
name="Streamlabs",
update_interval=timedelta(seconds=60),
)
self.client = client
async def _async_update_data(self) -> dict[str, StreamlabsData]:
return await self.hass.async_add_executor_job(self._update_data)
def _update_data(self) -> dict[str, StreamlabsData]:
locations = self.client.get_locations()
res = {}
for location in locations:
location_id = location["locationId"]
water_usage = self.client.get_water_usage_summary(location_id)
res[location_id] = StreamlabsData(
is_away=location["homeAway"] == "away",
name=location["name"],
daily_usage=round(water_usage["today"], 1),
monthly_usage=round(water_usage["thisMonth"], 1),
yearly_usage=round(water_usage["thisYear"], 1),
)
return res

View File

@ -2,6 +2,7 @@
"domain": "streamlabswater",
"name": "StreamLabs",
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/streamlabswater",
"iot_class": "cloud_polling",
"loggers": ["streamlabswater"],

View File

@ -1,111 +1,69 @@
"""Support for Streamlabs Water Monitor Usage."""
from __future__ import annotations
from datetime import timedelta
from streamlabswater.streamlabswater import StreamlabsClient
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfVolume
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import DOMAIN as STREAMLABSWATER_DOMAIN
DEPENDENCIES = ["streamlabswater"]
WATER_ICON = "mdi:water"
MIN_TIME_BETWEEN_USAGE_UPDATES = timedelta(seconds=60)
from . import StreamlabsCoordinator
from .const import DOMAIN
from .coordinator import StreamlabsData
NAME_DAILY_USAGE = "Daily Water"
NAME_MONTHLY_USAGE = "Monthly Water"
NAME_YEARLY_USAGE = "Yearly Water"
def setup_platform(
async def async_setup_entry(
hass: HomeAssistant,
config: ConfigType,
add_devices: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up water usage sensors."""
client = hass.data[STREAMLABSWATER_DOMAIN]["client"]
location_id = hass.data[STREAMLABSWATER_DOMAIN]["location_id"]
location_name = hass.data[STREAMLABSWATER_DOMAIN]["location_name"]
"""Set up Streamlabs water sensor from a config entry."""
coordinator = hass.data[DOMAIN][entry.entry_id]
streamlabs_usage_data = StreamlabsUsageData(location_id, client)
streamlabs_usage_data.update()
entities = []
add_devices(
[
StreamLabsDailyUsage(location_name, streamlabs_usage_data),
StreamLabsMonthlyUsage(location_name, streamlabs_usage_data),
StreamLabsYearlyUsage(location_name, streamlabs_usage_data),
]
)
for location_id in coordinator.data.values():
entities.extend(
[
StreamLabsDailyUsage(coordinator, location_id),
StreamLabsMonthlyUsage(coordinator, location_id),
StreamLabsYearlyUsage(coordinator, location_id),
]
)
async_add_entities(entities)
class StreamlabsUsageData:
"""Track and query usage data."""
def __init__(self, location_id: str, client: StreamlabsClient) -> None:
"""Initialize the usage data."""
self._location_id = location_id
self._client = client
self._today = None
self._this_month = None
self._this_year = None
@Throttle(MIN_TIME_BETWEEN_USAGE_UPDATES)
def update(self) -> None:
"""Query and store usage data."""
water_usage = self._client.get_water_usage_summary(self._location_id)
self._today = round(water_usage["today"], 1)
self._this_month = round(water_usage["thisMonth"], 1)
self._this_year = round(water_usage["thisYear"], 1)
def get_daily_usage(self) -> float | None:
"""Return the day's usage."""
return self._today
def get_monthly_usage(self) -> float | None:
"""Return the month's usage."""
return self._this_month
def get_yearly_usage(self) -> float | None:
"""Return the year's usage."""
return self._this_year
class StreamLabsDailyUsage(SensorEntity):
class StreamLabsDailyUsage(CoordinatorEntity[StreamlabsCoordinator], SensorEntity):
"""Monitors the daily water usage."""
_attr_device_class = SensorDeviceClass.WATER
_attr_native_unit_of_measurement = UnitOfVolume.GALLONS
def __init__(
self, location_name: str, streamlabs_usage_data: StreamlabsUsageData
) -> None:
def __init__(self, coordinator: StreamlabsCoordinator, location_id: str) -> None:
"""Initialize the daily water usage device."""
self._location_name = location_name
self._streamlabs_usage_data = streamlabs_usage_data
self._state = None
super().__init__(coordinator)
self._location_id = location_id
@property
def location_data(self) -> StreamlabsData:
"""Returns the data object."""
return self.coordinator.data[self._location_id]
@property
def name(self) -> str:
"""Return the name for daily usage."""
return f"{self._location_name} {NAME_DAILY_USAGE}"
return f"{self.location_data.name} {NAME_DAILY_USAGE}"
@property
def native_value(self) -> float | None:
def native_value(self) -> float:
"""Return the current daily usage."""
return self._streamlabs_usage_data.get_daily_usage()
def update(self) -> None:
"""Retrieve the latest daily usage."""
self._streamlabs_usage_data.update()
return self.location_data.daily_usage
class StreamLabsMonthlyUsage(StreamLabsDailyUsage):
@ -114,12 +72,12 @@ class StreamLabsMonthlyUsage(StreamLabsDailyUsage):
@property
def name(self) -> str:
"""Return the name for monthly usage."""
return f"{self._location_name} {NAME_MONTHLY_USAGE}"
return f"{self.location_data.name} {NAME_MONTHLY_USAGE}"
@property
def native_value(self) -> float | None:
def native_value(self) -> float:
"""Return the current monthly usage."""
return self._streamlabs_usage_data.get_monthly_usage()
return self.location_data.monthly_usage
class StreamLabsYearlyUsage(StreamLabsDailyUsage):
@ -128,9 +86,9 @@ class StreamLabsYearlyUsage(StreamLabsDailyUsage):
@property
def name(self) -> str:
"""Return the name for yearly usage."""
return f"{self._location_name} {NAME_YEARLY_USAGE}"
return f"{self.location_data.name} {NAME_YEARLY_USAGE}"
@property
def native_value(self) -> float | None:
def native_value(self) -> float:
"""Return the current yearly usage."""
return self._streamlabs_usage_data.get_yearly_usage()
return self.location_data.yearly_usage

View File

@ -7,3 +7,6 @@ set_away_mode:
options:
- "away"
- "home"
location_id:
selector:
text:

View File

@ -1,4 +1,20 @@
{
"config": {
"step": {
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"services": {
"set_away_mode": {
"name": "Set away mode",
@ -7,8 +23,22 @@
"away_mode": {
"name": "Away mode",
"description": "Home or away."
},
"location_id": {
"name": "Location ID",
"description": "The location ID of the Streamlabs Water Monitor."
}
}
}
},
"issues": {
"deprecated_yaml_import_issue_cannot_connect": {
"title": "The Streamlabs water YAML configuration import failed",
"description": "Configuring Streamlabs water using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to Streamlabs water works and restart Home Assistant to try again or remove the Streamlabs water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
},
"deprecated_yaml_import_issue_unknown": {
"title": "The Streamlabs water YAML configuration import failed",
"description": "Configuring Streamlabs water using YAML is being removed but there was an unknown error when trying to import the YAML configuration.\n\nEnsure the imported configuration is correct and remove the Streamlabs water YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
}
}
}

View File

@ -476,6 +476,7 @@ FLOWS = {
"steamist",
"stookalert",
"stookwijzer",
"streamlabswater",
"subaru",
"suez_water",
"sun",

View File

@ -5585,7 +5585,7 @@
"streamlabswater": {
"name": "StreamLabs",
"integration_type": "hub",
"config_flow": false,
"config_flow": true,
"iot_class": "cloud_polling"
},
"subaru": {

View File

@ -1930,6 +1930,9 @@ stookalert==0.1.4
# homeassistant.components.stookwijzer
stookwijzer==1.3.0
# homeassistant.components.streamlabswater
streamlabswater==1.0.1
# homeassistant.components.huawei_lte
# homeassistant.components.solaredge
# homeassistant.components.thermoworks_smoke

View File

@ -0,0 +1 @@
"""Tests for the StreamLabs integration."""

View File

@ -0,0 +1,14 @@
"""Common fixtures for the StreamLabs tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.streamlabswater.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@ -0,0 +1,193 @@
"""Test the StreamLabs config flow."""
from unittest.mock import AsyncMock, patch
from homeassistant import config_entries
from homeassistant.components.streamlabswater.const import DOMAIN
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "abc"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Streamlabs"
assert result["data"] == {CONF_API_KEY: "abc"}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_cannot_connect(
hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
return_value={},
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "abc"},
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"}
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "abc"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Streamlabs"
assert result["data"] == {CONF_API_KEY: "abc"}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_unknown(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we handle unknown error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
side_effect=Exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "abc"},
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "unknown"}
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "abc"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Streamlabs"
assert result["data"] == {CONF_API_KEY: "abc"}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_entry_already_exists(hass: HomeAssistant) -> None:
"""Test we handle if the entry already exists."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_API_KEY: "abc"},
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
side_effect=Exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_API_KEY: "abc"},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_import(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test import flow."""
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_API_KEY: "abc"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Streamlabs"
assert result["data"] == {CONF_API_KEY: "abc"}
assert len(mock_setup_entry.mock_calls) == 1
async def test_import_cannot_connect(
hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test we handle cannot connect error."""
with patch(
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
return_value={},
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_API_KEY: "abc"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "cannot_connect"
async def test_import_unknown(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we handle unknown error."""
with patch(
"homeassistant.components.streamlabswater.config_flow.StreamlabsClient.get_locations",
side_effect=Exception,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_API_KEY: "abc"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "unknown"
async def test_import_entry_already_exists(hass: HomeAssistant) -> None:
"""Test we handle if the entry already exists."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_API_KEY: "abc"},
)
entry.add_to_hass(hass)
with patch("homeassistant.components.streamlabswater.config_flow.StreamlabsClient"):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_API_KEY: "abc"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"