mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 03:37:07 +00:00
Migrate unique id in Trafikverket Camera (#101937)
This commit is contained in:
parent
88296c1998
commit
6b05f51413
@ -1,15 +1,23 @@
|
|||||||
"""The trafikverket_camera component."""
|
"""The trafikverket_camera component."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pytrafikverket.trafikverket_camera import TrafikverketCamera
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN, PLATFORMS
|
from .const import CONF_LOCATION, DOMAIN, PLATFORMS
|
||||||
from .coordinator import TVDataUpdateCoordinator
|
from .coordinator import TVDataUpdateCoordinator
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Trafikverket Camera from a config entry."""
|
"""Set up Trafikverket Camera from a config entry."""
|
||||||
@ -30,3 +38,37 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Migrate old entry."""
|
||||||
|
# Change entry unique id from location to camera id
|
||||||
|
if entry.version == 1:
|
||||||
|
location = entry.data[CONF_LOCATION]
|
||||||
|
api_key = entry.data[CONF_API_KEY]
|
||||||
|
|
||||||
|
web_session = async_get_clientsession(hass)
|
||||||
|
camera_api = TrafikverketCamera(web_session, api_key)
|
||||||
|
|
||||||
|
try:
|
||||||
|
camera_info = await camera_api.async_get_camera(location)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.error(
|
||||||
|
"Could not migrate the config entry. No connection to the api"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if camera_id := camera_info.camera_id:
|
||||||
|
entry.version = 2
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migrate Trafikverket Camera config entry unique id to %s",
|
||||||
|
camera_id,
|
||||||
|
)
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
unique_id=f"{DOMAIN}-{camera_id}",
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
_LOGGER.error("Could not migrate the config entry. Camera has no id")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
@ -25,17 +25,18 @@ from .const import CONF_LOCATION, DOMAIN
|
|||||||
class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Trafikverket Camera integration."""
|
"""Handle a config flow for Trafikverket Camera integration."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 2
|
||||||
|
|
||||||
entry: config_entries.ConfigEntry | None
|
entry: config_entries.ConfigEntry | None
|
||||||
|
|
||||||
async def validate_input(
|
async def validate_input(
|
||||||
self, sensor_api: str, location: str
|
self, sensor_api: str, location: str
|
||||||
) -> tuple[dict[str, str], str | None]:
|
) -> tuple[dict[str, str], str | None, str | None]:
|
||||||
"""Validate input from user input."""
|
"""Validate input from user input."""
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
camera_info: CameraInfo | None = None
|
camera_info: CameraInfo | None = None
|
||||||
camera_location: str | None = None
|
camera_location: str | None = None
|
||||||
|
camera_id: str | None = None
|
||||||
|
|
||||||
web_session = async_get_clientsession(self.hass)
|
web_session = async_get_clientsession(self.hass)
|
||||||
camera_api = TrafikverketCamera(web_session, sensor_api)
|
camera_api = TrafikverketCamera(web_session, sensor_api)
|
||||||
@ -51,12 +52,13 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
|
|
||||||
if camera_info:
|
if camera_info:
|
||||||
|
camera_id = camera_info.camera_id
|
||||||
if _location := camera_info.location:
|
if _location := camera_info.location:
|
||||||
camera_location = _location
|
camera_location = _location
|
||||||
else:
|
else:
|
||||||
camera_location = camera_info.camera_name
|
camera_location = camera_info.camera_name
|
||||||
|
|
||||||
return (errors, camera_location)
|
return (errors, camera_location, camera_id)
|
||||||
|
|
||||||
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||||
"""Handle re-authentication with Trafikverket."""
|
"""Handle re-authentication with Trafikverket."""
|
||||||
@ -74,7 +76,7 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
api_key = user_input[CONF_API_KEY]
|
api_key = user_input[CONF_API_KEY]
|
||||||
|
|
||||||
assert self.entry is not None
|
assert self.entry is not None
|
||||||
errors, _ = await self.validate_input(
|
errors, _, _ = await self.validate_input(
|
||||||
api_key, self.entry.data[CONF_LOCATION]
|
api_key, self.entry.data[CONF_LOCATION]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,11 +111,13 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
api_key = user_input[CONF_API_KEY]
|
api_key = user_input[CONF_API_KEY]
|
||||||
location = user_input[CONF_LOCATION]
|
location = user_input[CONF_LOCATION]
|
||||||
|
|
||||||
errors, camera_location = await self.validate_input(api_key, location)
|
errors, camera_location, camera_id = await self.validate_input(
|
||||||
|
api_key, location
|
||||||
|
)
|
||||||
|
|
||||||
if not errors:
|
if not errors:
|
||||||
assert camera_location
|
assert camera_location
|
||||||
await self.async_set_unique_id(f"{DOMAIN}-{camera_location}")
|
await self.async_set_unique_id(f"{DOMAIN}-{camera_id}")
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=camera_location,
|
title=camera_location,
|
||||||
|
@ -32,7 +32,8 @@ async def load_integration_from_entry(
|
|||||||
source=SOURCE_USER,
|
source=SOURCE_USER,
|
||||||
data=ENTRY_CONFIG,
|
data=ENTRY_CONFIG,
|
||||||
entry_id="1",
|
entry_id="1",
|
||||||
unique_id="123",
|
version=2,
|
||||||
|
unique_id="trafikverket_camera-1234",
|
||||||
title="Test location",
|
title="Test location",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ async def test_form(hass: HomeAssistant, get_camera: CameraInfo) -> None:
|
|||||||
"location": "Test location",
|
"location": "Test location",
|
||||||
}
|
}
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
assert result2["result"].unique_id == "trafikverket_camera-Test location"
|
assert result2["result"].unique_id == "trafikverket_camera-1234"
|
||||||
|
|
||||||
|
|
||||||
async def test_form_no_location_data(
|
async def test_form_no_location_data(
|
||||||
@ -90,7 +90,7 @@ async def test_form_no_location_data(
|
|||||||
"location": "Test Camera",
|
"location": "Test Camera",
|
||||||
}
|
}
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
assert result2["result"].unique_id == "trafikverket_camera-Test Camera"
|
assert result2["result"].unique_id == "trafikverket_camera-1234"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -153,6 +153,7 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
|
|||||||
CONF_LOCATION: "Test location",
|
CONF_LOCATION: "Test location",
|
||||||
},
|
},
|
||||||
unique_id="1234",
|
unique_id="1234",
|
||||||
|
version=2,
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
@ -225,6 +226,7 @@ async def test_reauth_flow_error(
|
|||||||
CONF_LOCATION: "Test location",
|
CONF_LOCATION: "Test location",
|
||||||
},
|
},
|
||||||
unique_id="1234",
|
unique_id="1234",
|
||||||
|
version=2,
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -40,7 +40,8 @@ async def test_coordinator(
|
|||||||
source=SOURCE_USER,
|
source=SOURCE_USER,
|
||||||
data=ENTRY_CONFIG,
|
data=ENTRY_CONFIG,
|
||||||
entry_id="1",
|
entry_id="1",
|
||||||
unique_id="123",
|
version=2,
|
||||||
|
unique_id="trafikverket_camera-1234",
|
||||||
title="Test location",
|
title="Test location",
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
@ -100,7 +101,8 @@ async def test_coordinator_failed_update(
|
|||||||
source=SOURCE_USER,
|
source=SOURCE_USER,
|
||||||
data=ENTRY_CONFIG,
|
data=ENTRY_CONFIG,
|
||||||
entry_id="1",
|
entry_id="1",
|
||||||
unique_id="123",
|
version=2,
|
||||||
|
unique_id="trafikverket_camera-1234",
|
||||||
title="Test location",
|
title="Test location",
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
@ -133,7 +135,8 @@ async def test_coordinator_failed_get_image(
|
|||||||
source=SOURCE_USER,
|
source=SOURCE_USER,
|
||||||
data=ENTRY_CONFIG,
|
data=ENTRY_CONFIG,
|
||||||
entry_id="1",
|
entry_id="1",
|
||||||
unique_id="123",
|
version=2,
|
||||||
|
unique_id="trafikverket_camera-1234",
|
||||||
title="Test location",
|
title="Test location",
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
"""Test for Trafikverket Ferry component Init."""
|
"""Test for Trafikverket Ferry component Init."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pytrafikverket.exceptions import UnknownError
|
||||||
from pytrafikverket.trafikverket_camera import CameraInfo
|
from pytrafikverket.trafikverket_camera import CameraInfo
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.trafikverket_camera import async_migrate_entry
|
||||||
from homeassistant.components.trafikverket_camera.const import DOMAIN
|
from homeassistant.components.trafikverket_camera.const import DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import ENTRY_CONFIG
|
from . import ENTRY_CONFIG
|
||||||
|
|
||||||
@ -31,7 +35,8 @@ async def test_setup_entry(
|
|||||||
source=SOURCE_USER,
|
source=SOURCE_USER,
|
||||||
data=ENTRY_CONFIG,
|
data=ENTRY_CONFIG,
|
||||||
entry_id="1",
|
entry_id="1",
|
||||||
unique_id="123",
|
version=2,
|
||||||
|
unique_id="trafikverket_camera-1234",
|
||||||
title="Test location",
|
title="Test location",
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
@ -62,7 +67,8 @@ async def test_unload_entry(
|
|||||||
source=SOURCE_USER,
|
source=SOURCE_USER,
|
||||||
data=ENTRY_CONFIG,
|
data=ENTRY_CONFIG,
|
||||||
entry_id="1",
|
entry_id="1",
|
||||||
unique_id="321",
|
version=2,
|
||||||
|
unique_id="trafikverket_camera-1234",
|
||||||
title="Test location",
|
title="Test location",
|
||||||
)
|
)
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
@ -78,3 +84,145 @@ async def test_unload_entry(
|
|||||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migrate_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
get_camera: CameraInfo,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test migrate entry to version 2."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://www.testurl.com/test_photo.jpg?type=fullsize", content=b"0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
source=SOURCE_USER,
|
||||||
|
data=ENTRY_CONFIG,
|
||||||
|
entry_id="1",
|
||||||
|
unique_id="trafikverket_camera-Test location",
|
||||||
|
title="Test location",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.trafikverket_camera.coordinator.TrafikverketCamera.async_get_camera",
|
||||||
|
return_value=get_camera,
|
||||||
|
) as mock_tvt_camera:
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
||||||
|
assert entry.version == 2
|
||||||
|
assert entry.unique_id == "trafikverket_camera-1234"
|
||||||
|
assert len(mock_tvt_camera.mock_calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migrate_entry_fails_with_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
get_camera: CameraInfo,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test migrate entry fails with api error."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://www.testurl.com/test_photo.jpg?type=fullsize", content=b"0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
source=SOURCE_USER,
|
||||||
|
data=ENTRY_CONFIG,
|
||||||
|
entry_id="1",
|
||||||
|
unique_id="trafikverket_camera-Test location",
|
||||||
|
title="Test location",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.trafikverket_camera.coordinator.TrafikverketCamera.async_get_camera",
|
||||||
|
side_effect=UnknownError,
|
||||||
|
) as mock_tvt_camera:
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state is config_entries.ConfigEntryState.MIGRATION_ERROR
|
||||||
|
assert entry.version == 1
|
||||||
|
assert entry.unique_id == "trafikverket_camera-Test location"
|
||||||
|
assert len(mock_tvt_camera.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migrate_entry_fails_no_id(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test migrate entry fails, camera returns no id."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://www.testurl.com/test_photo.jpg?type=fullsize", content=b"0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
source=SOURCE_USER,
|
||||||
|
data=ENTRY_CONFIG,
|
||||||
|
entry_id="1",
|
||||||
|
unique_id="trafikverket_camera-Test location",
|
||||||
|
title="Test location",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
_camera = CameraInfo(
|
||||||
|
camera_name="Test_camera",
|
||||||
|
camera_id=None,
|
||||||
|
active=True,
|
||||||
|
deleted=False,
|
||||||
|
description="Test Camera for testing",
|
||||||
|
direction="180",
|
||||||
|
fullsizephoto=True,
|
||||||
|
location="Test location",
|
||||||
|
modified=datetime(2022, 4, 4, 4, 4, 4, tzinfo=dt_util.UTC),
|
||||||
|
phototime=datetime(2022, 4, 4, 4, 4, 4, tzinfo=dt_util.UTC),
|
||||||
|
photourl="https://www.testurl.com/test_photo.jpg",
|
||||||
|
status="Running",
|
||||||
|
camera_type="Road",
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.trafikverket_camera.coordinator.TrafikverketCamera.async_get_camera",
|
||||||
|
return_value=_camera,
|
||||||
|
) as mock_tvt_camera:
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state is config_entries.ConfigEntryState.MIGRATION_ERROR
|
||||||
|
assert entry.version == 1
|
||||||
|
assert entry.unique_id == "trafikverket_camera-Test location"
|
||||||
|
assert len(mock_tvt_camera.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_migration_needed(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
get_camera: CameraInfo,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
) -> None:
|
||||||
|
"""Test migrate entry fails, camera returns no id."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"https://www.testurl.com/test_photo.jpg?type=fullsize", content=b"0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
source=SOURCE_USER,
|
||||||
|
data=ENTRY_CONFIG,
|
||||||
|
version=2,
|
||||||
|
entry_id="1234",
|
||||||
|
unique_id="trafikverket_camera-1234",
|
||||||
|
title="Test location",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.trafikverket_camera.coordinator.TrafikverketCamera.async_get_camera",
|
||||||
|
return_value=get_camera,
|
||||||
|
):
|
||||||
|
assert await async_migrate_entry(hass, entry) is True
|
||||||
|
Loading…
x
Reference in New Issue
Block a user