mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
WeatherFlow Forecast (REST API) (#106615)
* rebase off dev * Update homeassistant/components/weatherflow_cloud/const.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Addressing 1st round of PR Comments * Update homeassistant/components/weatherflow_cloud/config_flow.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * addressing PR Comments * fixing last comment that i can see * Update homeassistant/components/weatherflow_cloud/coordinator.py OOPS Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/weatherflow_cloud/weather.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/weatherflow_cloud/coordinator.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * switching to station id * Update homeassistant/components/weatherflow_cloud/strings.json Co-authored-by: G Johansson <goran.johansson@shiftit.se> * addressing PR * Updated tests to be better * Updated tests accordingly * REAuth flow and tests added * Update homeassistant/components/weatherflow_cloud/strings.json Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Update homeassistant/components/weatherflow_cloud/coordinator.py Co-authored-by: G Johansson <goran.johansson@shiftit.se> * Addressing PR comments * Apply suggestions from code review * ruff fix --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
parent
b6393cc3a0
commit
bc20e7900c
@ -1582,6 +1582,10 @@ omit =
|
||||
homeassistant/components/weatherflow/__init__.py
|
||||
homeassistant/components/weatherflow/const.py
|
||||
homeassistant/components/weatherflow/sensor.py
|
||||
homeassistant/components/weatherflow_cloud/__init__.py
|
||||
homeassistant/components/weatherflow_cloud/const.py
|
||||
homeassistant/components/weatherflow_cloud/coordinator.py
|
||||
homeassistant/components/weatherflow_cloud/weather.py
|
||||
homeassistant/components/webmin/sensor.py
|
||||
homeassistant/components/wiffi/__init__.py
|
||||
homeassistant/components/wiffi/binary_sensor.py
|
||||
|
@ -1509,6 +1509,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/weather/ @home-assistant/core
|
||||
/homeassistant/components/weatherflow/ @natekspencer @jeeftor
|
||||
/tests/components/weatherflow/ @natekspencer @jeeftor
|
||||
/homeassistant/components/weatherflow_cloud/ @jeeftor
|
||||
/tests/components/weatherflow_cloud/ @jeeftor
|
||||
/homeassistant/components/weatherkit/ @tjhorner
|
||||
/tests/components/weatherkit/ @tjhorner
|
||||
/homeassistant/components/webhook/ @home-assistant/core
|
||||
|
34
homeassistant/components/weatherflow_cloud/__init__.py
Normal file
34
homeassistant/components/weatherflow_cloud/__init__.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""The WeatherflowCloud integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import WeatherFlowCloudDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.WEATHER]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up WeatherFlowCloud from a config entry."""
|
||||
|
||||
data_coordinator = WeatherFlowCloudDataUpdateCoordinator(
|
||||
hass=hass,
|
||||
api_token=entry.data[CONF_API_TOKEN],
|
||||
)
|
||||
await data_coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
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
|
79
homeassistant/components/weatherflow_cloud/config_flow.py
Normal file
79
homeassistant/components/weatherflow_cloud/config_flow.py
Normal file
@ -0,0 +1,79 @@
|
||||
"""Config flow for WeatherflowCloud integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
import voluptuous as vol
|
||||
from weatherflow4py.api import WeatherFlowRestAPI
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_API_TOKEN
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
async def _validate_api_token(api_token: str) -> dict[str, Any]:
|
||||
"""Validate the API token."""
|
||||
try:
|
||||
async with WeatherFlowRestAPI(api_token) as api:
|
||||
await api.async_get_stations()
|
||||
except ClientResponseError as err:
|
||||
if err.status == 401:
|
||||
return {"base": "invalid_api_key"}
|
||||
return {"base": "cannot_connect"}
|
||||
return {}
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for WeatherFlowCloud."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_reauth(self, user_input: Mapping[str, Any]) -> FlowResult:
|
||||
"""Handle a flow for reauth."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
api_token = user_input[CONF_API_TOKEN]
|
||||
errors = await _validate_api_token(api_token)
|
||||
if not errors:
|
||||
# Update the existing entry and abort
|
||||
if existing_entry := self.hass.config_entries.async_get_entry(
|
||||
self.context["entry_id"]
|
||||
):
|
||||
return self.async_update_reload_and_abort(
|
||||
existing_entry,
|
||||
data={CONF_API_TOKEN: api_token},
|
||||
reason="reauth_successful",
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reauth",
|
||||
data_schema=vol.Schema({vol.Required(CONF_API_TOKEN): str}),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
self._async_abort_entries_match(user_input)
|
||||
api_token = user_input[CONF_API_TOKEN]
|
||||
errors = await _validate_api_token(api_token)
|
||||
if not errors:
|
||||
return self.async_create_entry(
|
||||
title="Weatherflow REST",
|
||||
data={CONF_API_TOKEN: api_token},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema({vol.Required(CONF_API_TOKEN): str}),
|
||||
errors=errors,
|
||||
)
|
8
homeassistant/components/weatherflow_cloud/const.py
Normal file
8
homeassistant/components/weatherflow_cloud/const.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""Constants for the WeatherflowCloud integration."""
|
||||
import logging
|
||||
|
||||
DOMAIN = "weatherflow_cloud"
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
ATTR_ATTRIBUTION = "Weather data delivered by WeatherFlow/Tempest REST Api"
|
||||
MANUFACTURER = "WeatherFlow"
|
39
homeassistant/components/weatherflow_cloud/coordinator.py
Normal file
39
homeassistant/components/weatherflow_cloud/coordinator.py
Normal file
@ -0,0 +1,39 @@
|
||||
"""Data coordinator for WeatherFlow Cloud Data."""
|
||||
from datetime import timedelta
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from weatherflow4py.api import WeatherFlowRestAPI
|
||||
from weatherflow4py.models.unified import WeatherFlowData
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
|
||||
class WeatherFlowCloudDataUpdateCoordinator(
|
||||
DataUpdateCoordinator[dict[int, WeatherFlowData]]
|
||||
):
|
||||
"""Class to manage fetching REST Based WeatherFlow Forecast data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api_token: str) -> None:
|
||||
"""Initialize global WeatherFlow forecast data updater."""
|
||||
self.weather_api = WeatherFlowRestAPI(api_token=api_token)
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(minutes=15),
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> dict[int, WeatherFlowData]:
|
||||
"""Fetch data from WeatherFlow Forecast."""
|
||||
try:
|
||||
async with self.weather_api:
|
||||
return await self.weather_api.get_all_data()
|
||||
except ClientResponseError as err:
|
||||
if err.status == 401:
|
||||
raise ConfigEntryAuthFailed(err) from err
|
||||
raise UpdateFailed(f"Update failed: {err}") from err
|
9
homeassistant/components/weatherflow_cloud/manifest.json
Normal file
9
homeassistant/components/weatherflow_cloud/manifest.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"domain": "weatherflow_cloud",
|
||||
"name": "WeatherflowCloud",
|
||||
"codeowners": ["@jeeftor"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["weatherflow4py==0.1.11"]
|
||||
}
|
27
homeassistant/components/weatherflow_cloud/strings.json
Normal file
27
homeassistant/components/weatherflow_cloud/strings.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Set up a WeatherFlow Forecast Station",
|
||||
"data": {
|
||||
"api_token": "Personal api token"
|
||||
}
|
||||
},
|
||||
"reauth": {
|
||||
"description": "Reauthenticate with WeatherFlow",
|
||||
"data": {
|
||||
"api_token": "[%key:component::weatherflow_cloud::config::step::user::data::api_token%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]"
|
||||
},
|
||||
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
}
|
||||
}
|
139
homeassistant/components/weatherflow_cloud/weather.py
Normal file
139
homeassistant/components/weatherflow_cloud/weather.py
Normal file
@ -0,0 +1,139 @@
|
||||
"""Support for WeatherFlow Forecast weather service."""
|
||||
from __future__ import annotations
|
||||
|
||||
from weatherflow4py.models.unified import WeatherFlowData
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
Forecast,
|
||||
SingleCoordinatorWeatherEntity,
|
||||
WeatherEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfPressure,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ATTR_ATTRIBUTION, DOMAIN, MANUFACTURER
|
||||
from .coordinator import WeatherFlowCloudDataUpdateCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add a weather entity from a config_entry."""
|
||||
coordinator: WeatherFlowCloudDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
WeatherFlowWeather(coordinator, station_id=station_id)
|
||||
for station_id, data in coordinator.data.items()
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class WeatherFlowWeather(
|
||||
SingleCoordinatorWeatherEntity[WeatherFlowCloudDataUpdateCoordinator]
|
||||
):
|
||||
"""Implementation of a WeatherFlow weather condition."""
|
||||
|
||||
_attr_attribution = ATTR_ATTRIBUTION
|
||||
_attr_has_entity_name = True
|
||||
|
||||
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
|
||||
_attr_native_pressure_unit = UnitOfPressure.MBAR
|
||||
_attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
|
||||
_attr_supported_features = (
|
||||
WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
|
||||
)
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: WeatherFlowCloudDataUpdateCoordinator,
|
||||
station_id: int,
|
||||
) -> None:
|
||||
"""Initialise the platform with a data instance and station."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.station_id = station_id
|
||||
self._attr_unique_id = f"weatherflow_forecast_{station_id}"
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
name=self.local_data.station.name,
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, f"{station_id}")},
|
||||
manufacturer=MANUFACTURER,
|
||||
configuration_url=f"https://tempestwx.com/station/{station_id}/grid",
|
||||
)
|
||||
|
||||
@property
|
||||
def local_data(self) -> WeatherFlowData:
|
||||
"""Return the local weather data object for this station."""
|
||||
return self.coordinator.data[self.station_id]
|
||||
|
||||
@property
|
||||
def condition(self) -> str | None:
|
||||
"""Return current condition - required property."""
|
||||
return self.local_data.weather.current_conditions.icon.ha_icon
|
||||
|
||||
@property
|
||||
def native_temperature(self) -> float | None:
|
||||
"""Return the temperature."""
|
||||
return self.local_data.weather.current_conditions.air_temperature
|
||||
|
||||
@property
|
||||
def native_pressure(self) -> float | None:
|
||||
"""Return the Air Pressure @ Station."""
|
||||
return self.local_data.weather.current_conditions.station_pressure
|
||||
|
||||
#
|
||||
@property
|
||||
def humidity(self) -> float | None:
|
||||
"""Return the humidity."""
|
||||
return self.local_data.weather.current_conditions.relative_humidity
|
||||
|
||||
@property
|
||||
def native_wind_speed(self) -> float | None:
|
||||
"""Return the wind speed."""
|
||||
return self.local_data.weather.current_conditions.wind_avg
|
||||
|
||||
@property
|
||||
def wind_bearing(self) -> float | str | None:
|
||||
"""Return the wind direction."""
|
||||
return self.local_data.weather.current_conditions.wind_direction
|
||||
|
||||
@property
|
||||
def native_wind_gust_speed(self) -> float | None:
|
||||
"""Return the wind gust speed in native units."""
|
||||
return self.local_data.weather.current_conditions.wind_gust
|
||||
|
||||
@property
|
||||
def native_dew_point(self) -> float | None:
|
||||
"""Return dew point."""
|
||||
return self.local_data.weather.current_conditions.dew_point
|
||||
|
||||
@property
|
||||
def uv_index(self) -> float | None:
|
||||
"""Return UV Index."""
|
||||
return self.local_data.weather.current_conditions.uv
|
||||
|
||||
@callback
|
||||
def _async_forecast_daily(self) -> list[Forecast] | None:
|
||||
"""Return the daily forecast in native units."""
|
||||
return [x.ha_forecast for x in self.local_data.weather.forecast.daily]
|
||||
|
||||
@callback
|
||||
def _async_forecast_hourly(self) -> list[Forecast] | None:
|
||||
"""Return the hourly forecast in native units."""
|
||||
return [x.ha_forecast for x in self.local_data.weather.forecast.hourly]
|
@ -585,6 +585,7 @@ FLOWS = {
|
||||
"watttime",
|
||||
"waze_travel_time",
|
||||
"weatherflow",
|
||||
"weatherflow_cloud",
|
||||
"weatherkit",
|
||||
"webmin",
|
||||
"webostv",
|
||||
|
@ -6657,6 +6657,12 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"weatherflow_cloud": {
|
||||
"name": "WeatherflowCloud",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"webhook": {
|
||||
"name": "Webhook",
|
||||
"integration_type": "hub",
|
||||
|
@ -2835,6 +2835,9 @@ watchdog==2.3.1
|
||||
# homeassistant.components.waterfurnace
|
||||
waterfurnace==1.1.0
|
||||
|
||||
# homeassistant.components.weatherflow_cloud
|
||||
weatherflow4py==0.1.11
|
||||
|
||||
# homeassistant.components.webmin
|
||||
webmin-xmlrpc==0.0.1
|
||||
|
||||
|
@ -2170,6 +2170,9 @@ wallbox==0.6.0
|
||||
# homeassistant.components.folder_watcher
|
||||
watchdog==2.3.1
|
||||
|
||||
# homeassistant.components.weatherflow_cloud
|
||||
weatherflow4py==0.1.11
|
||||
|
||||
# homeassistant.components.webmin
|
||||
webmin-xmlrpc==0.0.1
|
||||
|
||||
|
1
tests/components/weatherflow_cloud/__init__.py
Normal file
1
tests/components/weatherflow_cloud/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the WeatherflowCloud integration."""
|
57
tests/components/weatherflow_cloud/conftest.py
Normal file
57
tests/components/weatherflow_cloud/conftest.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""Common fixtures for the WeatherflowCloud tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.weatherflow_cloud.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_stations() -> Generator[AsyncMock, None, None]:
|
||||
"""Mock get_stations with a sequence of responses."""
|
||||
side_effects = [
|
||||
True,
|
||||
]
|
||||
|
||||
with patch(
|
||||
"weatherflow4py.api.WeatherFlowRestAPI.async_get_stations",
|
||||
side_effect=side_effects,
|
||||
) as mock_get_stations:
|
||||
yield mock_get_stations
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_stations_500_error() -> Generator[AsyncMock, None, None]:
|
||||
"""Mock get_stations with a sequence of responses."""
|
||||
side_effects = [
|
||||
ClientResponseError(Mock(), (), status=500),
|
||||
True,
|
||||
]
|
||||
|
||||
with patch(
|
||||
"weatherflow4py.api.WeatherFlowRestAPI.async_get_stations",
|
||||
side_effect=side_effects,
|
||||
) as mock_get_stations:
|
||||
yield mock_get_stations
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_stations_401_error() -> Generator[AsyncMock, None, None]:
|
||||
"""Mock get_stations with a sequence of responses."""
|
||||
side_effects = [ClientResponseError(Mock(), (), status=401), True, True, True]
|
||||
|
||||
with patch(
|
||||
"weatherflow4py.api.WeatherFlowRestAPI.async_get_stations",
|
||||
side_effect=side_effects,
|
||||
) as mock_get_stations:
|
||||
yield mock_get_stations
|
120
tests/components/weatherflow_cloud/test_config_flow.py
Normal file
120
tests/components/weatherflow_cloud/test_config_flow.py
Normal file
@ -0,0 +1,120 @@
|
||||
"""Test the WeatherflowCloud config flow."""
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.weatherflow_cloud.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.const import CONF_API_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_config(hass: HomeAssistant, mock_get_stations) -> None:
|
||||
"""Test the config flow for the ideal case."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_API_TOKEN: "string",
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_config_flow_abort(hass: HomeAssistant, mock_get_stations) -> None:
|
||||
"""Test an abort case."""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_API_TOKEN: "same_same",
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_API_TOKEN: "same_same",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mock_fixture, expected_error", # noqa: PT006
|
||||
[
|
||||
("mock_get_stations_500_error", "cannot_connect"),
|
||||
("mock_get_stations_401_error", "invalid_api_key"),
|
||||
],
|
||||
)
|
||||
async def test_config_errors(
|
||||
hass: HomeAssistant, request, expected_error, mock_fixture, mock_get_stations
|
||||
) -> None:
|
||||
"""Test the config flow for various error scenarios."""
|
||||
mock_get_stations_bad = request.getfixturevalue(mock_fixture)
|
||||
with mock_get_stations_bad:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_TOKEN: "string"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] == {"base": expected_error}
|
||||
|
||||
with mock_get_stations:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_API_TOKEN: "string"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_reauth(hass: HomeAssistant, mock_get_stations_401_error) -> None:
|
||||
"""Test a reauth_flow."""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_API_TOKEN: "same_same",
|
||||
},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_REAUTH, "entry_id": entry.entry_id}, data=None
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_REAUTH, "entry_id": entry.entry_id},
|
||||
data={CONF_API_TOKEN: "SAME_SAME"},
|
||||
)
|
||||
|
||||
assert result["reason"] == "reauth_successful"
|
||||
assert result["type"] == FlowResultType.ABORT
|
Loading…
x
Reference in New Issue
Block a user