Use id as location data in Trafikverket Camera (#104473)

pull/104723/head
G Johansson 2023-11-29 13:35:32 +01:00 committed by GitHub
parent cf23de1c48
commit e5a7446afe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 135 additions and 79 deletions

View File

@ -6,7 +6,7 @@ import logging
from pytrafikverket.trafikverket_camera import TrafikverketCamera
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.const import CONF_API_KEY, CONF_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -42,13 +42,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old entry."""
api_key = entry.data[CONF_API_KEY]
web_session = async_get_clientsession(hass)
camera_api = TrafikverketCamera(web_session, api_key)
# 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)
@ -60,14 +59,40 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
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}",
)
_LOGGER.debug(
"Migrated Trafikverket Camera config entry unique id to %s",
camera_id,
)
else:
_LOGGER.error("Could not migrate the config entry. Camera has no id")
return False
# Change entry data from location to id
if entry.version == 2:
location = entry.data[CONF_LOCATION]
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 = 3
_LOGGER.debug(
"Migrate Trafikverket Camera config entry unique id to %s",
camera_id,
)
new_data = entry.data.copy()
new_data.pop(CONF_LOCATION)
new_data[CONF_ID] = camera_id
hass.config_entries.async_update_entry(entry, data=new_data)
return True
_LOGGER.error("Could not migrate the config entry. Camera has no id")
return False

View File

@ -14,7 +14,7 @@ from pytrafikverket.trafikverket_camera import CameraInfo, TrafikverketCamera
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY
from homeassistant.const import CONF_API_KEY, CONF_ID
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
@ -25,7 +25,7 @@ from .const import CONF_LOCATION, DOMAIN
class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Trafikverket Camera integration."""
VERSION = 2
VERSION = 3
entry: config_entries.ConfigEntry | None
@ -53,10 +53,7 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if camera_info:
camera_id = camera_info.camera_id
if _location := camera_info.location:
camera_location = _location
else:
camera_location = camera_info.camera_name
camera_location = camera_info.camera_name or "Trafikverket Camera"
return (errors, camera_location, camera_id)
@ -76,9 +73,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(
api_key, self.entry.data[CONF_LOCATION]
)
errors, _, _ = await self.validate_input(api_key, self.entry.data[CONF_ID])
if not errors:
self.hass.config_entries.async_update_entry(
@ -121,10 +116,7 @@ class TVCameraConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=camera_location,
data={
CONF_API_KEY: api_key,
CONF_LOCATION: camera_location,
},
data={CONF_API_KEY: api_key, CONF_ID: camera_id},
)
return self.async_show_form(

View File

@ -15,13 +15,13 @@ from pytrafikverket.exceptions import (
from pytrafikverket.trafikverket_camera import CameraInfo, TrafikverketCamera
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.const import CONF_API_KEY, CONF_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_LOCATION, DOMAIN
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
TIME_BETWEEN_UPDATES = timedelta(minutes=5)
@ -48,14 +48,14 @@ class TVDataUpdateCoordinator(DataUpdateCoordinator[CameraData]):
)
self.session = async_get_clientsession(hass)
self._camera_api = TrafikverketCamera(self.session, entry.data[CONF_API_KEY])
self._location = entry.data[CONF_LOCATION]
self._id = entry.data[CONF_ID]
async def _async_update_data(self) -> CameraData:
"""Fetch data from Trafikverket."""
camera_data: CameraInfo
image: bytes | None = None
try:
camera_data = await self._camera_api.async_get_camera(self._location)
camera_data = await self._camera_api.async_get_camera(self._id)
except (NoCameraFound, MultipleCamerasFound, UnknownError) as error:
raise UpdateFailed from error
except InvalidAuthentication as error:

View File

@ -2,9 +2,14 @@
from __future__ import annotations
from homeassistant.components.trafikverket_camera.const import CONF_LOCATION
from homeassistant.const import CONF_API_KEY
from homeassistant.const import CONF_API_KEY, CONF_ID
ENTRY_CONFIG = {
CONF_API_KEY: "1234567890",
CONF_ID: "1234",
}
ENTRY_CONFIG_OLD_CONFIG = {
CONF_API_KEY: "1234567890",
CONF_LOCATION: "Test location",
}

View File

@ -32,9 +32,9 @@ async def load_integration_from_entry(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
version=2,
version=3,
unique_id="trafikverket_camera-1234",
title="Test location",
title="Test Camera",
)
config_entry.add_to_hass(hass)
@ -54,7 +54,7 @@ def fixture_get_camera() -> CameraInfo:
"""Construct Camera Mock."""
return CameraInfo(
camera_name="Test_camera",
camera_name="Test Camera",
camera_id="1234",
active=True,
deleted=False,

View File

@ -16,5 +16,5 @@ async def test_sensor(
) -> None:
"""Test the Trafikverket Camera binary sensor."""
state = hass.states.get("binary_sensor.test_location_active")
state = hass.states.get("binary_sensor.test_camera_active")
assert state.state == STATE_ON

View File

@ -26,7 +26,7 @@ async def test_camera(
get_camera: CameraInfo,
) -> None:
"""Test the Trafikverket Camera sensor."""
state1 = hass.states.get("camera.test_location")
state1 = hass.states.get("camera.test_camera")
assert state1.state == "idle"
assert state1.attributes["description"] == "Test Camera for testing"
assert state1.attributes["location"] == "Test location"
@ -44,11 +44,11 @@ async def test_camera(
async_fire_time_changed(hass)
await hass.async_block_till_done()
state1 = hass.states.get("camera.test_location")
state1 = hass.states.get("camera.test_camera")
assert state1.state == "idle"
assert state1.attributes != {}
assert await async_get_image(hass, "camera.test_location")
assert await async_get_image(hass, "camera.test_camera")
monkeypatch.setattr(
get_camera,
@ -69,4 +69,4 @@ async def test_camera(
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError):
await async_get_image(hass, "camera.test_location")
await async_get_image(hass, "camera.test_camera")

View File

@ -14,7 +14,7 @@ from pytrafikverket.trafikverket_camera import CameraInfo
from homeassistant import config_entries
from homeassistant.components.trafikverket_camera.const import CONF_LOCATION, DOMAIN
from homeassistant.const import CONF_API_KEY
from homeassistant.const import CONF_API_KEY, CONF_ID
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
@ -47,10 +47,10 @@ async def test_form(hass: HomeAssistant, get_camera: CameraInfo) -> None:
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Test location"
assert result2["title"] == "Test Camera"
assert result2["data"] == {
"api_key": "1234567890",
"location": "Test location",
"id": "1234",
}
assert len(mock_setup_entry.mock_calls) == 1
assert result2["result"].unique_id == "trafikverket_camera-1234"
@ -87,7 +87,7 @@ async def test_form_no_location_data(
assert result2["title"] == "Test Camera"
assert result2["data"] == {
"api_key": "1234567890",
"location": "Test Camera",
"id": "1234",
}
assert len(mock_setup_entry.mock_calls) == 1
assert result2["result"].unique_id == "trafikverket_camera-1234"
@ -150,10 +150,10 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
domain=DOMAIN,
data={
CONF_API_KEY: "1234567890",
CONF_LOCATION: "Test location",
CONF_ID: "1234",
},
unique_id="1234",
version=2,
version=3,
)
entry.add_to_hass(hass)
@ -186,7 +186,7 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
assert result2["reason"] == "reauth_successful"
assert entry.data == {
"api_key": "1234567891",
"location": "Test location",
"id": "1234",
}
@ -223,10 +223,10 @@ async def test_reauth_flow_error(
domain=DOMAIN,
data={
CONF_API_KEY: "1234567890",
CONF_LOCATION: "Test location",
CONF_ID: "1234",
},
unique_id="1234",
version=2,
version=3,
)
entry.add_to_hass(hass)
await hass.async_block_till_done()
@ -271,5 +271,5 @@ async def test_reauth_flow_error(
assert result2["reason"] == "reauth_successful"
assert entry.data == {
"api_key": "1234567891",
"location": "Test location",
"id": "1234",
}

View File

@ -40,9 +40,9 @@ async def test_coordinator(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
version=2,
version=3,
unique_id="trafikverket_camera-1234",
title="Test location",
title="Test Camera",
)
entry.add_to_hass(hass)
@ -54,7 +54,7 @@ async def test_coordinator(
await hass.async_block_till_done()
mock_data.assert_called_once()
state1 = hass.states.get("camera.test_location")
state1 = hass.states.get("camera.test_camera")
assert state1.state == "idle"
@ -101,9 +101,9 @@ async def test_coordinator_failed_update(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
version=2,
version=3,
unique_id="trafikverket_camera-1234",
title="Test location",
title="Test Camera",
)
entry.add_to_hass(hass)
@ -115,7 +115,7 @@ async def test_coordinator_failed_update(
await hass.async_block_till_done()
mock_data.assert_called_once()
state = hass.states.get("camera.test_location")
state = hass.states.get("camera.test_camera")
assert state is None
assert entry.state == entry_state
@ -135,7 +135,7 @@ async def test_coordinator_failed_get_image(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
version=2,
version=3,
unique_id="trafikverket_camera-1234",
title="Test location",
)
@ -149,6 +149,6 @@ async def test_coordinator_failed_get_image(
await hass.async_block_till_done()
mock_data.assert_called_once()
state = hass.states.get("camera.test_location")
state = hass.states.get("camera.test_camera")
assert state is None
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from datetime import datetime
from unittest.mock import patch
import pytest
from pytrafikverket.exceptions import UnknownError
from pytrafikverket.trafikverket_camera import CameraInfo
@ -14,7 +15,7 @@ from homeassistant.config_entries import SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util
from . import ENTRY_CONFIG
from . import ENTRY_CONFIG, ENTRY_CONFIG_OLD_CONFIG
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
@ -35,9 +36,9 @@ async def test_setup_entry(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
version=2,
version=3,
unique_id="trafikverket_camera-1234",
title="Test location",
title="Test Camera",
)
entry.add_to_hass(hass)
@ -67,9 +68,9 @@ async def test_unload_entry(
source=SOURCE_USER,
data=ENTRY_CONFIG,
entry_id="1",
version=2,
version=3,
unique_id="trafikverket_camera-1234",
title="Test location",
title="Test Camera",
)
entry.add_to_hass(hass)
@ -99,7 +100,7 @@ async def test_migrate_entry(
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data=ENTRY_CONFIG,
data=ENTRY_CONFIG_OLD_CONFIG,
entry_id="1",
unique_id="trafikverket_camera-Test location",
title="Test location",
@ -114,15 +115,31 @@ async def test_migrate_entry(
await hass.async_block_till_done()
assert entry.state is config_entries.ConfigEntryState.LOADED
assert entry.version == 2
assert entry.version == 3
assert entry.unique_id == "trafikverket_camera-1234"
assert len(mock_tvt_camera.mock_calls) == 2
assert entry.data == ENTRY_CONFIG
assert len(mock_tvt_camera.mock_calls) == 3
@pytest.mark.parametrize(
("version", "unique_id"),
[
(
1,
"trafikverket_camera-Test location",
),
(
2,
"trafikverket_camera-1234",
),
],
)
async def test_migrate_entry_fails_with_error(
hass: HomeAssistant,
get_camera: CameraInfo,
aioclient_mock: AiohttpClientMocker,
version: int,
unique_id: str,
) -> None:
"""Test migrate entry fails with api error."""
aioclient_mock.get(
@ -132,9 +149,10 @@ async def test_migrate_entry_fails_with_error(
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data=ENTRY_CONFIG,
data=ENTRY_CONFIG_OLD_CONFIG,
entry_id="1",
unique_id="trafikverket_camera-Test location",
version=version,
unique_id=unique_id,
title="Test location",
)
entry.add_to_hass(hass)
@ -147,14 +165,29 @@ async def test_migrate_entry_fails_with_error(
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 entry.version == version
assert entry.unique_id == unique_id
assert len(mock_tvt_camera.mock_calls) == 1
@pytest.mark.parametrize(
("version", "unique_id"),
[
(
1,
"trafikverket_camera-Test location",
),
(
2,
"trafikverket_camera-1234",
),
],
)
async def test_migrate_entry_fails_no_id(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
version: int,
unique_id: str,
) -> None:
"""Test migrate entry fails, camera returns no id."""
aioclient_mock.get(
@ -164,9 +197,10 @@ async def test_migrate_entry_fails_no_id(
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_USER,
data=ENTRY_CONFIG,
data=ENTRY_CONFIG_OLD_CONFIG,
entry_id="1",
unique_id="trafikverket_camera-Test location",
version=version,
unique_id=unique_id,
title="Test location",
)
entry.add_to_hass(hass)
@ -195,8 +229,8 @@ async def test_migrate_entry_fails_no_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 entry.version == version
assert entry.unique_id == unique_id
assert len(mock_tvt_camera.mock_calls) == 1
@ -214,7 +248,7 @@ async def test_no_migration_needed(
domain=DOMAIN,
source=SOURCE_USER,
data=ENTRY_CONFIG,
version=2,
version=3,
entry_id="1234",
unique_id="trafikverket_camera-1234",
title="Test location",

View File

@ -24,7 +24,7 @@ async def test_exclude_attributes(
get_camera: CameraInfo,
) -> None:
"""Test camera has description and location excluded from recording."""
state1 = hass.states.get("camera.test_location")
state1 = hass.states.get("camera.test_camera")
assert state1.state == "idle"
assert state1.attributes["description"] == "Test Camera for testing"
assert state1.attributes["location"] == "Test location"
@ -39,10 +39,10 @@ async def test_exclude_attributes(
hass.states.async_entity_ids(),
)
assert len(states) == 8
assert states.get("camera.test_location")
assert states.get("camera.test_camera")
for entity_states in states.values():
for state in entity_states:
if state.entity_id == "camera.test_location":
if state.entity_id == "camera.test_camera":
assert "location" not in state.attributes
assert "description" not in state.attributes
assert "type" in state.attributes

View File

@ -15,15 +15,15 @@ async def test_sensor(
) -> None:
"""Test the Trafikverket Camera sensor."""
state = hass.states.get("sensor.test_location_direction")
state = hass.states.get("sensor.test_camera_direction")
assert state.state == "180"
state = hass.states.get("sensor.test_location_modified")
state = hass.states.get("sensor.test_camera_modified")
assert state.state == "2022-04-04T04:04:04+00:00"
state = hass.states.get("sensor.test_location_photo_time")
state = hass.states.get("sensor.test_camera_photo_time")
assert state.state == "2022-04-04T04:04:04+00:00"
state = hass.states.get("sensor.test_location_photo_url")
state = hass.states.get("sensor.test_camera_photo_url")
assert state.state == "https://www.testurl.com/test_photo.jpg"
state = hass.states.get("sensor.test_location_status")
state = hass.states.get("sensor.test_camera_status")
assert state.state == "Running"
state = hass.states.get("sensor.test_location_camera_type")
state = hass.states.get("sensor.test_camera_camera_type")
assert state.state == "Road"