feat(config): enable both native transports by default (t41)

The transports are ours now, so setup offers them ON rather than OFF: a fresh
form renders "Read state over ADB" and "Add the Nvidia Remote Protocol" checked,
with the Shield's address pre-filled by discovery (or typed by the user). The
schema default, the async_step_user fallback and the _create_entry fallback all
carry True, so the checkbox the user sees and the entry that gets written cannot
disagree — the t33 F-A lesson applied to the new defaults.

Still a choice, not a mandate:
* unchecking either toggle routes to the other transport's pairing step;
* each pairing step keeps its "continue without pairing" escape (the transport
  stays off until paired), so a passive entry is still one click away;
* no_state_source now means exactly "neither transport enabled" — and a payload
  that names neither is REJECTED rather than silently enabling both (fail
  closed; a real submission always carries the schema defaults).
* the YAML seed keeps t32 F4's shape rule: an address turns ADB on, a
  protocol-only block leaves it off, an explicit adb_enabled wins.

Verified: 323 passed / 2 skipped, 324 / 1 with androidtvremote2, ruff clean,
drift engine exit 0.
This commit is contained in:
James Mackay committed 2026-10-11 06:20:41 +01:00
1 parent 81f70585cd
commit fb481c3c14
2 files changed
+114 -8

No files matched your search

+104
View File
@@ -581,6 +581,110 @@ class TestDiscoveryPrefill:
assert defaults[CONF_HOST] == DISCOVERED_HOST
class TestTransportsDefaultOn:
"""t41: the two native transports are OURS — both start enabled.
The ruling's other half: they are still choices. Turning either off (or
skipping its pairing) stays a first-class path, and unchecking BOTH is the
only way to reach ``no_state_source``.
"""
async def test_the_fresh_form_renders_both_toggles_checked(self, tmp_path):
module = _form_module()
flow, _hass = _new_flow(module, tmp_path)
result = await flow.async_step_user(None)
defaults = vol_mirror.defaults_by_key(result["data_schema"])
assert defaults[CONF_ADB_ENABLED] is True
assert defaults[CONF_PROTOCOL_ENABLED] is True
# the ONE address field is offered, empty until discovery or typing
assert CONF_HOST in vol_mirror.declared_fields(result["data_schema"])
async def test_a_discovered_form_prefills_the_host_and_both_toggles(
self, tmp_path
):
module = _form_module()
flow, _hass = _new_flow(module, tmp_path)
result = await flow.async_step_zeroconf(_discovery())
defaults = vol_mirror.defaults_by_key(result["data_schema"])
assert defaults[CONF_HOST] == DISCOVERED_HOST
assert defaults[CONF_ADB_ENABLED] is True
assert defaults[CONF_PROTOCOL_ENABLED] is True
async def test_a_submission_naming_neither_transport_is_rejected(
self, tmp_path, monkeypatch
):
"""Fail closed: the step never assumes a transport the form did not send.
The rendered schema carries both defaults, so a real submission always
names them (t33 F-A) — but a payload without them must not silently turn
both transports on.
"""
module = _form_module()
_patch_protocol(monkeypatch, module)
flow, _hass = _new_flow(module, tmp_path)
result = await flow.async_step_user(
{"name": "mdarcy", CONF_HOST: DISCOVERED_HOST, CONF_ADB_PORT: 5555}
)
assert result["errors"] == {"base": "no_state_source"}
async def test_the_code_fallback_matches_the_rendered_default(self, tmp_path):
"""`_create_entry` records ON when its data never named the toggles.
t33 F-A's rule: the code's fallback must reproduce what the checkbox
promised, so a partially-filled `_user_data` (a YAML seed) cannot come
out of setup with both transports off.
"""
module = _form_module()
flow, _hass = _new_flow(module, tmp_path)
flow._user_data = {"name": "mdarcy", CONF_HOST: DISCOVERED_HOST}
result = flow._create_entry()
assert result["options"][CONF_ADB_ENABLED] is True
assert result["options"][CONF_PROTOCOL_ENABLED] is True
async def test_adb_alone_is_still_reachable(self, tmp_path, monkeypatch):
module = _form_module()
_patch_adb(monkeypatch, module)
flow, _hass = _new_flow(module, tmp_path)
result = await flow.async_step_user(
_user_input(**{CONF_ADB_ENABLED: True, CONF_PROTOCOL_ENABLED: False})
)
assert result["step_id"] == "adb_pair"
assert flow._user_data[CONF_PROTOCOL_ENABLED] is False
assert flow._user_data[CONF_ADB_ENABLED] is True
async def test_protocol_alone_is_still_reachable(self, tmp_path, monkeypatch):
module = _form_module()
_patch_protocol(monkeypatch, module)
flow, _hass = _new_flow(module, tmp_path)
result = await flow.async_step_user(
_user_input(**{CONF_ADB_ENABLED: False, CONF_PROTOCOL_ENABLED: True})
)
assert result["step_id"] == "protocol_pair"
assert flow._user_data[CONF_ADB_ENABLED] is False
async def test_neither_transport_is_the_only_error_path(self, tmp_path):
module = _form_module()
flow, _hass = _new_flow(module, tmp_path)
result = await flow.async_step_user(
_user_input(**{CONF_ADB_ENABLED: False, CONF_PROTOCOL_ENABLED: False})
)
assert result["errors"] == {"base": "no_state_source"}
class TestNoDeadEndPairing:
"""F-B: both pairing steps offer 'continue without pairing'."""