From 9c11b0aa8993b5d6ad82046c039f1c628ab2d227 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Thu, 20 Jan 2022 21:32:28 +0100 Subject: [PATCH] Add diagnostics to Renault integration (#64531) * Add diagnostics to Renault * Add diagnostic tests * Add device diagnostics * Include vehicle information in main diag Co-authored-by: epenet --- .../components/renault/diagnostics.py | 69 +++++++ tests/components/renault/test_diagnostics.py | 176 ++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 homeassistant/components/renault/diagnostics.py create mode 100644 tests/components/renault/test_diagnostics.py diff --git a/homeassistant/components/renault/diagnostics.py b/homeassistant/components/renault/diagnostics.py new file mode 100644 index 00000000000..a0c94f3b3ae --- /dev/null +++ b/homeassistant/components/renault/diagnostics.py @@ -0,0 +1,69 @@ +"""Diagnostics support for Renault.""" +from __future__ import annotations + +from types import MappingProxyType +from typing import Any + +from homeassistant.components.diagnostics import REDACTED +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceEntry + +from . import RenaultHub +from .const import CONF_KAMEREON_ACCOUNT_ID, DOMAIN + +TO_REDACT = ( + CONF_KAMEREON_ACCOUNT_ID, + CONF_PASSWORD, + CONF_USERNAME, + "radioCode", + "registrationNumber", + "vin", +) + + +@callback +def _async_redact_data(data: MappingProxyType | dict) -> dict[str, Any]: + """Redact sensitive data in a dict.""" + redacted = {**data} + + for key, value in redacted.items(): + if key in TO_REDACT: + redacted[key] = REDACTED + elif isinstance(value, dict): + redacted[key] = _async_redact_data(value) + elif isinstance(value, list): + redacted[key] = [_async_redact_data(item) for item in value] + + return redacted + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + renault_hub: RenaultHub = hass.data[DOMAIN][entry.entry_id] + + return { + "entry": { + "title": entry.title, + "data": _async_redact_data(entry.data), + }, + "vehicles": [ + _async_redact_data(vehicle.details.raw_data) + for vehicle in renault_hub.vehicles.values() + ], + } + + +async def async_get_device_diagnostics( + hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry +) -> dict: + """Return diagnostics for a device.""" + renault_hub: RenaultHub = hass.data[DOMAIN][entry.entry_id] + vin = next(iter(device.identifiers))[1] + + return { + "details": _async_redact_data(renault_hub.vehicles[vin].details.raw_data), + } diff --git a/tests/components/renault/test_diagnostics.py b/tests/components/renault/test_diagnostics.py new file mode 100644 index 00000000000..dd3ab369f05 --- /dev/null +++ b/tests/components/renault/test_diagnostics.py @@ -0,0 +1,176 @@ +"""Test Renault diagnostics.""" +import pytest + +from homeassistant.components.renault import DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from tests.common import mock_device_registry +from tests.components.diagnostics import ( + get_diagnostics_for_config_entry, + get_diagnostics_for_device, +) + +pytestmark = pytest.mark.usefixtures("patch_renault_account", "patch_get_vehicles") + +VEHICLE_DETAILS = { + "vin": "**REDACTED**", + "registrationDate": "2017-08-01", + "firstRegistrationDate": "2017-08-01", + "engineType": "5AQ", + "engineRatio": "601", + "modelSCR": "ZOE", + "deliveryCountry": {"code": "FR", "label": "FRANCE"}, + "family": {"code": "X10", "label": "FAMILLE X10", "group": "007"}, + "tcu": { + "code": "TCU0G2", + "label": "TCU VER 0 GEN 2", + "group": "E70", + }, + "navigationAssistanceLevel": { + "code": "NAV3G5", + "label": "LEVEL 3 TYPE 5 NAVIGATION", + "group": "408", + }, + "battery": { + "code": "BT4AR1", + "label": "BATTERIE BT4AR1", + "group": "968", + }, + "radioType": { + "code": "RAD37A", + "label": "RADIO 37A", + "group": "425", + }, + "registrationCountry": {"code": "FR"}, + "brand": {"label": "RENAULT"}, + "model": {"code": "X101VE", "label": "ZOE", "group": "971"}, + "gearbox": { + "code": "BVEL", + "label": "BOITE A VARIATEUR ELECTRIQUE", + "group": "427", + }, + "version": {"code": "INT MB 10R"}, + "energy": {"code": "ELEC", "label": "ELECTRIQUE", "group": "019"}, + "registrationNumber": "**REDACTED**", + "vcd": "SYTINC/SKTPOU/SAND41/FDIU1/SSESM/MAPSUP/SSCALL/SAND88/SAND90/SQKDRO/SDIFPA/FACBA2/PRLEX1/SSRCAR/CABDO2/TCU0G2/SWALBO/EVTEC1/STANDA/X10/B10/EA2/MB/ELEC/DG/TEMP/TR4X2/RV/ABS/CAREG/LAC/VT003/CPE/RET03/SPROJA/RALU16/CEAVRH/AIRBA1/SERIE/DRA/DRAP08/HARM02/ATAR/TERQG/SFBANA/KM/DPRPN/AVREPL/SSDECA/ASRESP/RDAR02/ALEVA/CACBL2/SOP02C/CTHAB2/TRNOR/LVAVIP/LVAREL/SASURV/KTGREP/SGSCHA/APL03/ALOUCC/CMAR3P/NAV3G5/RAD37A/BVEL/AUTAUG/RNORM/ISOFIX/EQPEUR/HRGM01/SDPCLV/TLFRAN/SPRODI/SAN613/SSAPEX/GENEV1/ELC1/SANCML/PE2012/PHAS1/SAN913/045KWH/BT4AR1/VEC153/X101VE/NBT017/5AQ", + "assets": [ + { + "assetType": "PICTURE", + "renditions": [ + { + "resolutionType": "ONE_MYRENAULT_LARGE", + "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FPRLEX1%2FSTANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_LARGE", + }, + { + "resolutionType": "ONE_MYRENAULT_SMALL", + "url": "https://3dv2.renault.com/ImageFromBookmark?configuration=SKTPOU%2FPRLEX1%2FSTANDA%2FB10%2FEA2%2FDG%2FVT003%2FRET03%2FRALU16%2FDRAP08%2FHARM02%2FTERQG%2FRDAR02%2FALEVA%2FSOP02C%2FTRNOR%2FLVAVIP%2FLVAREL%2FNAV3G5%2FRAD37A%2FSDPCLV%2FTLFRAN%2FGENEV1%2FSAN913%2FBT4AR1%2FNBT017&databaseId=1d514feb-93a6-4b45-8785-e11d2a6f1864&bookmarkSet=RSITE&bookmark=EXT_34_DESSUS&profile=HELIOS_OWNERSERVICES_SMALL_V2", + }, + ], + }, + { + "assetType": "PDF", + "assetRole": "GUIDE", + "title": "PDF Guide", + "description": "", + "renditions": [ + { + "url": "https://cdn.group.renault.com/ren/gb/myr/assets/x101ve/manual.pdf.asset.pdf/1558704861676.pdf" + } + ], + }, + { + "assetType": "URL", + "assetRole": "GUIDE", + "title": "e-guide", + "description": "", + "renditions": [{"url": "http://gb.e-guide.renault.com/eng/Zoe"}], + }, + { + "assetType": "VIDEO", + "assetRole": "CAR", + "title": "10 Fundamentals about getting the best out of your electric vehicle", + "description": "", + "renditions": [{"url": "39r6QEKcOM4"}], + }, + { + "assetType": "VIDEO", + "assetRole": "CAR", + "title": "Automatic Climate Control", + "description": "", + "renditions": [{"url": "Va2FnZFo_GE"}], + }, + { + "assetType": "URL", + "assetRole": "CAR", + "title": "More videos", + "description": "", + "renditions": [{"url": "https://www.youtube.com/watch?v=wfpCMkK1rKI"}], + }, + { + "assetType": "VIDEO", + "assetRole": "CAR", + "title": "Charging the battery", + "description": "", + "renditions": [{"url": "RaEad8DjUJs"}], + }, + { + "assetType": "VIDEO", + "assetRole": "CAR", + "title": "Charging the battery at a station with a flap", + "description": "", + "renditions": [{"url": "zJfd7fJWtr0"}], + }, + ], + "yearsOfMaintenance": 12, + "connectivityTechnology": "RLINK1", + "easyConnectStore": False, + "electrical": True, + "rlinkStore": False, + "deliveryDate": "2017-08-11", + "retrievedFromDhs": False, + "engineEnergyType": "ELEC", + "radioCode": "**REDACTED**", +} + + +@pytest.mark.usefixtures("fixtures_with_data") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry, hass_client +): + """Test config entry diagnostics.""" + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "entry": { + "data": { + "kamereon_account_id": "**REDACTED**", + "locale": "fr_FR", + "password": "**REDACTED**", + "username": "**REDACTED**", + }, + "title": "Mock Title", + }, + "vehicles": [VEHICLE_DETAILS], + } + + +@pytest.mark.usefixtures("fixtures_with_data") +@pytest.mark.parametrize("vehicle_type", ["zoe_40"], indirect=True) +async def test_device_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry, hass_client +): + """Test config entry diagnostics.""" + device_registry = mock_device_registry(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + device = device_registry.async_get_device({(DOMAIN, "VF1AAAAA555777999")}) + assert device is not None + + assert await get_diagnostics_for_device( + hass, hass_client, config_entry, device + ) == {"details": VEHICLE_DETAILS}