fix(poller): F1 — the poller factory re-reads entry.options at call time (audit-sanctioned option), so the next built poller targets the NEW address after a zeroconf IP change; the stale live-read comment in config_flow points at the actual mechanism; TestIpChangeSelfHealF1 drives an IP change and pins the next poller host (t32)
This commit is contained in:
1 parent
c1a82a2fa5
commit
8f40b6435c
3 files changed
+91
-2
No files matched your search
@@ -10,11 +10,16 @@ migration validity for v2 entries without the field.
|
||||
import asyncio # noqa: F401 (async steps need the event loop)
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path # noqa: E402 (F1 test key staging)
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from conftest import _SHIELD_DIR, _load_module
|
||||
from nsh.const import (
|
||||
CONF_ADB_ENABLED,
|
||||
CONF_ADB_HOST,
|
||||
CONF_ADB_PORT,
|
||||
CONF_ANDROIDTV_ENTITY,
|
||||
CONF_PROTOCOL_ENABLED,
|
||||
CONF_PROTOCOL_HOST,
|
||||
@@ -758,6 +763,78 @@ class TestLegacyUniqueIdMigration:
|
||||
assert hass.config_entries.updating_entry_calls == [] # untouched
|
||||
|
||||
|
||||
class TestIpChangeSelfHealF1:
|
||||
"""t32 F1 (HIGH): the poller factory re-reads entry.options at call
|
||||
time, so the NEXT built poller targets the NEW address after a
|
||||
zeroconf IP change (the audit-sanctioned factory re-read option)."""
|
||||
|
||||
def _adb_entry(self, host="192.168.1.50"):
|
||||
return SimpleNamespace(
|
||||
entry_id="e1",
|
||||
title="Shield",
|
||||
data={CONF_ANDROIDTV_ENTITY: "media_player.shield"},
|
||||
options={
|
||||
CONF_ADB_ENABLED: True,
|
||||
CONF_ADB_HOST: host,
|
||||
CONF_ADB_PORT: 5555,
|
||||
},
|
||||
runtime_data=None,
|
||||
)
|
||||
|
||||
def _hass_with_key(self, tmp_path):
|
||||
import conftest
|
||||
import entity_stublib
|
||||
|
||||
entity_stublib.install()
|
||||
if "nsh.init" not in sys.modules:
|
||||
conftest._load_module("nsh.init", conftest._SHIELD_DIR / "__init__.py")
|
||||
init_module = sys.modules["nsh.init"]
|
||||
hass = FakeHass(str(tmp_path))
|
||||
key_dir = Path(hass.config.config_dir) / ".storage" / "nvidia_shield"
|
||||
key_dir.mkdir(parents=True, exist_ok=True)
|
||||
(key_dir / "adbkey").write_text("test-key")
|
||||
return init_module, hass
|
||||
|
||||
async def test_built_poller_targets_the_new_address(self, tmp_path):
|
||||
"""The delivered self-heal: after the zeroconf host update the
|
||||
NEXT built poller targets the new address (no reload involved)."""
|
||||
init_module, hass = self._hass_with_key(tmp_path)
|
||||
entry = self._adb_entry()
|
||||
hass.config_entries.entries = [entry]
|
||||
|
||||
factory = await init_module._build_poller_factory(hass, entry)
|
||||
assert factory is not None
|
||||
|
||||
first = factory()
|
||||
assert first is not None
|
||||
# AdbClientBase exposes the connection config as a dict (t27 F-NEW3)
|
||||
assert first.config["host"] == "192.168.1.50"
|
||||
|
||||
# the config-flow zeroconf path updates the entry's hosts via
|
||||
# async_update_entry — the NEXT built poller targets the NEW address
|
||||
entry.options = {
|
||||
**entry.options,
|
||||
CONF_ADB_HOST: "192.168.1.77",
|
||||
CONF_PROTOCOL_HOST: "192.168.1.77",
|
||||
}
|
||||
|
||||
second = factory()
|
||||
assert second is not None
|
||||
assert second.config["host"] == "192.168.1.77"
|
||||
|
||||
async def test_unresolvable_call_stays_passive(self, tmp_path):
|
||||
"""A momentary unresolvable host yields None (passive cycle) — the
|
||||
watchdog's existing None handling stays intact."""
|
||||
init_module, hass = self._hass_with_key(tmp_path)
|
||||
entry = self._adb_entry(host="10.255.255.1")
|
||||
|
||||
factory = await init_module._build_poller_factory(hass, entry)
|
||||
assert factory is not None
|
||||
|
||||
with mock.patch("nsh.init.resolve_adb_config", return_value=None):
|
||||
assert factory() is None
|
||||
|
||||
|
||||
class TestZeroconfDiscovery:
|
||||
"""P4-1a: _androidtvremote2._tcp discovery → pre-filled user step."""
|
||||
|
||||
|
||||
Reference in new issue
Block a user