Skip to content

Commit 18833a1

Browse files
authored
Let AuthenticationRequired also trigger the reauth flow in MusicAssistant (home-assistant#157580)
1 parent 2631c77 commit 18833a1

File tree

4 files changed

+116
-8
lines changed

4 files changed

+116
-8
lines changed

homeassistant/components/music_assistant/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from music_assistant_models.errors import (
1919
ActionUnavailable,
2020
AuthenticationFailed,
21+
AuthenticationRequired,
2122
InvalidToken,
2223
MusicAssistantError,
2324
)
@@ -99,7 +100,7 @@ async def async_setup_entry( # noqa: C901
99100
translation_key="invalid_server_version",
100101
)
101102
raise ConfigEntryNotReady(f"Invalid server version: {err}") from err
102-
except (AuthenticationFailed, InvalidToken) as err:
103+
except (AuthenticationRequired, AuthenticationFailed, InvalidToken) as err:
103104
raise ConfigEntryAuthFailed(
104105
f"Authentication failed for {mass_url}: {err}"
105106
) from err

homeassistant/components/music_assistant/config_flow.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
from music_assistant_models.errors import AuthenticationFailed, InvalidToken
1818
import voluptuous as vol
1919

20-
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
20+
from homeassistant.config_entries import (
21+
SOURCE_REAUTH,
22+
ConfigEntryState,
23+
ConfigFlow,
24+
ConfigFlowResult,
25+
)
2126
from homeassistant.const import CONF_URL
2227
from homeassistant.core import HomeAssistant
2328
from homeassistant.helpers import aiohttp_client
@@ -165,10 +170,23 @@ async def async_step_hassio(
165170
self.token = discovery_info.config["auth_token"]
166171

167172
self.server_info = server_info
168-
await self.async_set_unique_id(server_info.server_id)
169-
self._abort_if_unique_id_configured(
170-
updates={CONF_URL: self.url, CONF_TOKEN: self.token}
171-
)
173+
174+
# Check if there's an existing entry
175+
if entry := await self.async_set_unique_id(server_info.server_id):
176+
# Update the entry with new URL and token
177+
if self.hass.config_entries.async_update_entry(
178+
entry, data={**entry.data, CONF_URL: self.url, CONF_TOKEN: self.token}
179+
):
180+
# Reload the entry if it's in a state that can be reloaded
181+
if entry.state in (
182+
ConfigEntryState.LOADED,
183+
ConfigEntryState.SETUP_ERROR,
184+
ConfigEntryState.SETUP_RETRY,
185+
):
186+
self.hass.config_entries.async_schedule_reload(entry.entry_id)
187+
188+
# Abort since entry already exists
189+
return self.async_abort(reason="already_configured")
172190

173191
return await self.async_step_hassio_confirm()
174192

tests/components/music_assistant/test_config_flow.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
SOURCE_REAUTH,
3333
SOURCE_USER,
3434
SOURCE_ZEROCONF,
35+
ConfigEntryState,
3536
)
3637
from homeassistant.core import HomeAssistant
3738
from homeassistant.data_entry_flow import FlowResultType
@@ -465,6 +466,56 @@ async def test_hassio_flow_duplicate(
465466
assert result["reason"] == "already_configured"
466467

467468

469+
async def test_hassio_flow_updates_failed_entry_and_reloads(
470+
hass: HomeAssistant,
471+
mock_get_server_info: AsyncMock,
472+
) -> None:
473+
"""Test hassio discovery updates entry in SETUP_ERROR state and schedules reload."""
474+
# Create an entry with old URL and token
475+
failed_entry = MockConfigEntry(
476+
domain=DOMAIN,
477+
title="Music Assistant",
478+
data={CONF_URL: "http://old-url:8094", CONF_TOKEN: "old_token"},
479+
unique_id="1234",
480+
)
481+
failed_entry.add_to_hass(hass)
482+
483+
# First, setup the entry with invalid auth to get it into SETUP_ERROR state
484+
with patch(
485+
"homeassistant.components.music_assistant.MusicAssistantClient"
486+
) as mock_client:
487+
mock_client.return_value.connect.side_effect = AuthenticationFailed(
488+
"Invalid token"
489+
)
490+
await hass.config_entries.async_setup(failed_entry.entry_id)
491+
await hass.async_block_till_done()
492+
493+
# Verify entry is in SETUP_ERROR state
494+
assert failed_entry.state is ConfigEntryState.SETUP_ERROR
495+
496+
# Now trigger hassio discovery with valid token
497+
# Mock async_schedule_reload to prevent actual reload attempt
498+
with patch.object(
499+
hass.config_entries, "async_schedule_reload"
500+
) as mock_schedule_reload:
501+
result = await hass.config_entries.flow.async_init(
502+
DOMAIN,
503+
context={"source": SOURCE_HASSIO},
504+
data=HASSIO_DATA,
505+
)
506+
await hass.async_block_till_done()
507+
508+
assert result["type"] is FlowResultType.ABORT
509+
assert result["reason"] == "already_configured"
510+
511+
# Verify the entry was updated with new URL and token
512+
assert failed_entry.data[CONF_URL] == "http://addon-music-assistant:8094"
513+
assert failed_entry.data[CONF_TOKEN] == "test_token"
514+
515+
# Verify reload was scheduled
516+
mock_schedule_reload.assert_called_once_with(failed_entry.entry_id)
517+
518+
468519
@pytest.mark.parametrize(
469520
("exception", "error_reason"),
470521
[

tests/components/music_assistant/test_init.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,24 @@
55
from unittest.mock import AsyncMock, MagicMock
66

77
from music_assistant_models.enums import EventType
8-
from music_assistant_models.errors import ActionUnavailable
8+
from music_assistant_models.errors import ActionUnavailable, AuthenticationRequired
99

1010
from homeassistant.components.music_assistant.const import (
1111
ATTR_CONF_EXPOSE_PLAYER_TO_HA,
1212
DOMAIN,
1313
)
14+
from homeassistant.config_entries import ConfigEntryState
1415
from homeassistant.core import HomeAssistant
15-
from homeassistant.helpers import device_registry as dr, entity_registry as er
16+
from homeassistant.helpers import (
17+
device_registry as dr,
18+
entity_registry as er,
19+
issue_registry as ir,
20+
)
1621
from homeassistant.setup import async_setup_component
1722

1823
from .common import setup_integration_from_fixtures, trigger_subscription_callback
1924

25+
from tests.common import MockConfigEntry
2026
from tests.typing import WebSocketGenerator
2127

2228

@@ -151,3 +157,35 @@ async def test_player_config_expose_to_ha_toggle(
151157
assert entity_registry.async_get(entity_id)
152158
device_entry = device_registry.async_get_device({(DOMAIN, player_id)})
153159
assert device_entry
160+
161+
162+
async def test_authentication_required_triggers_reauth(
163+
hass: HomeAssistant,
164+
music_assistant_client: MagicMock,
165+
) -> None:
166+
"""Test that AuthenticationRequired exception triggers reauth flow."""
167+
# Create a config entry
168+
config_entry = MockConfigEntry(
169+
domain=DOMAIN,
170+
title="Music Assistant",
171+
data={"url": "http://localhost:8095", "token": "old_token"},
172+
unique_id="test_server_id",
173+
)
174+
config_entry.add_to_hass(hass)
175+
176+
# Mock the client to raise AuthenticationRequired during connect
177+
music_assistant_client.connect.side_effect = AuthenticationRequired(
178+
"Authentication required"
179+
)
180+
181+
# Try to set up the integration
182+
await hass.config_entries.async_setup(config_entry.entry_id)
183+
await hass.async_block_till_done()
184+
185+
# Verify the entry is in SETUP_ERROR state (auth failed)
186+
assert config_entry.state is ConfigEntryState.SETUP_ERROR
187+
188+
# Verify a reauth repair issue was created
189+
issue_reg = ir.async_get(hass)
190+
issue_id = f"config_entry_reauth_{DOMAIN}_{config_entry.entry_id}"
191+
assert issue_reg.async_get_issue("homeassistant", issue_id)

0 commit comments

Comments
 (0)