Migrate unique id in Trafikverket Camera (#101937)

pull/101928/head^2
G Johansson 2023-10-16 10:28:11 +02:00 committed by GitHub
parent 88296c1998
commit 6b05f51413
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 215 additions and 15 deletions

View File

@ -1,15 +1,23 @@
"""The trafikverket_camera component."""
from __future__ import annotations
import logging
from pytrafikverket.trafikverket_camera import TrafikverketCamera
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
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
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""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)
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

View File

@ -25,17 +25,18 @@ from .const import CONF_LOCATION, DOMAIN
class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Trafikverket Camera integration."""
VERSION = 1
VERSION = 2
entry: config_entries.ConfigEntry | None
async def validate_input(
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."""
errors: dict[str, str] = {}
camera_info: CameraInfo | None = None
camera_location: str | None = None
camera_id: str | None = None
web_session = async_get_clientsession(self.hass)
camera_api = TrafikverketCamera(web_session, sensor_api)
@ -51,12 +52,13 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors["base"] = "cannot_connect"
if camera_info:
camera_id = camera_info.camera_id
if _location := camera_info.location:
camera_location = _location
else:
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:
"""Handle re-authentication with Trafikverket."""
@ -74,7 +76,7 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
api_key = user_input[CONF_API_KEY]
assert self.entry is not None
errors, _ = await self.validate_input(
errors, _, _ = await self.validate_input(
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]
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:
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()
return self.async_create_entry(
title=camera_location,

View File

@ -32,7 +32,8 @@ async def load_integration_from_entry(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
unique_id="123",
version=2,
unique_id="trafikverket_camera-1234",
title="Test location",
)

View File

@ -53,7 +53,7 @@ async def test_form(hass: HomeAssistant, get_camera: CameraInfo) -> None:
"location": "Test location",
}
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(
@ -90,7 +90,7 @@ async def test_form_no_location_data(
"location": "Test Camera",
}
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(
@ -153,6 +153,7 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
CONF_LOCATION: "Test location",
},
unique_id="1234",
version=2,
)
entry.add_to_hass(hass)
@ -225,6 +226,7 @@ async def test_reauth_flow_error(
CONF_LOCATION: "Test location",
},
unique_id="1234",
version=2,
)
entry.add_to_hass(hass)
await hass.async_block_till_done()

View File

@ -40,7 +40,8 @@ async def test_coordinator(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
unique_id="123",
version=2,
unique_id="trafikverket_camera-1234",
title="Test location",
)
entry.add_to_hass(hass)
@ -100,7 +101,8 @@ async def test_coordinator_failed_update(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
unique_id="123",
version=2,
unique_id="trafikverket_camera-1234",
title="Test location",
)
entry.add_to_hass(hass)
@ -133,7 +135,8 @@ async def test_coordinator_failed_get_image(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
unique_id="123",
version=2,
unique_id="trafikverket_camera-1234",
title="Test location",
)
entry.add_to_hass(hass)

View File

@ -1,14 +1,18 @@
"""Test for Trafikverket Ferry component Init."""
from __future__ import annotations
from datetime import datetime
from unittest.mock import patch
from pytrafikverket.exceptions import UnknownError
from pytrafikverket.trafikverket_camera import CameraInfo
from homeassistant import config_entries
from homeassistant.components.trafikverket_camera import async_migrate_entry
from homeassistant.components.trafikverket_camera.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from . import ENTRY_CONFIG
@ -31,7 +35,8 @@ async def test_setup_entry(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
unique_id="123",
version=2,
unique_id="trafikverket_camera-1234",
title="Test location",
)
entry.add_to_hass(hass)
@ -62,7 +67,8 @@ async def test_unload_entry(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
unique_id="321",
version=2,
unique_id="trafikverket_camera-1234",
title="Test location",
)
entry.add_to_hass(hass)
@ -78,3 +84,145 @@ async def test_unload_entry(
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
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