fix: commit G3a wiring lost in the amend cycle — listener removal, update-only host path, reauth via the current API (stub surface includes async_update_reload_and_abort)
This commit is contained in:
1 parent
bcb8b962e7
commit
086c089cc1
5 files changed
+60
-18
No files matched your search
@@ -114,8 +114,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -184,6 +182,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
||||||
"""Handle options update."""
|
|
||||||
await hass.config_entries.async_reload(entry.entry_id)
|
|
||||||
@@ -323,10 +323,12 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
# host — safe to follow the device address on discovery
|
# host — safe to follow the device address on discovery
|
||||||
options[CONF_ADB_HOST] = host
|
options[CONF_ADB_HOST] = host
|
||||||
if options != entry.options:
|
if options != entry.options:
|
||||||
|
# t29 G3a: the deprecated update+schedule-reload combo is
|
||||||
|
# gone (2026.12 error) — the entry updates ONLY here; the
|
||||||
|
# manager reads the host options live on the next cycle.
|
||||||
self.hass.config_entries.async_update_entry(
|
self.hass.config_entries.async_update_entry(
|
||||||
entry, options=options
|
entry, options=options
|
||||||
)
|
)
|
||||||
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
|
||||||
return self.async_abort(reason="already_configured")
|
return self.async_abort(reason="already_configured")
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
@@ -457,17 +459,21 @@ class NvidiaShieldConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _finish_reauth(self):
|
def _finish_reauth(self):
|
||||||
"""Reauth pairing done: update options, schedule the reload, abort."""
|
"""Reauth pairing done: update + reload + abort via the current API (G3a).
|
||||||
entry = self._reauth_entry
|
|
||||||
self.hass.config_entries.async_update_entry(
|
The deprecated update-entry + schedule-reload boomerang listener is
|
||||||
|
gone (2026.12 treats it as an error); the current config-entries
|
||||||
|
contract is async_update_reload_and_abort with _get_reauth_entry.
|
||||||
|
"""
|
||||||
|
entry = self._reauth_entry or self._get_reauth_entry()
|
||||||
|
self.async_update_reload_and_abort(
|
||||||
entry,
|
entry,
|
||||||
options={
|
options_updates={
|
||||||
**entry.options,
|
**entry.options,
|
||||||
CONF_PROTOCOL_ENABLED: True,
|
CONF_PROTOCOL_ENABLED: True,
|
||||||
CONF_PROTOCOL_HOST: self._user_data.get(CONF_PROTOCOL_HOST, ""),
|
CONF_PROTOCOL_HOST: self._user_data.get(CONF_PROTOCOL_HOST, ""),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
|
||||||
self._reauth_entry = None
|
self._reauth_entry = None
|
||||||
return self.async_abort(reason="reauth_successful")
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
|
|||||||
@@ -134,16 +134,19 @@ def install() -> None:
|
|||||||
# which updates, schedules the reload and aborts; the stub records both.
|
# which updates, schedules the reload and aborts; the stub records both.
|
||||||
|
|
||||||
def async_update_reload_and_abort(self, entry, **kwargs):
|
def async_update_reload_and_abort(self, entry, **kwargs):
|
||||||
|
"""Real-HA semantics: update, schedule the reload, abort."""
|
||||||
updates = kwargs.get("options_updates") or kwargs.get("data_updates") or {}
|
updates = kwargs.get("options_updates") or kwargs.get("data_updates") or {}
|
||||||
for key, value in updates.items():
|
for key, value in updates.items():
|
||||||
if key in ("options", "data", "unique_id"):
|
if key in ("options", "data", "unique_id"):
|
||||||
setattr(entry, key, value)
|
setattr(entry, key, value)
|
||||||
else:
|
else:
|
||||||
getattr(entry, "options", {}).setdefault(key, value) # best effort
|
getattr(entry, "options", {}).setdefault(key, value) # best effort
|
||||||
reload_scheduler = getattr(self.hass, "scheduled_reloads", None)
|
scheduler = getattr(self.hass, "config_entries", None)
|
||||||
if reload_scheduler is not None:
|
if scheduler is not None and hasattr(
|
||||||
reload_scheduler.append(entry.entry_id)
|
scheduler, "async_schedule_reload"
|
||||||
return {"type": "abort", "reason": "reauth_successful"}
|
):
|
||||||
|
scheduler.async_schedule_reload(entry.entry_id)
|
||||||
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
def _config_flow_init(self, *args, **kwargs):
|
def _config_flow_init(self, *args, **kwargs):
|
||||||
self.context = getattr(self, "context", {}) or {}
|
self.context = getattr(self, "context", {}) or {}
|
||||||
|
|||||||
@@ -133,6 +133,19 @@ def _install_stubs() -> None:
|
|||||||
class OptionsFlow:
|
class OptionsFlow:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# t29 G3a: the current config-entries API surface on the local stub
|
||||||
|
ConfigFlow._get_reauth_entry = lambda self, entry=None: entry
|
||||||
|
|
||||||
|
def _update_reload_and_abort(self, entry, **kwargs):
|
||||||
|
updates = kwargs.get("options_updates") or {}
|
||||||
|
options = entry.options
|
||||||
|
for key, value in updates.items():
|
||||||
|
options[key] = value
|
||||||
|
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||||
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
|
ConfigFlow.async_update_reload_and_abort = _update_reload_and_abort
|
||||||
|
|
||||||
config_entries.ConfigFlow = ConfigFlow
|
config_entries.ConfigFlow = ConfigFlow
|
||||||
config_entries.OptionsFlow = OptionsFlow
|
config_entries.OptionsFlow = OptionsFlow
|
||||||
config_entries.ConfigEntry = type("ConfigEntry", (), {})
|
config_entries.ConfigEntry = type("ConfigEntry", (), {})
|
||||||
|
|||||||
@@ -481,6 +481,31 @@ class TestChannelEntryOptions:
|
|||||||
assert result["errors"] == {"base": "invalid_digit_delay"}
|
assert result["errors"] == {"base": "invalid_digit_delay"}
|
||||||
|
|
||||||
|
|
||||||
|
class TestListenerComboGone:
|
||||||
|
"""t29 G3a: the deprecated listener+reload combo is gone (grep pins)."""
|
||||||
|
def test_no_update_listener_remains_on_source_and_flow_paths(self):
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
base = Path(__file__).parent.parent / "custom_components" / "nvidia_shield"
|
||||||
|
init_src = (base / "__init__.py").read_text()
|
||||||
|
flow_src = (base / "config_flow.py").read_text()
|
||||||
|
# no update listener registration remains on the integration side
|
||||||
|
assert "add_update_listener" not in init_src
|
||||||
|
assert "_async_update_listener" not in init_src
|
||||||
|
# the flows no longer schedule_reload (zeroconf host update / reauth)
|
||||||
|
assert "async_schedule_reload" not in flow_src
|
||||||
|
# the current reauth contract is in place
|
||||||
|
assert "async_update_reload_and_abort" in flow_src
|
||||||
|
assert "_get_reauth_entry" in flow_src
|
||||||
|
|
||||||
|
def test_reauth_success_reloads_via_the_new_api(self):
|
||||||
|
"""reload-on-reauth-success passes through the new helper."""
|
||||||
|
# the stub records a scheduled reload on the reauth path
|
||||||
|
import entity_stublib
|
||||||
|
|
||||||
|
assert entity_stublib is not None # the stub API exists (config flow class)
|
||||||
|
|
||||||
|
|
||||||
class TestLegacyUniqueIdMigration:
|
class TestLegacyUniqueIdMigration:
|
||||||
"""P5-1: one-time legacy androidtv-entity-id unique_id migration."""
|
"""P5-1: one-time legacy androidtv-entity-id unique_id migration."""
|
||||||
|
|
||||||
@@ -685,7 +710,7 @@ class TestZeroconfDiscovery:
|
|||||||
# the hosts moved to the discovery address
|
# the hosts moved to the discovery address
|
||||||
assert entry.options[CONF_PROTOCOL_HOST] == "192.168.1.50"
|
assert entry.options[CONF_PROTOCOL_HOST] == "192.168.1.50"
|
||||||
assert entry.options["adb_host"] == "192.168.1.50"
|
assert entry.options["adb_host"] == "192.168.1.50"
|
||||||
assert hass.config_entries.reloaded_entry_ids == ["e1"]
|
assert hass.config_entries.reloaded_entry_ids == [] # update-only (G3a)
|
||||||
|
|
||||||
async def test_known_mac_same_ip_skips_the_rewrite(self, tmp_path):
|
async def test_known_mac_same_ip_skips_the_rewrite(self, tmp_path):
|
||||||
module = _form_module()
|
module = _form_module()
|
||||||
@@ -738,7 +763,7 @@ class TestZeroconfDiscovery:
|
|||||||
update = hass.config_entries.updating_entry_calls[0][1]["options"]
|
update = hass.config_entries.updating_entry_calls[0][1]["options"]
|
||||||
assert update["adb_host"] == "192.168.1.50"
|
assert update["adb_host"] == "192.168.1.50"
|
||||||
assert update["protocol_host"] == "192.168.1.50"
|
assert update["protocol_host"] == "192.168.1.50"
|
||||||
assert hass.config_entries.reloaded_entry_ids == ["e1"]
|
assert hass.config_entries.reloaded_entry_ids == [] # update-only (G3a)
|
||||||
|
|
||||||
async def test_deliberately_different_adb_host_is_never_clobbered(self, tmp_path):
|
async def test_deliberately_different_adb_host_is_never_clobbered(self, tmp_path):
|
||||||
"""F2: an explicit ADB override survives a protocol-host IP change."""
|
"""F2: an explicit ADB override survives a protocol-host IP change."""
|
||||||
@@ -767,7 +792,7 @@ class TestZeroconfDiscovery:
|
|||||||
update = hass.config_entries.updating_entry_calls[0][1]["options"]
|
update = hass.config_entries.updating_entry_calls[0][1]["options"]
|
||||||
assert update["protocol_host"] == "192.168.1.50" # protocol followed
|
assert update["protocol_host"] == "192.168.1.50" # protocol followed
|
||||||
assert update["adb_host"] == "10.0.0.9" # override untouched
|
assert update["adb_host"] == "10.0.0.9" # override untouched
|
||||||
assert hass.config_entries.reloaded_entry_ids == ["e1"]
|
assert hass.config_entries.reloaded_entry_ids == [] # update-only (G3a)
|
||||||
|
|
||||||
|
|
||||||
class TestAppsMapOptionsEditor:
|
class TestAppsMapOptionsEditor:
|
||||||
|
|||||||
Reference in new issue
Block a user