1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 00:20:30 +01:00

Add skeleton with repair issue to bmw integration (#166983)

Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Ariel Ebersberger
2026-03-31 20:31:45 +02:00
committed by Franck Nijhof
parent 9650aea6a1
commit 12dc33eabc
7 changed files with 150 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
"""The BMW Connected Drive integration."""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
DOMAIN = "bmw_connected_drive"
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up BMW Connected Drive from a config entry."""
ir.async_create_issue(
hass,
DOMAIN,
DOMAIN,
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="integration_removed",
translation_placeholders={
"entries": "/config/integrations/integration/bmw_connected_drive",
"custom_component_url": "https://github.com/kvanbiesen/bmw-cardata-ha",
},
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return True
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Remove a config entry."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
# Remove any remaining disabled or ignored entries
for _entry in hass.config_entries.async_entries(DOMAIN):
hass.async_create_task(hass.config_entries.async_remove(_entry.entry_id))

View File

@@ -0,0 +1,9 @@
"""The BMW Connected Drive integration config flow."""
from homeassistant.config_entries import ConfigFlow
from . import DOMAIN
class BMWConnectedDriveConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for BMW Connected Drive."""

View File

@@ -0,0 +1,10 @@
{
"domain": "bmw_connected_drive",
"name": "BMW Connected Drive",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"integration_type": "system",
"iot_class": "cloud_polling",
"quality_scale": "legacy",
"requirements": []
}

View File

@@ -0,0 +1,8 @@
{
"issues": {
"integration_removed": {
"description": "The BMW Connected Drive integration has been removed from Home Assistant.\n\nIn September 2025, BMW blocked third-party access to their servers by adding additional security measures. For EU-registered cars, a community-developed [custom component]({custom_component_url}) using BMW's CarData API is available as an alternative.\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing BMW Connected Drive integration entries]({entries}).",
"title": "The BMW Connected Drive integration has been removed"
}
}
}

View File

@@ -212,6 +212,7 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
"bluetooth",
"bluetooth_adapters",
"bluetooth_le_tracker",
"bmw_connected_drive",
"bond",
"bosch_shc",
"braviatv",
@@ -1183,6 +1184,7 @@ INTEGRATIONS_WITHOUT_SCALE = [
"bluetooth",
"bluetooth_adapters",
"bluetooth_le_tracker",
"bmw_connected_drive",
"bond",
"bosch_shc",
"braviatv",

View File

@@ -0,0 +1 @@
"""Tests for the BMW Connected Drive integration."""

View File

@@ -0,0 +1,79 @@
"""Tests for the BMW Connected Drive integration."""
from homeassistant.components.bmw_connected_drive import DOMAIN
from homeassistant.config_entries import (
SOURCE_IGNORE,
ConfigEntryDisabler,
ConfigEntryState,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from tests.common import MockConfigEntry
async def test_bmw_connected_drive_repair_issue(
hass: HomeAssistant, issue_registry: ir.IssueRegistry
) -> None:
"""Test the BMW Connected Drive configuration entry loading/unloading handles the repair."""
config_entry_1 = MockConfigEntry(
title="Example 1",
domain=DOMAIN,
)
config_entry_1.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry_1.entry_id)
await hass.async_block_till_done()
assert config_entry_1.state is ConfigEntryState.LOADED
# Add a second one
config_entry_2 = MockConfigEntry(
title="Example 2",
domain=DOMAIN,
)
config_entry_2.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry_2.entry_id)
await hass.async_block_till_done()
assert config_entry_2.state is ConfigEntryState.LOADED
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
# Add an ignored entry
config_entry_3 = MockConfigEntry(
source=SOURCE_IGNORE,
domain=DOMAIN,
)
config_entry_3.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry_3.entry_id)
await hass.async_block_till_done()
assert config_entry_3.state is ConfigEntryState.NOT_LOADED
# Add a disabled entry
config_entry_4 = MockConfigEntry(
disabled_by=ConfigEntryDisabler.USER,
domain=DOMAIN,
)
config_entry_4.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry_4.entry_id)
await hass.async_block_till_done()
assert config_entry_4.state is ConfigEntryState.NOT_LOADED
# Remove the first one
await hass.config_entries.async_remove(config_entry_1.entry_id)
await hass.async_block_till_done()
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
assert config_entry_2.state is ConfigEntryState.LOADED
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
# Remove the second one
await hass.config_entries.async_remove(config_entry_2.entry_id)
await hass.async_block_till_done()
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
assert config_entry_2.state is ConfigEntryState.NOT_LOADED
assert issue_registry.async_get_issue(DOMAIN, DOMAIN) is None
# Check the ignored and disabled entries are removed
assert not hass.config_entries.async_entries(DOMAIN)