mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 03:03:17 +01:00
Forbid to choose state in Ukraine Alarm integration (#156183)
This commit is contained in:
@@ -2,13 +2,23 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import aiohttp
|
||||
from uasiren.client import Client
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME, CONF_REGION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
from .coordinator import UkraineAlarmDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Ukraine Alarm as config entry."""
|
||||
@@ -30,3 +40,56 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
_LOGGER.debug("Migrating from version %s", config_entry.version)
|
||||
|
||||
if config_entry.version == 1:
|
||||
# Version 1 had states as first-class selections
|
||||
# Version 2 only allows states w/o districts, districts and communities
|
||||
region_id = config_entry.data[CONF_REGION]
|
||||
|
||||
websession = async_get_clientsession(hass)
|
||||
try:
|
||||
regions_data = await Client(websession).get_regions()
|
||||
except (aiohttp.ClientError, TimeoutError) as err:
|
||||
_LOGGER.warning(
|
||||
"Could not migrate config entry %s: failed to fetch current regions: %s",
|
||||
config_entry.entry_id,
|
||||
err,
|
||||
)
|
||||
return False
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(regions_data, dict)
|
||||
|
||||
state_with_districts = None
|
||||
for state in regions_data["states"]:
|
||||
if state["regionId"] == region_id and state.get("regionChildIds"):
|
||||
state_with_districts = state
|
||||
break
|
||||
|
||||
if state_with_districts:
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"deprecated_state_region_{config_entry.entry_id}",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key="deprecated_state_region",
|
||||
translation_placeholders={
|
||||
"region_name": config_entry.data.get(CONF_NAME, region_id),
|
||||
},
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
hass.config_entries.async_update_entry(config_entry, version=2)
|
||||
_LOGGER.info("Migration to version %s successful", 2)
|
||||
return True
|
||||
|
||||
_LOGGER.error("Unknown version %s", config_entry.version)
|
||||
return False
|
||||
|
||||
@@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class UkraineAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for Ukraine Alarm."""
|
||||
|
||||
VERSION = 1
|
||||
VERSION = 2
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize a new UkraineAlarmConfigFlow."""
|
||||
@@ -112,7 +112,7 @@ class UkraineAlarmConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return await self._async_finish_flow()
|
||||
|
||||
regions = {}
|
||||
if self.selected_region:
|
||||
if self.selected_region and step_id != "district":
|
||||
regions[self.selected_region["regionId"]] = self.selected_region[
|
||||
"regionName"
|
||||
]
|
||||
|
||||
@@ -13,19 +13,19 @@
|
||||
"data": {
|
||||
"region": "[%key:component::ukraine_alarm::config::step::user::data::region%]"
|
||||
},
|
||||
"description": "If you want to monitor not only state and district, choose its specific community"
|
||||
"description": "Choose the district you selected above or select a specific community within that district"
|
||||
},
|
||||
"district": {
|
||||
"data": {
|
||||
"region": "[%key:component::ukraine_alarm::config::step::user::data::region%]"
|
||||
},
|
||||
"description": "If you want to monitor not only state, choose its specific district"
|
||||
"description": "Choose a district to monitor within the selected state"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"region": "Region"
|
||||
},
|
||||
"description": "Choose state to monitor"
|
||||
"description": "Choose a state"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -50,5 +50,11 @@
|
||||
"name": "Urban fights"
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_state_region": {
|
||||
"description": "The region `{region_name}` is a state-level region, which is no longer supported. Please remove this integration entry and add it again, selecting a district or community instead of the entire state.",
|
||||
"title": "State-level region monitoring is no longer supported"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,27 @@
|
||||
"""Tests for the Ukraine Alarm integration."""
|
||||
|
||||
|
||||
def _region(rid, recurse=0, depth=0):
|
||||
"""Create a test region with optional nested structure."""
|
||||
if depth == 0:
|
||||
name_prefix = "State"
|
||||
elif depth == 1:
|
||||
name_prefix = "District"
|
||||
else:
|
||||
name_prefix = "Community"
|
||||
|
||||
name = f"{name_prefix} {rid}"
|
||||
region = {"regionId": rid, "regionName": name, "regionChildIds": []}
|
||||
|
||||
if not recurse:
|
||||
return region
|
||||
|
||||
for i in range(1, 4):
|
||||
region["regionChildIds"].append(_region(f"{rid}.{i}", recurse - 1, depth + 1))
|
||||
|
||||
return region
|
||||
|
||||
|
||||
REGIONS = {
|
||||
"states": [_region(f"{i}", i - 1) for i in range(1, 4)],
|
||||
}
|
||||
|
||||
@@ -12,34 +12,11 @@ from homeassistant.components.ukraine_alarm.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from . import REGIONS
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
def _region(rid, recurse=0, depth=0):
|
||||
if depth == 0:
|
||||
name_prefix = "State"
|
||||
elif depth == 1:
|
||||
name_prefix = "District"
|
||||
else:
|
||||
name_prefix = "Community"
|
||||
|
||||
name = f"{name_prefix} {rid}"
|
||||
region = {"regionId": rid, "regionName": name, "regionChildIds": []}
|
||||
|
||||
if not recurse:
|
||||
return region
|
||||
|
||||
for i in range(1, 4):
|
||||
region["regionChildIds"].append(_region(f"{rid}.{i}", recurse - 1, depth + 1))
|
||||
|
||||
return region
|
||||
|
||||
|
||||
REGIONS = {
|
||||
"states": [_region(f"{i}", i - 1) for i in range(1, 4)],
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_get_regions() -> Generator[AsyncMock]:
|
||||
"""Mock the get_regions method."""
|
||||
@@ -51,37 +28,6 @@ def mock_get_regions() -> Generator[AsyncMock]:
|
||||
yield mock_get
|
||||
|
||||
|
||||
async def test_state(hass: HomeAssistant) -> None:
|
||||
"""Test we can create entry for state."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ukraine_alarm.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"region": "1",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result3["title"] == "State 1"
|
||||
assert result3["data"] == {
|
||||
"region": "1",
|
||||
"name": result3["title"],
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_state_district(hass: HomeAssistant) -> None:
|
||||
"""Test we can create entry for state + district."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -121,45 +67,6 @@ async def test_state_district(hass: HomeAssistant) -> None:
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_state_district_pick_region(hass: HomeAssistant) -> None:
|
||||
"""Test we can create entry for region which has districts."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"region": "2",
|
||||
},
|
||||
)
|
||||
assert result3["type"] is FlowResultType.FORM
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ukraine_alarm.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"region": "2",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result4["title"] == "State 2"
|
||||
assert result4["data"] == {
|
||||
"region": "2",
|
||||
"name": result4["title"],
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_state_district_community(hass: HomeAssistant) -> None:
|
||||
"""Test we can create entry for state + district + community."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
||||
70
tests/components/ukraine_alarm/test_init.py
Normal file
70
tests/components/ukraine_alarm/test_init.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Test the Ukraine Alarm integration initialization."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.ukraine_alarm.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from . import REGIONS
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_migration_v1_to_v2_state_without_districts(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test migration allows states without districts."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
version=1,
|
||||
data={"region": "1", "name": "State 1"},
|
||||
unique_id="1",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.ukraine_alarm.Client.get_regions",
|
||||
return_value=REGIONS,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.ukraine_alarm.Client.get_alerts",
|
||||
return_value=[{"activeAlerts": []}],
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert result is True
|
||||
assert entry.version == 2
|
||||
|
||||
assert (
|
||||
DOMAIN,
|
||||
f"deprecated_state_region_{entry.entry_id}",
|
||||
) not in issue_registry.issues
|
||||
|
||||
|
||||
async def test_migration_v1_to_v2_state_with_districts_fails(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test migration rejects states with districts."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
version=1,
|
||||
data={"region": "2", "name": "State 2"},
|
||||
unique_id="2",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ukraine_alarm.Client.get_regions",
|
||||
return_value=REGIONS,
|
||||
):
|
||||
result = await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert result is False
|
||||
|
||||
assert (
|
||||
DOMAIN,
|
||||
f"deprecated_state_region_{entry.entry_id}",
|
||||
) in issue_registry.issues
|
||||
Reference in New Issue
Block a user