Smhi minor fixes (#72606)

* Initial commit

* Tests

* From review comments
This commit is contained in:
G Johansson 2022-06-28 14:29:00 +02:00 committed by GitHub
parent 50cba60d1f
commit c4ff317ec6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 60 deletions

View file

@ -1,7 +1,14 @@
"""Support for the Swedish weather institute weather service."""
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.const import (
CONF_LATITUDE,
CONF_LOCATION,
CONF_LONGITUDE,
CONF_NAME,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
PLATFORMS = [Platform.WEATHER]
@ -11,7 +18,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Setting unique id where missing
if entry.unique_id is None:
unique_id = f"{entry.data[CONF_LATITUDE]}-{entry.data[CONF_LONGITUDE]}"
unique_id = f"{entry.data[CONF_LOCATION][CONF_LATITUDE]}-{entry.data[CONF_LOCATION][CONF_LONGITUDE]}"
hass.config_entries.async_update_entry(entry, unique_id=unique_id)
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
@ -21,3 +28,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old entry."""
if entry.version == 1:
new_data = {
CONF_NAME: entry.data[CONF_NAME],
CONF_LOCATION: {
CONF_LATITUDE: entry.data[CONF_LATITUDE],
CONF_LONGITUDE: entry.data[CONF_LONGITUDE],
},
}
new_unique_id = f"smhi-{entry.data[CONF_LATITUDE]}-{entry.data[CONF_LONGITUDE]}"
if not hass.config_entries.async_update_entry(
entry, data=new_data, unique_id=new_unique_id
):
return False
entry.version = 2
new_unique_id_entity = f"smhi-{entry.data[CONF_LOCATION][CONF_LATITUDE]}-{entry.data[CONF_LOCATION][CONF_LONGITUDE]}"
@callback
def update_unique_id(entity_entry: RegistryEntry) -> dict[str, str]:
"""Update unique ID of entity entry."""
return {"new_unique_id": new_unique_id_entity}
await async_migrate_entries(hass, entry.entry_id, update_unique_id)
return True

View file

@ -7,11 +7,11 @@ from smhi.smhi_lib import Smhi, SmhiForecastException
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.selector import LocationSelector
from .const import DEFAULT_NAME, DOMAIN, HOME_LOCATION_NAME
@ -33,7 +33,7 @@ async def async_check_location(
class SmhiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for SMHI component."""
VERSION = 1
VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@ -43,8 +43,8 @@ class SmhiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
lat: float = user_input[CONF_LATITUDE]
lon: float = user_input[CONF_LONGITUDE]
lat: float = user_input[CONF_LOCATION][CONF_LATITUDE]
lon: float = user_input[CONF_LOCATION][CONF_LONGITUDE]
if await async_check_location(self.hass, lon, lat):
name = f"{DEFAULT_NAME} {round(lat, 6)} {round(lon, 6)}"
if (
@ -57,30 +57,20 @@ class SmhiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
HOME_LOCATION_NAME if name == HOME_LOCATION_NAME else DEFAULT_NAME
)
await self.async_set_unique_id(f"{lat}-{lon}")
await self.async_set_unique_id(f"smhi-{lat}-{lon}")
self._abort_if_unique_id_configured()
return self.async_create_entry(title=name, data=user_input)
errors["base"] = "wrong_location"
default_lat: float = self.hass.config.latitude
default_lon: float = self.hass.config.longitude
for entry in self.hass.config_entries.async_entries(DOMAIN):
if (
entry.data[CONF_LATITUDE] == self.hass.config.latitude
and entry.data[CONF_LONGITUDE] == self.hass.config.longitude
):
default_lat = 0
default_lon = 0
home_location = {
CONF_LATITUDE: self.hass.config.latitude,
CONF_LONGITUDE: self.hass.config.longitude,
}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_LATITUDE, default=default_lat): cv.latitude,
vol.Required(CONF_LONGITUDE, default=default_lon): cv.longitude,
}
{vol.Required(CONF_LOCATION, default=home_location): LocationSelector()}
),
errors=errors,
)

View file

@ -42,6 +42,7 @@ from homeassistant.components.weather import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_LATITUDE,
CONF_LOCATION,
CONF_LONGITUDE,
CONF_NAME,
LENGTH_KILOMETERS,
@ -106,8 +107,8 @@ async def async_setup_entry(
entity = SmhiWeather(
location[CONF_NAME],
location[CONF_LATITUDE],
location[CONF_LONGITUDE],
location[CONF_LOCATION][CONF_LATITUDE],
location[CONF_LOCATION][CONF_LONGITUDE],
session=session,
)
entity.entity_id = ENTITY_ID_SENSOR_FORMAT.format(name)
@ -135,7 +136,7 @@ class SmhiWeather(WeatherEntity):
"""Initialize the SMHI weather entity."""
self._attr_name = name
self._attr_unique_id = f"{latitude}, {longitude}"
self._attr_unique_id = f"smhi-{latitude}-{longitude}"
self._forecasts: list[SmhiForecast] | None = None
self._fail_count = 0
self._smhi_api = Smhi(longitude, latitude, session=session)

View file

@ -1,3 +1,14 @@
"""Tests for the SMHI component."""
ENTITY_ID = "weather.smhi_test"
TEST_CONFIG = {"name": "test", "longitude": "17.84197", "latitude": "59.32624"}
TEST_CONFIG = {
"name": "test",
"location": {
"longitude": "17.84197",
"latitude": "59.32624",
},
}
TEST_CONFIG_MIGRATE = {
"name": "test",
"longitude": "17.84197",
"latitude": "17.84197",
}

View file

@ -7,7 +7,7 @@ from smhi.smhi_lib import SmhiForecastException
from homeassistant import config_entries
from homeassistant.components.smhi.const import DOMAIN
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT,
@ -40,8 +40,10 @@ async def test_form(hass: HomeAssistant) -> None:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_LATITUDE: 0.0,
CONF_LONGITUDE: 0.0,
CONF_LOCATION: {
CONF_LATITUDE: 0.0,
CONF_LONGITUDE: 0.0,
}
},
)
await hass.async_block_till_done()
@ -49,8 +51,10 @@ async def test_form(hass: HomeAssistant) -> None:
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "Home"
assert result2["data"] == {
"latitude": 0.0,
"longitude": 0.0,
"location": {
"latitude": 0.0,
"longitude": 0.0,
},
"name": "Home",
}
assert len(mock_setup_entry.mock_calls) == 1
@ -69,8 +73,10 @@ async def test_form(hass: HomeAssistant) -> None:
result4 = await hass.config_entries.flow.async_configure(
result3["flow_id"],
{
CONF_LATITUDE: 1.0,
CONF_LONGITUDE: 1.0,
CONF_LOCATION: {
CONF_LATITUDE: 1.0,
CONF_LONGITUDE: 1.0,
}
},
)
await hass.async_block_till_done()
@ -78,8 +84,10 @@ async def test_form(hass: HomeAssistant) -> None:
assert result4["type"] == RESULT_TYPE_CREATE_ENTRY
assert result4["title"] == "Weather 1.0 1.0"
assert result4["data"] == {
"latitude": 1.0,
"longitude": 1.0,
"location": {
"latitude": 1.0,
"longitude": 1.0,
},
"name": "Weather",
}
@ -97,8 +105,10 @@ async def test_form_invalid_coordinates(hass: HomeAssistant) -> None:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_LATITUDE: 0.0,
CONF_LONGITUDE: 0.0,
CONF_LOCATION: {
CONF_LATITUDE: 0.0,
CONF_LONGITUDE: 0.0,
}
},
)
await hass.async_block_till_done()
@ -117,8 +127,10 @@ async def test_form_invalid_coordinates(hass: HomeAssistant) -> None:
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_LATITUDE: 2.0,
CONF_LONGITUDE: 2.0,
CONF_LOCATION: {
CONF_LATITUDE: 2.0,
CONF_LONGITUDE: 2.0,
}
},
)
await hass.async_block_till_done()
@ -126,8 +138,10 @@ async def test_form_invalid_coordinates(hass: HomeAssistant) -> None:
assert result3["type"] == RESULT_TYPE_CREATE_ENTRY
assert result3["title"] == "Weather 2.0 2.0"
assert result3["data"] == {
"latitude": 2.0,
"longitude": 2.0,
"location": {
"latitude": 2.0,
"longitude": 2.0,
},
"name": "Weather",
}
@ -136,10 +150,12 @@ async def test_form_unique_id_exist(hass: HomeAssistant) -> None:
"""Test we handle unique id already exist."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="1.0-1.0",
unique_id="smhi-1.0-1.0",
data={
"latitude": 1.0,
"longitude": 1.0,
"location": {
"latitude": 1.0,
"longitude": 1.0,
},
"name": "Weather",
},
)
@ -155,8 +171,10 @@ async def test_form_unique_id_exist(hass: HomeAssistant) -> None:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_LATITUDE: 1.0,
CONF_LONGITUDE: 1.0,
CONF_LOCATION: {
CONF_LATITUDE: 1.0,
CONF_LONGITUDE: 1.0,
}
},
)
await hass.async_block_till_done()

View file

@ -3,8 +3,9 @@ from smhi.smhi_lib import APIURL_TEMPLATE
from homeassistant.components.smhi.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_registry import async_get
from . import ENTITY_ID, TEST_CONFIG
from . import ENTITY_ID, TEST_CONFIG, TEST_CONFIG_MIGRATE
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
@ -14,9 +15,11 @@ async def test_setup_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None:
"""Test setup entry."""
uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"])
uri = APIURL_TEMPLATE.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
@ -30,9 +33,11 @@ async def test_remove_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None:
"""Test remove entry."""
uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"])
uri = APIURL_TEMPLATE.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
@ -46,3 +51,38 @@ async def test_remove_entry(
state = hass.states.get(ENTITY_ID)
assert not state
async def test_migrate_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None:
"""Test migrate entry and entities unique id."""
uri = APIURL_TEMPLATE.format(
TEST_CONFIG_MIGRATE["longitude"], TEST_CONFIG_MIGRATE["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG_MIGRATE)
entry.add_to_hass(hass)
assert entry.version == 1
entity_reg = async_get(hass)
entity = entity_reg.async_get_or_create(
domain="weather",
config_entry=entry,
original_name="Weather",
platform="smhi",
supported_features=0,
unique_id="17.84197, 17.84197",
)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(entity.entity_id)
assert state
assert entry.version == 2
assert entry.unique_id == "smhi-17.84197-17.84197"
entity_get = entity_reg.async_get(entity.entity_id)
assert entity_get.unique_id == "smhi-17.84197-17.84197"

View file

@ -46,10 +46,12 @@ async def test_setup_hass(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None:
"""Test for successfully setting up the smhi integration."""
uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"])
uri = APIURL_TEMPLATE.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
@ -87,7 +89,7 @@ async def test_setup_hass(
async def test_properties_no_data(hass: HomeAssistant) -> None:
"""Test properties when no API data available."""
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
with patch(
@ -176,7 +178,7 @@ async def test_properties_unknown_symbol(hass: HomeAssistant) -> None:
testdata = [data, data2, data3]
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
with patch(
@ -203,7 +205,7 @@ async def test_refresh_weather_forecast_retry(
hass: HomeAssistant, error: Exception
) -> None:
"""Test the refresh weather forecast function."""
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
now = utcnow()
@ -320,10 +322,12 @@ async def test_custom_speed_unit(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None:
"""Test Wind Gust speed with custom unit."""
uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"])
uri = APIURL_TEMPLATE.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG, version=2)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)