refactor(config): one Shield IP field for both transports (t41)
The two address fields were the same Shield spelled twice, and every bug in the zeroconf/IP-change path came from keeping them in sync. One field now: * const.CONF_HOST replaces CONF_ADB_HOST / CONF_PROTOCOL_HOST in the setup form, the options page, the pairing forms, the reconfigure/reauth seeding and the description copy. ADB port 5555 stays the only port field; * helpers.entry_host(entry) is the single resolver (options first, then data); resolve_adb_config and the protocol settings both go through it, so both transports read the same value at call time (the t32 F1 factory re-read keeps working: the NEXT built poller sees the new address); * helpers.seed_host / seed_adb_enabled own the YAML seed's shape rule. A block may use `host` or the legacy protocol_host/adb_host keys — t41 keeps the old spelling working — and t32 F4's outcome is unchanged: a protocol-only block leaves ADB off, an address (or an explicit adb_enabled) turns it on; * the zeroconf IP-change path collapses the t21-F2 per-host dance into one rule: the discovery address IS the entry's address, so the entry is updated when it differs and left alone when it matches. The blank-self-heal and deliberately-different-ADB-host cases are gone with the duplication — a second address field cannot express them anymore, and the tests now pin that (previously "an explicit override is never clobbered"); * the options page exposes the protocol toggle it never had (the page could turn ADB off but not the protocol); * button.py (granted to this task) reads the entry's own address instead of another integration's entity. The v3 entry migration that maps existing adb_host/protocol_host onto CONF_HOST and drops the androidtv key lands in the next commit; HA runs migrations before setup, so an existing entry is never read with the old shape. Verified: 311 passed / 2 skipped, 312 / 1 with androidtvremote2, ruff clean, drift engine exit 0 on HEAD. The UI-copy contract tests (every rendered field has a label in strings.json AND translations/en.json, key trees identical, host errors name the field) drove the string edits.
This commit is contained in:
1 parent
86aa2827a2
commit
75ebeb40b0
12 files changed
+302
-276
No files matched your search
@@ -21,11 +21,10 @@ import voluptuous_mirror as vol_mirror
|
||||
from conftest import _SHIELD_DIR, _load_module
|
||||
from nsh.const import (
|
||||
CONF_ADB_ENABLED,
|
||||
CONF_ADB_HOST,
|
||||
CONF_ADB_PORT,
|
||||
CONF_ANDROIDTV_ENTITY,
|
||||
CONF_HOST,
|
||||
CONF_PROTOCOL_ENABLED,
|
||||
CONF_PROTOCOL_HOST,
|
||||
)
|
||||
|
||||
_STUB_NAMES = (
|
||||
@@ -302,7 +301,7 @@ def _user_input(**overrides) -> dict:
|
||||
base = {
|
||||
"name": "mdarcy",
|
||||
CONF_PROTOCOL_ENABLED: False,
|
||||
CONF_PROTOCOL_HOST: "",
|
||||
CONF_HOST: "",
|
||||
CONF_ADB_ENABLED: False,
|
||||
}
|
||||
base.update(overrides)
|
||||
@@ -372,7 +371,7 @@ class TestStandaloneValidation:
|
||||
flow._user_data = {
|
||||
"name": "mdarcy",
|
||||
CONF_PROTOCOL_ENABLED: True,
|
||||
CONF_PROTOCOL_HOST: "shield.local",
|
||||
CONF_HOST: "shield.local",
|
||||
}
|
||||
|
||||
# skip the pairing route: drive _create_entry directly
|
||||
@@ -436,7 +435,7 @@ class TestMigrationKeepsOrAllowsAbsence:
|
||||
(
|
||||
entry,
|
||||
{
|
||||
"options": {CONF_PROTOCOL_ENABLED: False, CONF_PROTOCOL_HOST: ""},
|
||||
"options": {CONF_PROTOCOL_ENABLED: False, CONF_HOST: ""},
|
||||
"version": 2,
|
||||
},
|
||||
)
|
||||
@@ -449,7 +448,7 @@ class TestMigrationKeepsOrAllowsAbsence:
|
||||
entry = SimpleNamespace(
|
||||
version=2,
|
||||
data={},
|
||||
options={CONF_PROTOCOL_ENABLED: True, CONF_PROTOCOL_HOST: "shield.local"},
|
||||
options={CONF_PROTOCOL_ENABLED: True, CONF_HOST: "shield.local"},
|
||||
title="mdarcy",
|
||||
)
|
||||
|
||||
@@ -791,10 +790,10 @@ class TestIpChangeSelfHealF1:
|
||||
return SimpleNamespace(
|
||||
entry_id="e1",
|
||||
title="Shield",
|
||||
data={CONF_ANDROIDTV_ENTITY: "media_player.shield"},
|
||||
data={},
|
||||
options={
|
||||
CONF_ADB_ENABLED: True,
|
||||
CONF_ADB_HOST: host,
|
||||
CONF_HOST: host, # t41: the entry's one address
|
||||
CONF_ADB_PORT: 5555,
|
||||
},
|
||||
runtime_data=None,
|
||||
@@ -831,11 +830,7 @@ class TestIpChangeSelfHealF1:
|
||||
|
||||
# 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",
|
||||
}
|
||||
entry.options = {**entry.options, CONF_HOST: "192.168.1.77"}
|
||||
|
||||
second = factory()
|
||||
assert second is not None
|
||||
@@ -890,7 +885,7 @@ class TestMacLessDiscoveryF2:
|
||||
entry = SimpleNamespace(
|
||||
entry_id="e1",
|
||||
unique_id=None, # MAC-less entry
|
||||
options={CONF_PROTOCOL_HOST: "192.168.1.50"},
|
||||
options={CONF_HOST: "192.168.1.50"},
|
||||
title="Shield",
|
||||
)
|
||||
self._holder().entries[:] = [entry]
|
||||
@@ -951,7 +946,7 @@ class TestSeedBlockShapeF4:
|
||||
)
|
||||
|
||||
assert blocks[0]["adb_enabled"] is False
|
||||
assert blocks[0]["adb_host"] == ""
|
||||
assert blocks[0]["host"] == "shield.local" # t41: one address field
|
||||
|
||||
def test_adb_block_keeps_adb_on(self):
|
||||
import conftest
|
||||
@@ -1006,8 +1001,7 @@ class TestZeroconfDiscovery:
|
||||
assert result["step_id"] == "user"
|
||||
assert flow._discovered_host == "192.168.1.50"
|
||||
defaults = vol_mirror.defaults_by_key(result["data_schema"])
|
||||
assert defaults[CONF_ADB_HOST] == "192.168.1.50"
|
||||
assert defaults[CONF_PROTOCOL_HOST] == "192.168.1.50"
|
||||
assert defaults[CONF_HOST] == "192.168.1.50"
|
||||
# ...and a discovery-driven form says the address is already known
|
||||
note = result["description_placeholders"]["discovery_note"]
|
||||
assert "Discovered Shield at 192.168.1.50" in note
|
||||
@@ -1031,7 +1025,7 @@ class TestZeroconfDiscovery:
|
||||
|
||||
entry = SimpleNamespace(
|
||||
unique_id="aa:bb:cc:dd:ee:ff",
|
||||
options={CONF_PROTOCOL_ENABLED: True, CONF_PROTOCOL_HOST: "10.0.0.1"},
|
||||
options={CONF_PROTOCOL_ENABLED: True, CONF_HOST: "10.0.0.1"},
|
||||
entry_id="e1",
|
||||
title="Shield",
|
||||
)
|
||||
@@ -1042,9 +1036,8 @@ class TestZeroconfDiscovery:
|
||||
)
|
||||
|
||||
assert result == {"type": "abort", "reason": "already_configured"}
|
||||
# the hosts moved to the discovery address
|
||||
assert entry.options[CONF_PROTOCOL_HOST] == "192.168.1.50"
|
||||
assert entry.options["adb_host"] == "192.168.1.50"
|
||||
# t41: the ONE address field moved to the discovery address
|
||||
assert entry.options[CONF_HOST] == "192.168.1.50"
|
||||
assert hass.config_entries.reloaded_entry_ids == [] # update-only (G3a)
|
||||
|
||||
async def test_known_mac_same_ip_skips_the_rewrite(self, tmp_path):
|
||||
@@ -1053,14 +1046,10 @@ class TestZeroconfDiscovery:
|
||||
hass = FakeHass(str(tmp_path))
|
||||
flow.hass = hass
|
||||
|
||||
# both hosts already hold the discovered address: nothing changes
|
||||
# the address already holds the discovered value: nothing changes
|
||||
entry = SimpleNamespace(
|
||||
unique_id="aa:bb:cc:dd:ee:ff",
|
||||
options={
|
||||
CONF_PROTOCOL_ENABLED: True,
|
||||
CONF_PROTOCOL_HOST: "192.168.1.50",
|
||||
"adb_host": "192.168.1.50",
|
||||
},
|
||||
options={CONF_PROTOCOL_ENABLED: True, CONF_HOST: "192.168.1.50"},
|
||||
entry_id="e1",
|
||||
title="Shield",
|
||||
)
|
||||
@@ -1074,8 +1063,24 @@ class TestZeroconfDiscovery:
|
||||
assert hass.config_entries.updating_entry_calls == [] # no IP change
|
||||
assert hass.config_entries.reloaded_entry_ids == []
|
||||
|
||||
async def test_blank_adb_host_self_heals_from_the_discovery_address(self, tmp_path):
|
||||
"""F2: a blank (auto-detect) adb host follows a same-IP rediscovery."""
|
||||
async def test_the_per_host_heal_cases_collapsed_into_one_rule(self):
|
||||
"""t41: the blank / self-heal / deliberate-override cases are gone.
|
||||
|
||||
They only existed because the address was stored twice (t21 F2). With one
|
||||
field the discovery address simply IS the entry's address, so this pins
|
||||
the simplification: a second address field cannot quietly come back.
|
||||
"""
|
||||
source = (_SHIELD_DIR / "config_flow.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "CONF_ADB_HOST" not in source
|
||||
assert "CONF_PROTOCOL_HOST" not in source
|
||||
|
||||
async def test_an_explicit_address_follows_the_device(self, tmp_path):
|
||||
"""t41: one Shield, one address — discovery is its current source.
|
||||
|
||||
The old rule kept a "deliberately different ADB host" untouched; that
|
||||
override is precisely what the single-field ruling removes.
|
||||
"""
|
||||
module = _form_module()
|
||||
flow = module.NvidiaShieldConfigFlow()
|
||||
hass = FakeHass(str(tmp_path))
|
||||
@@ -1083,37 +1088,7 @@ class TestZeroconfDiscovery:
|
||||
|
||||
entry = SimpleNamespace(
|
||||
unique_id="aa:bb:cc:dd:ee:ff",
|
||||
options={CONF_PROTOCOL_ENABLED: True, CONF_PROTOCOL_HOST: "192.168.1.50"},
|
||||
entry_id="e1",
|
||||
title="Shield",
|
||||
)
|
||||
sys.modules[__name__]._zc_holder.entries[:] = [entry]
|
||||
|
||||
result = await flow.async_step_zeroconf(
|
||||
self._discovery(props={"mac": "aa:bb:cc:dd:ee:ff"})
|
||||
)
|
||||
|
||||
assert result == {"type": "abort", "reason": "already_configured"}
|
||||
# only the adb host was filled (self-heal), protocol already current:
|
||||
update = hass.config_entries.updating_entry_calls[0][1]["options"]
|
||||
assert update["adb_host"] == "192.168.1.50"
|
||||
assert update["protocol_host"] == "192.168.1.50"
|
||||
assert hass.config_entries.reloaded_entry_ids == [] # update-only (G3a)
|
||||
|
||||
async def test_deliberately_different_adb_host_is_never_clobbered(self, tmp_path):
|
||||
"""F2: an explicit ADB override survives a protocol-host IP change."""
|
||||
module = _form_module()
|
||||
flow = module.NvidiaShieldConfigFlow()
|
||||
hass = FakeHass(str(tmp_path))
|
||||
flow.hass = hass
|
||||
|
||||
entry = SimpleNamespace(
|
||||
unique_id="aa:bb:cc:dd:ee:ff",
|
||||
options={
|
||||
CONF_PROTOCOL_ENABLED: True,
|
||||
CONF_PROTOCOL_HOST: "10.0.0.1", # stale (IP moved)
|
||||
"adb_host": "10.0.0.9", # deliberate ADB override
|
||||
},
|
||||
options={CONF_PROTOCOL_ENABLED: True, CONF_HOST: "10.0.0.9"}, # stale
|
||||
entry_id="e1",
|
||||
title="Shield",
|
||||
)
|
||||
@@ -1125,8 +1100,7 @@ class TestZeroconfDiscovery:
|
||||
|
||||
assert result == {"type": "abort", "reason": "already_configured"}
|
||||
update = hass.config_entries.updating_entry_calls[0][1]["options"]
|
||||
assert update["protocol_host"] == "192.168.1.50" # protocol followed
|
||||
assert update["adb_host"] == "10.0.0.9" # override untouched
|
||||
assert update[CONF_HOST] == "192.168.1.50"
|
||||
assert hass.config_entries.reloaded_entry_ids == [] # update-only (G3a)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user