mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Add RDW Vehicle information integration (#59240)
This commit is contained in:
@ -96,6 +96,7 @@ homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.proximity.*
|
||||
homeassistant.components.rainmachine.*
|
||||
homeassistant.components.rdw.*
|
||||
homeassistant.components.recollect_waste.*
|
||||
homeassistant.components.recorder.purge
|
||||
homeassistant.components.recorder.repack
|
||||
|
@ -425,6 +425,7 @@ homeassistant/components/raincloud/* @vanstinator
|
||||
homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert
|
||||
homeassistant/components/rainmachine/* @bachya
|
||||
homeassistant/components/random/* @fabaff
|
||||
homeassistant/components/rdw/* @frenck
|
||||
homeassistant/components/recollect_waste/* @bachya
|
||||
homeassistant/components/recorder/* @home-assistant/core
|
||||
homeassistant/components/rejseplanen/* @DarkFox
|
||||
|
42
homeassistant/components/rdw/__init__.py
Normal file
42
homeassistant/components/rdw/__init__.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""Support for RDW."""
|
||||
from __future__ import annotations
|
||||
|
||||
from vehicle import RDW, Vehicle
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import CONF_LICENSE_PLATE, DOMAIN, LOGGER, SCAN_INTERVAL
|
||||
|
||||
PLATFORMS = (SENSOR_DOMAIN,)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up RDW from a config entry."""
|
||||
session = async_get_clientsession(hass)
|
||||
rdw = RDW(session=session, license_plate=entry.data[CONF_LICENSE_PLATE])
|
||||
|
||||
coordinator: DataUpdateCoordinator[Vehicle] = DataUpdateCoordinator(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=f"{DOMAIN}_APK",
|
||||
update_interval=SCAN_INTERVAL,
|
||||
update_method=rdw.vehicle,
|
||||
)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload RDW config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
return unload_ok
|
56
homeassistant/components/rdw/config_flow.py
Normal file
56
homeassistant/components/rdw/config_flow.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""Config flow to configure the RDW integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from vehicle import RDW, RDWError, RDWUnknownLicensePlateError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONF_LICENSE_PLATE, DOMAIN
|
||||
|
||||
|
||||
class RDWFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for RDW."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
session = async_get_clientsession(self.hass)
|
||||
rdw = RDW(session=session)
|
||||
try:
|
||||
vehicle = await rdw.vehicle(
|
||||
license_plate=user_input[CONF_LICENSE_PLATE]
|
||||
)
|
||||
except RDWUnknownLicensePlateError:
|
||||
errors["base"] = "unknown_license_plate"
|
||||
except RDWError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(vehicle.license_plate)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_LICENSE_PLATE],
|
||||
data={
|
||||
CONF_LICENSE_PLATE: vehicle.license_plate,
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_LICENSE_PLATE): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
14
homeassistant/components/rdw/const.py
Normal file
14
homeassistant/components/rdw/const.py
Normal file
@ -0,0 +1,14 @@
|
||||
"""Constants for the RDW integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Final
|
||||
|
||||
DOMAIN: Final = "rdw"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
SCAN_INTERVAL = timedelta(hours=1)
|
||||
|
||||
ENTRY_TYPE_SERVICE: Final = "service"
|
||||
CONF_LICENSE_PLATE: Final = "license_plate"
|
10
homeassistant/components/rdw/manifest.json
Normal file
10
homeassistant/components/rdw/manifest.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"domain": "rdw",
|
||||
"name": "RDW",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/rdw",
|
||||
"requirements": ["vehicle==0.1.0"],
|
||||
"codeowners": ["@frenck"],
|
||||
"quality_scale": "platinum",
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
103
homeassistant/components/rdw/sensor.py
Normal file
103
homeassistant/components/rdw/sensor.py
Normal file
@ -0,0 +1,103 @@
|
||||
"""Support for RDW sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
from vehicle import Vehicle
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DEVICE_CLASS_DATE,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
|
||||
from .const import CONF_LICENSE_PLATE, DOMAIN, ENTRY_TYPE_SERVICE
|
||||
|
||||
|
||||
@dataclass
|
||||
class RDWSensorEntityDescriptionMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[Vehicle], str | float | None]
|
||||
|
||||
|
||||
@dataclass
|
||||
class RDWSensorEntityDescription(
|
||||
SensorEntityDescription, RDWSensorEntityDescriptionMixin
|
||||
):
|
||||
"""Describes RDW sensor entity."""
|
||||
|
||||
|
||||
SENSORS: tuple[RDWSensorEntityDescription, ...] = (
|
||||
RDWSensorEntityDescription(
|
||||
key="apk_expiration",
|
||||
name="APK Expiration",
|
||||
device_class=DEVICE_CLASS_DATE,
|
||||
value_fn=lambda vehicle: vehicle.apk_expiration.isoformat(),
|
||||
),
|
||||
RDWSensorEntityDescription(
|
||||
key="name_registration_date",
|
||||
name="Name Registration Date",
|
||||
device_class=DEVICE_CLASS_DATE,
|
||||
value_fn=lambda vehicle: vehicle.name_registration_date.isoformat(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up RDW sensors based on a config entry."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities(
|
||||
RDWSensorEntity(
|
||||
coordinator=coordinator,
|
||||
license_plate=entry.data[CONF_LICENSE_PLATE],
|
||||
description=description,
|
||||
)
|
||||
for description in SENSORS
|
||||
)
|
||||
|
||||
|
||||
class RDWSensorEntity(CoordinatorEntity, SensorEntity):
|
||||
"""Defines an RDW sensor."""
|
||||
|
||||
entity_description: RDWSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
license_plate: str,
|
||||
description: RDWSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize RDW sensor."""
|
||||
super().__init__(coordinator=coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{license_plate}_{description.key}"
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=ENTRY_TYPE_SERVICE,
|
||||
identifiers={(DOMAIN, f"{license_plate}")},
|
||||
manufacturer=coordinator.data.brand,
|
||||
name=f"{coordinator.data.brand}: {coordinator.data.license_plate}",
|
||||
model=coordinator.data.model,
|
||||
configuration_url=f"https://ovi.rdw.nl/default.aspx?kenteken={coordinator.data.license_plate}",
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
15
homeassistant/components/rdw/strings.json
Normal file
15
homeassistant/components/rdw/strings.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"license_plate": "License plate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown_license_plate": "Unknown license plate"
|
||||
}
|
||||
}
|
||||
}
|
15
homeassistant/components/rdw/translations/en.json
Normal file
15
homeassistant/components/rdw/translations/en.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"unknown_license_plate": "Unknown license plate"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"license_plate": "License plate"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -235,6 +235,7 @@ FLOWS = [
|
||||
"rachio",
|
||||
"rainforest_eagle",
|
||||
"rainmachine",
|
||||
"rdw",
|
||||
"recollect_waste",
|
||||
"renault",
|
||||
"rfxtrx",
|
||||
|
11
mypy.ini
11
mypy.ini
@ -1067,6 +1067,17 @@ no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.rdw.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.recollect_waste.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -2365,6 +2365,9 @@ uvcclient==0.11.0
|
||||
# homeassistant.components.vallox
|
||||
vallox-websocket-api==2.8.1
|
||||
|
||||
# homeassistant.components.rdw
|
||||
vehicle==0.1.0
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2021.11.0
|
||||
|
||||
|
@ -1372,6 +1372,9 @@ url-normalize==1.4.1
|
||||
# homeassistant.components.uvc
|
||||
uvcclient==0.11.0
|
||||
|
||||
# homeassistant.components.rdw
|
||||
vehicle==0.1.0
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2021.11.0
|
||||
|
||||
|
1
tests/components/rdw/__init__.py
Normal file
1
tests/components/rdw/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the RDW integration."""
|
69
tests/components/rdw/conftest.py
Normal file
69
tests/components/rdw/conftest.py
Normal file
@ -0,0 +1,69 @@
|
||||
"""Fixtures for RDW integration tests."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from vehicle import Vehicle
|
||||
|
||||
from homeassistant.components.rdw.const import CONF_LICENSE_PLATE, DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Return the default mocked config entry."""
|
||||
return MockConfigEntry(
|
||||
title="My Car",
|
||||
domain=DOMAIN,
|
||||
data={CONF_LICENSE_PLATE: "11ZKZ3"},
|
||||
unique_id="11ZKZ3",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[None, None, None]:
|
||||
"""Mock setting up a config entry."""
|
||||
with patch("homeassistant.components.rdw.async_setup_entry", return_value=True):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_rdw_config_flow() -> Generator[None, MagicMock, None]:
|
||||
"""Return a mocked RDW client."""
|
||||
with patch(
|
||||
"homeassistant.components.rdw.config_flow.RDW", autospec=True
|
||||
) as rdw_mock:
|
||||
rdw = rdw_mock.return_value
|
||||
rdw.vehicle.return_value = Vehicle.parse_raw(load_fixture("rdw/11ZKZ3.json"))
|
||||
yield rdw
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_rdw(request: pytest.FixtureRequest) -> Generator[None, MagicMock, None]:
|
||||
"""Return a mocked WLED client."""
|
||||
fixture: str = "rdw/11ZKZ3.json"
|
||||
if hasattr(request, "param") and request.param:
|
||||
fixture = request.param
|
||||
|
||||
vehicle = Vehicle.parse_raw(load_fixture(fixture))
|
||||
with patch("homeassistant.components.rdw.RDW", autospec=True) as rdw_mock:
|
||||
rdw = rdw_mock.return_value
|
||||
rdw.vehicle.return_value = vehicle
|
||||
yield rdw
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_rdw: MagicMock
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the RDW integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_config_entry
|
52
tests/components/rdw/fixtures/11ZKZ3.json
Normal file
52
tests/components/rdw/fixtures/11ZKZ3.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"kenteken": "11ZKZ3",
|
||||
"voertuigsoort": "Personenauto",
|
||||
"merk": "SKODA",
|
||||
"handelsbenaming": "CITIGO",
|
||||
"vervaldatum_apk": "20220104",
|
||||
"datum_tenaamstelling": "20211104",
|
||||
"inrichting": "hatchback",
|
||||
"aantal_zitplaatsen": "4",
|
||||
"eerste_kleur": "GRIJS",
|
||||
"tweede_kleur": "Niet geregistreerd",
|
||||
"aantal_cilinders": "3",
|
||||
"cilinderinhoud": "999",
|
||||
"massa_ledig_voertuig": "840",
|
||||
"toegestane_maximum_massa_voertuig": "1290",
|
||||
"massa_rijklaar": "940",
|
||||
"zuinigheidslabel": "A",
|
||||
"datum_eerste_toelating": "20130104",
|
||||
"datum_eerste_afgifte_nederland": "20130104",
|
||||
"wacht_op_keuren": "Geen verstrekking in Open Data",
|
||||
"catalogusprijs": "10697",
|
||||
"wam_verzekerd": "Nee",
|
||||
"aantal_deuren": "0",
|
||||
"aantal_wielen": "4",
|
||||
"afstand_hart_koppeling_tot_achterzijde_voertuig": "0",
|
||||
"afstand_voorzijde_voertuig_tot_hart_koppeling": "0",
|
||||
"lengte": "356",
|
||||
"breedte": "0",
|
||||
"europese_voertuigcategorie": "M1",
|
||||
"plaats_chassisnummer": "r. motorruimte",
|
||||
"technische_max_massa_voertuig": "1290",
|
||||
"type": "AA",
|
||||
"typegoedkeuringsnummer": "e13*2007/46*1169*05",
|
||||
"variant": "ABCHYA",
|
||||
"uitvoering": "FM5FM5CF0037MGVR2N1FA1SK",
|
||||
"volgnummer_wijziging_eu_typegoedkeuring": "0",
|
||||
"vermogen_massarijklaar": "0.05",
|
||||
"wielbasis": "241",
|
||||
"export_indicator": "Nee",
|
||||
"openstaande_terugroepactie_indicator": "Nee",
|
||||
"maximum_massa_samenstelling": "0",
|
||||
"aantal_rolstoelplaatsen": "0",
|
||||
"jaar_laatste_registratie_tellerstand": "2021",
|
||||
"tellerstandoordeel": "Logisch",
|
||||
"code_toelichting_tellerstandoordeel": "00",
|
||||
"tenaamstellen_mogelijk": "Ja",
|
||||
"api_gekentekende_voertuigen_assen": "https://opendata.rdw.nl/resource/3huj-srit.json",
|
||||
"api_gekentekende_voertuigen_brandstof": "https://opendata.rdw.nl/resource/8ys7-d773.json",
|
||||
"api_gekentekende_voertuigen_carrosserie": "https://opendata.rdw.nl/resource/vezc-m2t6.json",
|
||||
"api_gekentekende_voertuigen_carrosserie_specifiek": "https://opendata.rdw.nl/resource/jhie-znh9.json",
|
||||
"api_gekentekende_voertuigen_voertuigklasse": "https://opendata.rdw.nl/resource/kmfi-hrps.json"
|
||||
}
|
92
tests/components/rdw/test_config_flow.py
Normal file
92
tests/components/rdw/test_config_flow.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""Tests for the RDW config flow."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from vehicle.exceptions import RDWConnectionError, RDWUnknownLicensePlateError
|
||||
|
||||
from homeassistant.components.rdw.const import CONF_LICENSE_PLATE, DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
|
||||
|
||||
|
||||
async def test_full_user_flow(
|
||||
hass: HomeAssistant, mock_rdw_config_flow: MagicMock, mock_setup_entry: MagicMock
|
||||
) -> None:
|
||||
"""Test the full user configuration flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result.get("type") == RESULT_TYPE_FORM
|
||||
assert result.get("step_id") == SOURCE_USER
|
||||
assert "flow_id" in result
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_LICENSE_PLATE: "11-ZKZ-3",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2.get("title") == "11-ZKZ-3"
|
||||
assert result2.get("data") == {CONF_LICENSE_PLATE: "11ZKZ3"}
|
||||
|
||||
|
||||
async def test_full_flow_with_authentication_error(
|
||||
hass: HomeAssistant, mock_rdw_config_flow: MagicMock, mock_setup_entry: MagicMock
|
||||
) -> None:
|
||||
"""Test the full user configuration flow with incorrect license plate.
|
||||
|
||||
This tests tests a full config flow, with a case the user enters an invalid
|
||||
license plate, but recover by entering the correct one.
|
||||
"""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result.get("type") == RESULT_TYPE_FORM
|
||||
assert result.get("step_id") == SOURCE_USER
|
||||
assert "flow_id" in result
|
||||
|
||||
mock_rdw_config_flow.vehicle.side_effect = RDWUnknownLicensePlateError
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_LICENSE_PLATE: "0001TJ",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2.get("type") == RESULT_TYPE_FORM
|
||||
assert result2.get("step_id") == SOURCE_USER
|
||||
assert result2.get("errors") == {"base": "unknown_license_plate"}
|
||||
assert "flow_id" in result2
|
||||
|
||||
mock_rdw_config_flow.vehicle.side_effect = None
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
user_input={
|
||||
CONF_LICENSE_PLATE: "11-ZKZ-3",
|
||||
},
|
||||
)
|
||||
|
||||
assert result3.get("type") == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result3.get("title") == "11-ZKZ-3"
|
||||
assert result3.get("data") == {CONF_LICENSE_PLATE: "11ZKZ3"}
|
||||
|
||||
|
||||
async def test_connection_error(
|
||||
hass: HomeAssistant, mock_rdw_config_flow: MagicMock
|
||||
) -> None:
|
||||
"""Test API connection error."""
|
||||
mock_rdw_config_flow.vehicle.side_effect = RDWConnectionError
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
data={CONF_LICENSE_PLATE: "0001TJ"},
|
||||
)
|
||||
|
||||
assert result.get("type") == RESULT_TYPE_FORM
|
||||
assert result.get("errors") == {"base": "cannot_connect"}
|
45
tests/components/rdw/test_init.py
Normal file
45
tests/components/rdw/test_init.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""Tests for the RDW integration."""
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from homeassistant.components.rdw.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_load_unload_config_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_rdw: AsyncMock,
|
||||
) -> None:
|
||||
"""Test the RDW configuration entry loading/unloading."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not hass.data.get(DOMAIN)
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
@patch(
|
||||
"homeassistant.components.rdw.RDW.vehicle",
|
||||
side_effect=RuntimeError,
|
||||
)
|
||||
async def test_config_entry_not_ready(
|
||||
mock_request: MagicMock,
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the RDW configuration entry not ready."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_request.call_count == 1
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
61
tests/components/rdw/test_sensor.py
Normal file
61
tests/components/rdw/test_sensor.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""Tests for the sensors provided by the RDW integration."""
|
||||
from homeassistant.components.rdw.const import DOMAIN, ENTRY_TYPE_SERVICE
|
||||
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_FRIENDLY_NAME,
|
||||
ATTR_ICON,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
DEVICE_CLASS_DATE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_vehicle_sensors(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the RDW vehicle sensors."""
|
||||
entity_registry = er.async_get(hass)
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
state = hass.states.get("sensor.apk_expiration")
|
||||
entry = entity_registry.async_get("sensor.apk_expiration")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "11ZKZ3_apk_expiration"
|
||||
assert state.state == "2022-01-04"
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "APK Expiration"
|
||||
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_DATE
|
||||
assert ATTR_ICON not in state.attributes
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||
|
||||
state = hass.states.get("sensor.name_registration_date")
|
||||
entry = entity_registry.async_get("sensor.name_registration_date")
|
||||
assert entry
|
||||
assert state
|
||||
assert entry.unique_id == "11ZKZ3_name_registration_date"
|
||||
assert state.state == "2021-11-04"
|
||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Name Registration Date"
|
||||
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_DATE
|
||||
assert ATTR_ICON not in state.attributes
|
||||
assert ATTR_STATE_CLASS not in state.attributes
|
||||
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||
|
||||
assert entry.device_id
|
||||
device_entry = device_registry.async_get(entry.device_id)
|
||||
assert device_entry
|
||||
assert device_entry.identifiers == {(DOMAIN, "11ZKZ3")}
|
||||
assert device_entry.manufacturer == "Skoda"
|
||||
assert device_entry.name == "Skoda: 11ZKZ3"
|
||||
assert device_entry.entry_type == ENTRY_TYPE_SERVICE
|
||||
assert device_entry.model == "Citigo"
|
||||
assert (
|
||||
device_entry.configuration_url
|
||||
== "https://ovi.rdw.nl/default.aspx?kenteken=11ZKZ3"
|
||||
)
|
||||
assert not device_entry.sw_version
|
Reference in New Issue
Block a user