Add unique IDs to config entries for Teslemetry (#115616)
* Add basic UID * Add Unique IDs * Add debug message * Readd debug message * Minor bump config version * Ruff * Rework migration * Fix migration return * Review feedback * Add test for v2
This commit is contained in:
parent
a515562a11
commit
dac661831e
6 changed files with 128 additions and 26 deletions
|
@ -18,7 +18,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
|||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
|
||||
from .const import DOMAIN, MODELS
|
||||
from .const import DOMAIN, LOGGER, MODELS
|
||||
from .coordinator import (
|
||||
TeslemetryEnergySiteInfoCoordinator,
|
||||
TeslemetryEnergySiteLiveCoordinator,
|
||||
|
@ -153,3 +153,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
|||
async def async_unload_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -> bool:
|
||||
"""Unload Teslemetry Config."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Migrate config entry."""
|
||||
if config_entry.version > 1:
|
||||
return False
|
||||
|
||||
if config_entry.version == 1 and config_entry.minor_version < 2:
|
||||
# Add unique_id to existing entry
|
||||
teslemetry = Teslemetry(
|
||||
session=async_get_clientsession(hass),
|
||||
access_token=config_entry.data[CONF_ACCESS_TOKEN],
|
||||
)
|
||||
try:
|
||||
metadata = await teslemetry.metadata()
|
||||
except TeslaFleetError as e:
|
||||
LOGGER.error(e.message)
|
||||
return False
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, unique_id=metadata["uid"], version=1, minor_version=2
|
||||
)
|
||||
return True
|
||||
|
|
|
@ -31,6 +31,7 @@ class TeslemetryConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
"""Config Teslemetry API connection."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
_entry: ConfigEntry | None = None
|
||||
|
||||
async def async_auth(self, user_input: Mapping[str, Any]) -> dict[str, str]:
|
||||
|
@ -40,7 +41,7 @@ class TeslemetryConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
access_token=user_input[CONF_ACCESS_TOKEN],
|
||||
)
|
||||
try:
|
||||
await teslemetry.test()
|
||||
metadata = await teslemetry.metadata()
|
||||
except InvalidToken:
|
||||
return {CONF_ACCESS_TOKEN: "invalid_access_token"}
|
||||
except SubscriptionRequired:
|
||||
|
@ -50,6 +51,8 @@ class TeslemetryConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
except TeslaFleetError as e:
|
||||
LOGGER.error(e)
|
||||
return {"base": "unknown"}
|
||||
|
||||
await self.async_set_unique_id(metadata["uid"])
|
||||
return {}
|
||||
|
||||
async def async_step_user(
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Account is already configured"
|
||||
},
|
||||
"error": {
|
||||
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",
|
||||
"subscription_required": "Subscription required, please visit {short_url}",
|
||||
|
|
|
@ -18,8 +18,7 @@ async def setup_platform(hass: HomeAssistant, platforms: list[Platform] | None =
|
|||
"""Set up the Teslemetry platform."""
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=CONFIG,
|
||||
domain=DOMAIN, data=CONFIG, minor_version=2, unique_id="abc-123"
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ COMMAND_ERRORS = (COMMAND_REASON, COMMAND_NOREASON, COMMAND_ERROR, COMMAND_NOERR
|
|||
RESPONSE_OK = {"response": {}, "error": None}
|
||||
|
||||
METADATA = {
|
||||
"uid": "abc-123",
|
||||
"region": "NA",
|
||||
"scopes": [
|
||||
"openid",
|
||||
|
@ -44,6 +45,7 @@ METADATA = {
|
|||
],
|
||||
}
|
||||
METADATA_NOSCOPE = {
|
||||
"uid": "abc-123",
|
||||
"region": "NA",
|
||||
"scopes": ["openid", "offline_access", "vehicle_device_data"],
|
||||
}
|
||||
|
|
|
@ -12,26 +12,18 @@ from tesla_fleet_api.exceptions import (
|
|||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.teslemetry.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from .const import CONFIG
|
||||
from .const import CONFIG, METADATA
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
BAD_CONFIG = {CONF_ACCESS_TOKEN: "bad_access_token"}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_test():
|
||||
"""Mock Teslemetry api class."""
|
||||
with patch(
|
||||
"homeassistant.components.teslemetry.Teslemetry.test", return_value=True
|
||||
) as mock_test:
|
||||
yield mock_test
|
||||
|
||||
|
||||
async def test_form(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
|
@ -67,14 +59,16 @@ async def test_form(
|
|||
(TeslaFleetError, {"base": "unknown"}),
|
||||
],
|
||||
)
|
||||
async def test_form_errors(hass: HomeAssistant, side_effect, error, mock_test) -> None:
|
||||
async def test_form_errors(
|
||||
hass: HomeAssistant, side_effect, error, mock_metadata
|
||||
) -> None:
|
||||
"""Test errors are handled."""
|
||||
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
mock_test.side_effect = side_effect
|
||||
mock_metadata.side_effect = side_effect
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
CONFIG,
|
||||
|
@ -84,7 +78,7 @@ async def test_form_errors(hass: HomeAssistant, side_effect, error, mock_test) -
|
|||
assert result2["errors"] == error
|
||||
|
||||
# Complete the flow
|
||||
mock_test.side_effect = None
|
||||
mock_metadata.side_effect = None
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
CONFIG,
|
||||
|
@ -92,12 +86,11 @@ async def test_form_errors(hass: HomeAssistant, side_effect, error, mock_test) -
|
|||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_reauth(hass: HomeAssistant, mock_test) -> None:
|
||||
async def test_reauth(hass: HomeAssistant, mock_metadata) -> None:
|
||||
"""Test reauth flow."""
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=BAD_CONFIG,
|
||||
domain=DOMAIN, data=BAD_CONFIG, minor_version=2, unique_id="abc-123"
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
|
@ -124,7 +117,7 @@ async def test_reauth(hass: HomeAssistant, mock_test) -> None:
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(mock_test.mock_calls) == 1
|
||||
assert len(mock_metadata.mock_calls) == 1
|
||||
|
||||
assert result2["type"] is FlowResultType.ABORT
|
||||
assert result2["reason"] == "reauth_successful"
|
||||
|
@ -141,14 +134,13 @@ async def test_reauth(hass: HomeAssistant, mock_test) -> None:
|
|||
],
|
||||
)
|
||||
async def test_reauth_errors(
|
||||
hass: HomeAssistant, mock_test, side_effect, error
|
||||
hass: HomeAssistant, mock_metadata, side_effect, error
|
||||
) -> None:
|
||||
"""Test reauth flows that fail."""
|
||||
|
||||
# Start the reauth
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=BAD_CONFIG,
|
||||
domain=DOMAIN, data=BAD_CONFIG, minor_version=2, unique_id="abc-123"
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
|
@ -162,7 +154,7 @@ async def test_reauth_errors(
|
|||
data=BAD_CONFIG,
|
||||
)
|
||||
|
||||
mock_test.side_effect = side_effect
|
||||
mock_metadata.side_effect = side_effect
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
BAD_CONFIG,
|
||||
|
@ -173,7 +165,7 @@ async def test_reauth_errors(
|
|||
assert result2["errors"] == error
|
||||
|
||||
# Complete the flow
|
||||
mock_test.side_effect = None
|
||||
mock_metadata.side_effect = None
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
CONFIG,
|
||||
|
@ -182,3 +174,83 @@ async def test_reauth_errors(
|
|||
assert result3["type"] is FlowResultType.ABORT
|
||||
assert result3["reason"] == "reauth_successful"
|
||||
assert mock_entry.data == CONFIG
|
||||
|
||||
|
||||
async def test_unique_id_abort(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test duplicate unique ID in config."""
|
||||
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=CONFIG
|
||||
)
|
||||
assert result1["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
# Setup a duplicate
|
||||
result2 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=CONFIG
|
||||
)
|
||||
assert result2["type"] is FlowResultType.ABORT
|
||||
|
||||
|
||||
async def test_migrate_from_1_1(hass: HomeAssistant, mock_metadata) -> None:
|
||||
"""Test config migration."""
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
version=1,
|
||||
minor_version=1,
|
||||
unique_id=None,
|
||||
data=CONFIG,
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entry = hass.config_entries.async_get_entry(mock_entry.entry_id)
|
||||
assert entry.version == 1
|
||||
assert entry.minor_version == 2
|
||||
assert entry.unique_id == METADATA["uid"]
|
||||
|
||||
|
||||
async def test_migrate_error_from_1_1(hass: HomeAssistant, mock_metadata) -> None:
|
||||
"""Test config migration handles errors."""
|
||||
|
||||
mock_metadata.side_effect = TeslaFleetError
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
version=1,
|
||||
minor_version=1,
|
||||
unique_id=None,
|
||||
data=CONFIG,
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entry = hass.config_entries.async_get_entry(mock_entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.MIGRATION_ERROR
|
||||
|
||||
|
||||
async def test_migrate_error_from_future(hass: HomeAssistant, mock_metadata) -> None:
|
||||
"""Test a future version isn't migrated."""
|
||||
|
||||
mock_metadata.side_effect = TeslaFleetError
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
version=2,
|
||||
minor_version=1,
|
||||
unique_id="abc-123",
|
||||
data=CONFIG,
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entry = hass.config_entries.async_get_entry(mock_entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.MIGRATION_ERROR
|
||||
|
|
Loading…
Add table
Reference in a new issue